diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml index 4eb11a3ac8572..5c69714bc1e99 100644 --- a/src/tools/clippy/.github/workflows/clippy_bors.yml +++ b/src/tools/clippy/.github/workflows/clippy_bors.yml @@ -187,16 +187,14 @@ jobs: - name: Extract Binaries run: | DIR=$CARGO_TARGET_DIR/debug - rm $DIR/deps/integration-*.d - mv $DIR/deps/integration-* $DIR/integration + find $DIR/deps/integration-* -executable ! -type d | xargs -I {} mv {} $DIR/integration find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf - rm -rf $CARGO_TARGET_DIR/release - name: Upload Binaries - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: - name: target - path: target + name: binaries + path: target/debug integration: needs: integration_build @@ -206,22 +204,20 @@ jobs: matrix: integration: - 'rust-lang/cargo' - # FIXME: re-enable once fmt_macros is renamed in RLS - # - 'rust-lang/rls' - 'rust-lang/chalk' - 'rust-lang/rustfmt' - 'Marwes/combine' - 'Geal/nom' - 'rust-lang/stdarch' - 'serde-rs/serde' - # FIXME: chrono currently cannot be compiled with `--all-targets` - # - 'chronotope/chrono' + - 'chronotope/chrono' - 'hyperium/hyper' - 'rust-random/rand' - 'rust-lang/futures-rs' - 'rust-itertools/itertools' - 'rust-lang-nursery/failure' - 'rust-lang/log' + - 'matthiaskrgr/clippy_ci_panic_test' runs-on: ubuntu-latest @@ -237,12 +233,17 @@ jobs: - name: Install toolchain run: rustup show active-toolchain + - name: Set LD_LIBRARY_PATH + run: | + SYSROOT=$(rustc --print sysroot) + echo "LD_LIBRARY_PATH=${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" >> $GITHUB_ENV + # Download - name: Download target dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: - name: target - path: target + name: binaries + path: target/debug - name: Make Binaries Executable run: chmod +x $CARGO_TARGET_DIR/debug/* @@ -251,7 +252,7 @@ jobs: - name: Test ${{ matrix.integration }} run: | RUSTUP_TOOLCHAIN="$(rustup show active-toolchain | grep -o -E "nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}")" \ - $CARGO_TARGET_DIR/debug/integration + $CARGO_TARGET_DIR/debug/integration --show-output env: INTEGRATION: ${{ matrix.integration }} diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index b3b6e3b865fce..2655d93599e7e 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -4680,6 +4680,7 @@ Released 2018-09-13 +[`absolute_paths`]: https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths [`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 @@ -4819,6 +4820,7 @@ Released 2018-09-13 [`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let [`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op [`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect +[`error_impl_error`]: https://rust-lang.github.io/rust-clippy/master/index.html#error_impl_error [`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence [`excessive_nesting`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting [`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision @@ -4842,6 +4844,7 @@ Released 2018-09-13 [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default [`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file [`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map +[`filter_map_bool_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_bool_then [`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity [`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next [`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next @@ -4865,8 +4868,10 @@ Released 2018-09-13 [`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy [`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref +[`format_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_collect [`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args [`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string +[`four_forward_slashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#four_forward_slashes [`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect [`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into [`from_raw_with_void_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_raw_with_void_ptr @@ -4937,6 +4942,7 @@ Released 2018-09-13 [`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items [`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next +[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero [`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain [`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits @@ -5092,6 +5098,7 @@ Released 2018-09-13 [`needless_raw_string_hashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string_hashes [`needless_raw_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_strings [`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return +[`needless_return_with_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return_with_question_mark [`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn [`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update [`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord @@ -5174,6 +5181,7 @@ Released 2018-09-13 [`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex [`read_line_without_trim`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_line_without_trim [`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec +[`readonly_write_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#readonly_write_lock [`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation [`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block @@ -5185,6 +5193,8 @@ Released 2018-09-13 [`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names +[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching [`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate @@ -5254,6 +5264,7 @@ Released 2018-09-13 [`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars [`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes [`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes +[`string_lit_chars_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_chars_any [`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice [`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string [`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings @@ -5362,6 +5373,7 @@ Released 2018-09-13 [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit [`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings [`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result +[`unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default [`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default [`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used [`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms @@ -5462,4 +5474,6 @@ Released 2018-09-13 [`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement [`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes [`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings +[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments +[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md index f8073dac3301a..caaad6d11736f 100644 --- a/src/tools/clippy/book/src/lint_configuration.md +++ b/src/tools/clippy/book/src/lint_configuration.md @@ -730,3 +730,24 @@ Whether to allow `r#""#` when `r""` can be used * [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes) +## `absolute-paths-max-segments` +The maximum number of segments a path can have before being linted, anything above this will +be linted. + +**Default Value:** `2` (`u64`) + +--- +**Affected lints:** +* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths) + + +## `absolute-paths-allowed-crates` +Which crates to allow absolute paths from + +**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet`) + +--- +**Affected lints:** +* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths) + + diff --git a/src/tools/clippy/clippy_dev/src/lib.rs b/src/tools/clippy/clippy_dev/src/lib.rs index 4624451cff4fc..c4ae4f0e2bdd6 100644 --- a/src/tools/clippy/clippy_dev/src/lib.rs +++ b/src/tools/clippy/clippy_dev/src/lib.rs @@ -51,7 +51,7 @@ pub fn clippy_project_root() -> PathBuf { for path in current_dir.ancestors() { let result = std::fs::read_to_string(path.join("Cargo.toml")); if let Err(err) = &result { - if err.kind() == std::io::ErrorKind::NotFound { + if err.kind() == io::ErrorKind::NotFound { continue; } } diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs index f39bc06e6d732..e64cf2c874968 100644 --- a/src/tools/clippy/clippy_dev/src/new_lint.rs +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -96,8 +96,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { path.push("src"); fs::create_dir(&path)?; - let header = format!("//@compile-flags: --crate-name={lint_name}"); - write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?; + write_file(path.join("main.rs"), get_test_file_contents(lint_name))?; Ok(()) } @@ -113,7 +112,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`"); } else { let test_path = format!("tests/ui/{}.rs", lint.name); - let test_contents = get_test_file_contents(lint.name, None); + let test_contents = get_test_file_contents(lint.name); write_file(lint.project_root.join(&test_path), test_contents)?; println!("Generated test file: `{test_path}`"); @@ -195,23 +194,16 @@ pub(crate) fn get_stabilization_version() -> String { parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") } -fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String { - let mut contents = formatdoc!( +fn get_test_file_contents(lint_name: &str) -> String { + formatdoc!( r#" - #![allow(unused)] #![warn(clippy::{lint_name})] fn main() {{ // test code goes here }} "# - ); - - if let Some(header) = header_commands { - contents = format!("{header}\n{contents}"); - } - - contents + ) } fn get_manifest_contents(lint_name: &str, hint: &str) -> String { diff --git a/src/tools/clippy/clippy_lints/src/absolute_paths.rs b/src/tools/clippy/clippy_lints/src/absolute_paths.rs new file mode 100644 index 0000000000000..04417c4c46007 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/absolute_paths.rs @@ -0,0 +1,100 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::source::snippet_opt; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc_hir::{HirId, ItemKind, Node, Path}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of items through absolute paths, like `std::env::current_dir`. + /// + /// ### Why is this bad? + /// Many codebases have their own style when it comes to importing, but one that is seldom used + /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you + /// should add a `use` statement. + /// + /// The default maximum segments (2) is pretty strict, you may want to increase this in + /// `clippy.toml`. + /// + /// Note: One exception to this is code from macro expansion - this does not lint such cases, as + /// using absolute paths is the proper way of referencing items in one. + /// + /// ### Example + /// ```rust + /// let x = std::f64::consts::PI; + /// ``` + /// Use any of the below instead, or anything else: + /// ```rust + /// use std::f64; + /// use std::f64::consts; + /// use std::f64::consts::PI; + /// let x = f64::consts::PI; + /// let x = consts::PI; + /// let x = PI; + /// use std::f64::consts as f64_consts; + /// let x = f64_consts::PI; + /// ``` + #[clippy::version = "1.73.0"] + pub ABSOLUTE_PATHS, + restriction, + "checks for usage of an item without a `use` statement" +} +impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); + +pub struct AbsolutePaths { + pub absolute_paths_max_segments: u64, + pub absolute_paths_allowed_crates: FxHashSet, +} + +impl LateLintPass<'_> for AbsolutePaths { + // We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath` + // we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't + // a `Use` + #[expect(clippy::cast_possible_truncation)] + fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) { + let Self { + absolute_paths_max_segments, + absolute_paths_allowed_crates, + } = self; + + if !path.span.from_expansion() + && let Some(node) = cx.tcx.hir().find(hir_id) + && !matches!(node, Node::Item(item) if matches!(item.kind, ItemKind::Use(_, _))) + && let [first, rest @ ..] = path.segments + // Handle `::std` + && let (segment, len) = if first.ident.name == kw::PathRoot { + // Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1` + // is fine here for the same reason + (&rest[0], path.segments.len() - 1) + } else { + (first, path.segments.len()) + } + && len > *absolute_paths_max_segments as usize + && let Some(segment_snippet) = snippet_opt(cx, segment.ident.span) + && segment_snippet == segment.ident.as_str() + { + let is_abs_external = + matches!(segment.res, Res::Def(DefKind::Mod, DefId { index, .. }) if index == CRATE_DEF_INDEX); + let is_abs_crate = segment.ident.name == kw::Crate; + + if is_abs_external && absolute_paths_allowed_crates.contains(segment.ident.name.as_str()) + || is_abs_crate && absolute_paths_allowed_crates.contains("crate") + { + return; + } + + if is_abs_external || is_abs_crate { + span_lint( + cx, + ABSOLUTE_PATHS, + path.span, + "consider bringing this path into scope with the `use` keyword", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs b/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs index 7adcd9ad05559..35a04b5e44a30 100644 --- a/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs +++ b/src/tools/clippy/clippy_lints/src/arc_with_non_send_sync.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::last_path_segment; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::{is_from_proc_macro, last_path_segment}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -38,10 +38,11 @@ declare_clippy_lint! { } declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]); -impl LateLintPass<'_> for ArcWithNonSendSync { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, ty, sym::Arc) +impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if !expr.span.from_expansion() + && let ty = cx.typeck_results().expr_ty(expr) + && is_type_diagnostic_item(cx, ty, sym::Arc) && let ExprKind::Call(func, [arg]) = expr.kind && let ExprKind::Path(func_path) = func.kind && last_path_segment(&func_path).ident.name == sym::new @@ -54,6 +55,7 @@ impl LateLintPass<'_> for ArcWithNonSendSync { && let Some(sync) = cx.tcx.lang_items().sync_trait() && let [is_send, is_sync] = [send, sync].map(|id| implements_trait(cx, arg_ty, id, &[])) && !(is_send && is_sync) + && !is_from_proc_macro(cx, expr) { span_lint_and_then( cx, diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs index 0ac6ef6496a8a..d34de305f5dce 100644 --- a/src/tools/clippy/clippy_lints/src/casts/mod.rs +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -181,6 +181,14 @@ declare_clippy_lint! { /// ### Why is this bad? /// It's just unnecessary. /// + /// ### Known problems + /// When the expression on the left is a function call, the lint considers the return type to be + /// a type alias if it's aliased through a `use` statement + /// (like `use std::io::Result as IoResult`). It will not lint such cases. + /// + /// This check is also rather primitive. It will only work on primitive types without any + /// intermediate references, raw pointers and trait objects may or may not work. + /// /// ### Example /// ```rust /// let _ = 2i32 as i32; diff --git a/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs index ae56f38d9ad57..86057bb74ee9d 100644 --- a/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs +++ b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs @@ -85,11 +85,6 @@ pub(super) fn check<'tcx>( } } - // skip cast of fn call that returns type alias - if let ExprKind::Cast(inner, ..) = expr.kind && is_cast_from_ty_alias(cx, inner, cast_from) { - return false; - } - // skip cast to non-primitive type if_chain! { if let ExprKind::Cast(_, cast_to) = expr.kind; @@ -101,6 +96,11 @@ pub(super) fn check<'tcx>( } } + // skip cast of fn call that returns type alias + if let ExprKind::Cast(inner, ..) = expr.kind && is_cast_from_ty_alias(cx, inner, cast_from) { + return false; + } + if let Some(lit) = get_numeric_literal(cast_expr) { let literal_str = &cast_str; @@ -254,14 +254,12 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx // function's declaration snippet is exactly equal to the `Ty`. That way, we can // see whether it's a type alias. // - // Will this work for more complex types? Probably not! + // FIXME: This won't work if the type is given an alias through `use`, should we + // consider this a type alias as well? if !snippet .split("->") - .skip(0) - .map(|s| { - s.trim() == cast_from.to_string() - || s.split("where").any(|ty| ty.trim() == cast_from.to_string()) - }) + .skip(1) + .map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) .any(|a| a) { return ControlFlow::Break(()); @@ -288,3 +286,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx }) .is_some() } + +fn snippet_eq_ty(snippet: &str, ty: Ty<'_>) -> bool { + snippet.trim() == ty.to_string() || snippet.trim().contains(&format!("::{ty}")) +} diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 498d657b31f9b..d4e0d2863348b 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -37,6 +37,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::absolute_paths::ABSOLUTE_PATHS_INFO, crate::allow_attributes::ALLOW_ATTRIBUTES_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, @@ -155,6 +156,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::enum_variants::MODULE_INCEPTION_INFO, crate::enum_variants::MODULE_NAME_REPETITIONS_INFO, crate::equatable_if_let::EQUATABLE_IF_LET_INFO, + crate::error_impl_error::ERROR_IMPL_ERROR_INFO, crate::escape::BOXED_LOCAL_INFO, crate::eta_reduction::REDUNDANT_CLOSURE_INFO, crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO, @@ -183,6 +185,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO, crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO, crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO, + crate::four_forward_slashes::FOUR_FORWARD_SLASHES_INFO, crate::from_over_into::FROM_OVER_INTO_INFO, crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, @@ -305,6 +308,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::MATCH_WILD_ERR_ARM_INFO, crate::matches::NEEDLESS_MATCH_INFO, + crate::matches::REDUNDANT_GUARDS_INFO, crate::matches::REDUNDANT_PATTERN_MATCHING_INFO, crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO, crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO, @@ -333,11 +337,13 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::EXPECT_USED_INFO, crate::methods::EXTEND_WITH_DRAIN_INFO, crate::methods::FILETYPE_IS_FILE_INFO, + crate::methods::FILTER_MAP_BOOL_THEN_INFO, crate::methods::FILTER_MAP_IDENTITY_INFO, crate::methods::FILTER_MAP_NEXT_INFO, crate::methods::FILTER_NEXT_INFO, crate::methods::FLAT_MAP_IDENTITY_INFO, crate::methods::FLAT_MAP_OPTION_INFO, + crate::methods::FORMAT_COLLECT_INFO, crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO, crate::methods::GET_FIRST_INFO, crate::methods::GET_LAST_WITH_LEN_INFO, @@ -358,6 +364,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::ITER_ON_SINGLE_ITEMS_INFO, crate::methods::ITER_OVEREAGER_CLONED_INFO, crate::methods::ITER_SKIP_NEXT_INFO, + crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, @@ -391,6 +398,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::OR_THEN_UNWRAP_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, + crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, @@ -403,6 +411,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, + crate::methods::STRING_LIT_CHARS_ANY_INFO, crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, @@ -418,7 +427,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, crate::methods::UNNECESSARY_SORT_BY_INFO, crate::methods::UNNECESSARY_TO_OWNED_INFO, - crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO, + crate::methods::UNWRAP_OR_DEFAULT_INFO, crate::methods::UNWRAP_USED_INFO, crate::methods::USELESS_ASREF_INFO, crate::methods::VEC_RESIZE_TO_ZERO_INFO, @@ -555,6 +564,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO, crate::redundant_else::REDUNDANT_ELSE_INFO, crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO, + crate::redundant_locals::REDUNDANT_LOCALS_INFO, crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO, crate::redundant_slicing::DEREF_BY_SLICING_INFO, crate::redundant_slicing::REDUNDANT_SLICING_INFO, @@ -568,6 +578,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO, crate::returns::LET_AND_RETURN_INFO, crate::returns::NEEDLESS_RETURN_INFO, + crate::returns::NEEDLESS_RETURN_WITH_QUESTION_MARK_INFO, crate::same_name_method::SAME_NAME_METHOD_INFO, crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO, crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO, diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index 8a9d978a1063f..a5ec979ccd978 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -3,21 +3,23 @@ use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exact use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{adt_and_variant_of_res, expr_sig, is_copy, peel_mid_ty_refs, ty_sig}; +use clippy_utils::ty::{is_copy, peel_mid_ty_refs}; use clippy_utils::{ - fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage, + expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, }; +use hir::def::DefKind; +use hir::MatchSource; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch}; use rustc_errors::Applicability; +use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, - ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, - TraitItemKind, TyKind, UnOp, + self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, + Path, QPath, TyKind, UnOp, }; use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; @@ -25,8 +27,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{ - self, Binder, BoundVariableKind, ClauseKind, EarlyBinder, FnSig, GenericArgKind, List, ParamEnv, ParamTy, - ProjectionPredicate, Ty, TyCtxt, TypeVisitableExt, TypeckResults, + self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamEnv, ParamTy, ProjectionPredicate, Ty, + TyCtxt, TypeVisitableExt, TypeckResults, }; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::sym; @@ -163,7 +165,7 @@ impl_lint_pass!(Dereferencing<'_> => [ #[derive(Default)] pub struct Dereferencing<'tcx> { - state: Option<(State, StateData)>, + state: Option<(State, StateData<'tcx>)>, // While parsing a `deref` method call in ufcs form, the path to the function is itself an // expression. This is to store the id of that expression so it can be skipped when @@ -203,29 +205,28 @@ impl<'tcx> Dereferencing<'tcx> { } #[derive(Debug)] -struct StateData { +struct StateData<'tcx> { /// Span of the top level expression span: Span, hir_id: HirId, - position: Position, + adjusted_ty: Ty<'tcx>, } -#[derive(Debug)] struct DerefedBorrow { count: usize, msg: &'static str, - snip_expr: Option, + stability: TyCoercionStability, + for_field_access: Option, } -#[derive(Debug)] enum State { // Any number of deref method calls. DerefMethod { // The number of calls in a sequence which changed the referenced type ty_changed_count: usize, - is_final_ufcs: bool, + is_ufcs: bool, /// The required mutability - target_mut: Mutability, + mutbl: Mutability, }, DerefedBorrow(DerefedBorrow), ExplicitDeref { @@ -244,7 +245,7 @@ enum State { // A reference operation considered by this lint pass enum RefOp { - Method(Mutability), + Method { mutbl: Mutability, is_ufcs: bool }, Deref, AddrOf(Mutability), } @@ -294,48 +295,115 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, &self.msrv); - match kind { - RefOp::Deref => { + let use_cx = expr_use_ctxt(cx, expr); + let adjusted_ty = match &use_cx { + Some(use_cx) => match use_cx.adjustments { + [.., a] => a.target, + _ => expr_ty, + }, + _ => typeck.expr_ty_adjusted(expr), + }; + + match (use_cx, kind) { + (Some(use_cx), RefOp::Deref) => { let sub_ty = typeck.expr_ty(sub_expr); - if let Position::FieldAccess { - name, - of_union: false, - } = position - && !ty_contains_field(sub_ty, name) + if let ExprUseNode::FieldAccess(name) = use_cx.node + && adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()) + && !ty_contains_field(sub_ty, name.name) { self.state = Some(( - State::ExplicitDerefField { name }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + State::ExplicitDerefField { name: name.name }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); - } else if position.is_deref_stable() && sub_ty.is_ref() { + } else if sub_ty.is_ref() + // Linting method receivers would require verifying that name lookup + // would resolve the same way. This is complicated by trait methods. + && !use_cx.node.is_recv() + && let Some(ty) = use_cx.node.defined_ty(cx) + && TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()).is_deref_stable() + { self.state = Some(( State::ExplicitDeref { mutability: None }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); } }, - RefOp::Method(target_mut) + (_, RefOp::Method { mutbl, is_ufcs }) if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) - && position.lint_explicit_deref() => + // Allow explicit deref in method chains. e.g. `foo.deref().bar()` + && (is_ufcs || !in_postfix_position(cx, expr)) => { let ty_changed_count = usize::from(!deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr))); self.state = Some(( State::DerefMethod { ty_changed_count, - is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), - target_mut, + is_ufcs, + mutbl, }, StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty, }, )); }, - RefOp::AddrOf(mutability) => { + (Some(use_cx), RefOp::AddrOf(mutability)) => { + let defined_ty = use_cx.node.defined_ty(cx); + + // Check needless_borrow for generic arguments. + if !use_cx.is_ty_unified + && let Some(DefinedTy::Mir(ty)) = defined_ty + && let ty::Param(ty) = *ty.value.skip_binder().kind() + && let Some((hir_id, fn_id, i)) = match use_cx.node { + ExprUseNode::MethodArg(_, _, 0) => None, + ExprUseNode::MethodArg(hir_id, None, i) => { + typeck.type_dependent_def_id(hir_id).map(|id| (hir_id, id, i)) + }, + ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i) + if !path_has_args(p) => match typeck.qpath_res(p, hir_id) { + Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => { + Some((hir_id, id, i)) + }, + _ => None, + }, + _ => None, + } && let count = needless_borrow_generic_arg_count( + cx, + &mut self.possible_borrowers, + fn_id, + typeck.node_args(hir_id), + i, + ty, + expr, + &self.msrv, + ) && count != 0 + { + self.state = Some(( + State::DerefedBorrow(DerefedBorrow { + count: count - 1, + msg: "the borrowed expression implements the required traits", + stability: TyCoercionStability::None, + for_field_access: None, + }), + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + }, + )); + return; + } + // Find the number of times the borrow is auto-derefed. - let mut iter = adjustments.iter(); + let mut iter = use_cx.adjustments.iter(); let mut deref_count = 0usize; let next_adjust = loop { match iter.next() { @@ -352,6 +420,58 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; }; + let stability = defined_ty.map_or(TyCoercionStability::None, |ty| { + TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()) + }); + let can_auto_borrow = match use_cx.node { + ExprUseNode::Callee => true, + ExprUseNode::FieldAccess(_) => adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()), + ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => { + // Check for calls to trait methods where the trait is implemented + // on a reference. + // Two cases need to be handled: + // * `self` methods on `&T` will never have auto-borrow + // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take + // priority. + if let Some(fn_id) = typeck.type_dependent_def_id(hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let arg_ty + = cx.tcx.erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target)) + && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() + && let args = cx + .typeck_results() + .node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() + && let impl_ty = if cx.tcx.fn_sig(fn_id) + .instantiate_identity() + .skip_binder() + .inputs()[0].is_ref() + { + // Trait methods taking `&self` + sub_ty + } else { + // Trait methods taking `self` + arg_ty + } && impl_ty.is_ref() + && cx.tcx.infer_ctxt().build() + .type_implements_trait( + trait_id, + [impl_ty.into()].into_iter().chain(args.iter().copied()), + cx.param_env, + ) + .must_apply_modulo_regions() + { + false + } else { + true + } + }, + _ => false, + }; + + let deref_msg = + "this expression creates a reference which is immediately dereferenced by the compiler"; + let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; + // Determine the required number of references before any can be removed. In all cases the // reference made by the current expression will be removed. After that there are four cases to // handle. @@ -374,26 +494,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // }; // } // ``` - let deref_msg = - "this expression creates a reference which is immediately dereferenced by the compiler"; - let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; - let impl_msg = "the borrowed expression implements the required traits"; - - let (required_refs, msg, snip_expr) = if position.can_auto_borrow() { - (1, if deref_count == 1 { borrow_msg } else { deref_msg }, None) - } else if let Position::ImplArg(hir_id) = position { - (0, impl_msg, Some(hir_id)) - } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = - next_adjust.map(|a| &a.kind) + let (required_refs, msg) = if can_auto_borrow { + (1, if deref_count == 1 { borrow_msg } else { deref_msg }) + } else if let Some(&Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(_, mutability)), + .. + }) = next_adjust + && matches!(mutability, AutoBorrowMutability::Mut { .. }) + && !stability.is_reborrow_stable() { - if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable() - { - (3, deref_msg, None) - } else { - (2, deref_msg, None) - } + (3, deref_msg) } else { - (2, deref_msg, None) + (2, deref_msg) }; if deref_count >= required_refs { @@ -403,15 +515,19 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // can't be removed without breaking the code. See earlier comment. count: deref_count - required_refs, msg, - snip_expr, + stability, + for_field_access: match use_cx.node { + ExprUseNode::FieldAccess(name) => Some(name.name), + _ => None, + }, }), StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), }, )); - } else if position.is_deref_stable() + } else if stability.is_deref_stable() // Auto-deref doesn't combine with other adjustments && next_adjust.map_or(true, |a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) && iter.all(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) @@ -421,24 +537,24 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), }, )); } }, - RefOp::Method(..) => (), + (None, _) | (_, RefOp::Method { .. }) => (), } }, ( Some(( State::DerefMethod { - target_mut, + mutbl, ty_changed_count, .. }, data, )), - RefOp::Method(_), + RefOp::Method { is_ufcs, .. }, ) => { self.state = Some(( State::DerefMethod { @@ -447,8 +563,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } else { ty_changed_count + 1 }, - is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), - target_mut, + is_ufcs, + mutbl, }, data, )); @@ -463,33 +579,44 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { )); }, (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => { - let position = data.position; + let adjusted_ty = data.adjusted_ty; + let stability = state.stability; report(cx, expr, State::DerefedBorrow(state), data); - if position.is_deref_stable() { + if stability.is_deref_stable() { self.state = Some(( State::Borrow { mutability }, StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty, }, )); } }, (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => { - let position = data.position; + let adjusted_ty = data.adjusted_ty; + let stability = state.stability; + let for_field_access = state.for_field_access; report(cx, expr, State::DerefedBorrow(state), data); - if let Position::FieldAccess{name, ..} = position + if let Some(name) = for_field_access && !ty_contains_field(typeck.expr_ty(sub_expr), name) { self.state = Some(( State::ExplicitDerefField { name }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); - } else if position.is_deref_stable() { + } else if stability.is_deref_stable() { self.state = Some(( State::ExplicitDeref { mutability: None }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); } }, @@ -611,8 +738,8 @@ fn try_parse_ref_op<'tcx>( typeck: &'tcx TypeckResults<'_>, expr: &'tcx Expr<'_>, ) -> Option<(RefOp, &'tcx Expr<'tcx>)> { - let (def_id, arg) = match expr.kind { - ExprKind::MethodCall(_, arg, [], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg), + let (is_ufcs, def_id, arg) = match expr.kind { + ExprKind::MethodCall(_, arg, [], _) => (false, typeck.type_dependent_def_id(expr.hir_id)?, arg), ExprKind::Call( Expr { kind: ExprKind::Path(path), @@ -620,7 +747,7 @@ fn try_parse_ref_op<'tcx>( .. }, [arg], - ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg), + ) => (true, typeck.qpath_res(path, *hir_id).opt_def_id()?, arg), ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => { return Some((RefOp::Deref, sub_expr)); }, @@ -628,9 +755,21 @@ fn try_parse_ref_op<'tcx>( _ => return None, }; if tcx.is_diagnostic_item(sym::deref_method, def_id) { - Some((RefOp::Method(Mutability::Not), arg)) + Some(( + RefOp::Method { + mutbl: Mutability::Not, + is_ufcs, + }, + arg, + )) } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? { - Some((RefOp::Method(Mutability::Mut), arg)) + Some(( + RefOp::Method { + mutbl: Mutability::Mut, + is_ufcs, + }, + arg, + )) } else { None } @@ -649,420 +788,164 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { } } -/// The position of an expression relative to it's parent. -#[derive(Clone, Copy, Debug)] -enum Position { - MethodReceiver, - /// The method is defined on a reference type. e.g. `impl Foo for &T` - MethodReceiverRefImpl, - Callee, - ImplArg(HirId), - FieldAccess { - name: Symbol, - of_union: bool, - }, // union fields cannot be auto borrowed - Postfix, - Deref, - /// Any other location which will trigger auto-deref to a specific time. - /// Contains the precedence of the parent expression and whether the target type is sized. - DerefStable(i8, bool), - /// Any other location which will trigger auto-reborrowing. - /// Contains the precedence of the parent expression. - ReborrowStable(i8), - /// Contains the precedence of the parent expression. - Other(i8), -} -impl Position { - fn is_deref_stable(self) -> bool { - matches!(self, Self::DerefStable(..)) +fn path_has_args(p: &QPath<'_>) -> bool { + match *p { + QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(), + _ => false, } +} - fn is_reborrow_stable(self) -> bool { - matches!(self, Self::DerefStable(..) | Self::ReborrowStable(_)) +fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + if let Some(parent) = get_parent_expr(cx, e) + && parent.span.ctxt() == e.span.ctxt() + { + match parent.kind { + ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _) + if child.hir_id == e.hir_id => true, + ExprKind::Field(_, _) | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) => true, + _ => false, + } + } else { + false } +} - fn can_auto_borrow(self) -> bool { - matches!( - self, - Self::MethodReceiver | Self::FieldAccess { of_union: false, .. } | Self::Callee - ) +#[derive(Clone, Copy)] +enum TyCoercionStability { + Deref, + Reborrow, + None, +} +impl TyCoercionStability { + fn is_deref_stable(self) -> bool { + matches!(self, Self::Deref) } - fn lint_explicit_deref(self) -> bool { - matches!(self, Self::Other(_) | Self::DerefStable(..) | Self::ReborrowStable(_)) + fn is_reborrow_stable(self) -> bool { + matches!(self, Self::Deref | Self::Reborrow) } - fn precedence(self) -> i8 { - match self { - Self::MethodReceiver - | Self::MethodReceiverRefImpl - | Self::Callee - | Self::FieldAccess { .. } - | Self::Postfix => PREC_POSTFIX, - Self::ImplArg(_) | Self::Deref => PREC_PREFIX, - Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p, + fn for_defined_ty<'tcx>(cx: &LateContext<'tcx>, ty: DefinedTy<'tcx>, for_return: bool) -> Self { + match ty { + DefinedTy::Hir(ty) => Self::for_hir_ty(ty), + DefinedTy::Mir(ty) => Self::for_mir_ty( + cx.tcx, + ty.param_env, + cx.tcx.erase_late_bound_regions(ty.value), + for_return, + ), } } -} - -/// Walks up the parent expressions attempting to determine both how stable the auto-deref result -/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow -/// locations as those follow different rules. -#[expect(clippy::too_many_lines)] -fn walk_parents<'tcx>( - cx: &LateContext<'tcx>, - possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, - e: &'tcx Expr<'_>, - msrv: &Msrv, -) -> (Position, &'tcx [Adjustment<'tcx>]) { - let mut adjustments = [].as_slice(); - let mut precedence = 0i8; - let ctxt = e.span.ctxt(); - let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| { - // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. - if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) { - adjustments = cx.typeck_results().expr_adjustments(e); - } - match parent { - Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => { - Some(binding_ty_auto_deref_stability(cx, ty, precedence, List::empty())) - }, - Node::Item(&Item { - kind: ItemKind::Static(..) | ItemKind::Const(..), - owner_id, - span, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Const(..), - owner_id, - span, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Const(..), - owner_id, - span, - .. - }) if span.ctxt() == ctxt => { - let ty = cx.tcx.type_of(owner_id.def_id).instantiate_identity(); - Some(ty_auto_deref_stability(cx.tcx, cx.param_env, ty, precedence).position_for_result(cx)) - }, - Node::Item(&Item { - kind: ItemKind::Fn(..), - owner_id, - span, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Fn(..), - owner_id, - span, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Fn(..), - owner_id, - span, - .. - }) if span.ctxt() == ctxt => { - let output = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).instantiate_identity().output()); - Some(ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx)) - }, - - Node::ExprField(field) if field.span.ctxt() == ctxt => match get_parent_expr_for_hir(cx, field.hir_id) { - Some(Expr { - hir_id, - kind: ExprKind::Struct(path, ..), - .. - }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) - .and_then(|(adt, variant)| { - variant - .fields - .iter() - .find(|f| f.name == field.ident.name) - .map(|f| (adt, f)) - }) - .map(|(adt, field_def)| { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target type. - cx.tcx.param_env(adt.did()), - cx.tcx.type_of(field_def.did).instantiate_identity(), - precedence, - ) - .position_for_arg() - }), - _ => None, - }, + // Checks the stability of type coercions when assigned to a binding with the given explicit type. + // + // e.g. + // let x = Box::new(Box::new(0u32)); + // let y1: &Box<_> = x.deref(); + // let y2: &Box<_> = &x; + // + // Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when + // switching to auto-dereferencing. + fn for_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Self { + let TyKind::Ref(_, ty) = &ty.kind else { + return Self::None; + }; + let mut ty = ty; - Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { - ExprKind::Ret(_) => { - let owner_id = cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()); - Some( - if let Node::Expr( - closure_expr @ Expr { - kind: ExprKind::Closure(closure), - .. - }, - ) = cx.tcx.hir().get_by_def_id(owner_id) - { - closure_result_position(cx, closure, cx.typeck_results().expr_ty(closure_expr), precedence) - } else { - let output = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).instantiate_identity().output()); - ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx) - }, - ) - }, - ExprKind::Closure(closure) => Some(closure_result_position( - cx, - closure, - cx.typeck_results().expr_ty(parent), - precedence, - )), - ExprKind::Call(func, _) if func.hir_id == child_id => { - (child_id == e.hir_id).then_some(Position::Callee) + loop { + break match ty.ty.kind { + TyKind::Ref(_, ref ref_ty) => { + ty = ref_ty; + continue; }, - ExprKind::Call(func, args) => args - .iter() - .position(|arg| arg.hir_id == child_id) - .zip(expr_sig(cx, func)) - .and_then(|(i, sig)| { - sig.input_with_hir(i).map(|(hir_ty, ty)| { - match hir_ty { - // Type inference for closures can depend on how they're called. Only go by the explicit - // types here. - Some(hir_ty) => { - binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()) - }, - None => { - // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 - // `!call_is_qualified(func)` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id - && !call_is_qualified(func) - && let ty::Param(param_ty) = ty.skip_binder().kind() - { - needless_borrow_impl_arg_position( - cx, - possible_borrowers, - parent, - i, - *param_ty, - e, - precedence, - msrv, - ) - } else { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target function. - sig.predicates_id().map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)), - cx.tcx.erase_late_bound_regions(ty), - precedence - ).position_for_arg() - } - }, - } + TyKind::Path( + QPath::TypeRelative(_, path) + | QPath::Resolved( + _, + Path { + segments: [.., path], .. + }, + ), + ) => { + if let Some(args) = path.args + && args.args.iter().any(|arg| match arg { + hir::GenericArg::Infer(_) => true, + hir::GenericArg::Type(ty) => ty_contains_infer(ty), + _ => false, }) - }), - ExprKind::MethodCall(method, receiver, args, _) => { - let fn_id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); - if receiver.hir_id == child_id { - // Check for calls to trait methods where the trait is implemented on a reference. - // Two cases need to be handled: - // * `self` methods on `&T` will never have auto-borrow - // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take - // priority. - if e.hir_id != child_id { - return Some(Position::ReborrowStable(precedence)) - } else if let Some(trait_id) = cx.tcx.trait_of_item(fn_id) - && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e)) - && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() - && let subs = cx - .typeck_results() - .node_args_opt(parent.hir_id).map(|subs| &subs[1..]).unwrap_or_default() - && let impl_ty = if cx.tcx.fn_sig(fn_id) - .instantiate_identity() - .skip_binder() - .inputs()[0].is_ref() - { - // Trait methods taking `&self` - sub_ty - } else { - // Trait methods taking `self` - arg_ty - } && impl_ty.is_ref() - && let infcx = cx.tcx.infer_ctxt().build() - && infcx - .type_implements_trait( - trait_id, - [impl_ty.into()].into_iter().chain(subs.iter().copied()), - cx.param_env, - ) - .must_apply_modulo_regions() - { - return Some(Position::MethodReceiverRefImpl) - } - return Some(Position::MethodReceiver); + { + Self::Reborrow + } else { + Self::Deref } - args.iter().position(|arg| arg.hir_id == child_id).map(|i| { - let ty = cx.tcx.fn_sig(fn_id).instantiate_identity().input(i + 1); - // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 - // `method.args.is_none()` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id - && method.args.is_none() - && let ty::Param(param_ty) = ty.skip_binder().kind() - { - needless_borrow_impl_arg_position( - cx, - possible_borrowers, - parent, - i + 1, - *param_ty, - e, - precedence, - msrv, - ) - } else { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target function. - cx.tcx.param_env(fn_id), - cx.tcx.erase_late_bound_regions(ty), - precedence, - ) - .position_for_arg() - } - }) - }, - ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess { - name: name.name, - of_union: is_union(cx.typeck_results(), child), - }), - ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref), - ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) - | ExprKind::Index(child, _) - if child.hir_id == e.hir_id => - { - Some(Position::Postfix) - }, - _ if child_id == e.hir_id => { - precedence = parent.precedence().order(); - None }, - _ => None, - }, - _ => None, + TyKind::Slice(_) + | TyKind::Array(..) + | TyKind::Ptr(_) + | TyKind::BareFn(_) + | TyKind::Never + | TyKind::Tup(_) + | TyKind::Path(_) => Self::Deref, + TyKind::OpaqueDef(..) + | TyKind::Infer + | TyKind::Typeof(..) + | TyKind::TraitObject(..) + | TyKind::Err(_) => Self::Reborrow, + }; } - }) - .unwrap_or(Position::Other(precedence)); - (position, adjustments) -} - -fn is_union<'tcx>(typeck: &'tcx TypeckResults<'_>, path_expr: &'tcx Expr<'_>) -> bool { - typeck - .expr_ty_adjusted(path_expr) - .ty_adt_def() - .map_or(false, rustc_middle::ty::AdtDef::is_union) -} - -fn closure_result_position<'tcx>( - cx: &LateContext<'tcx>, - closure: &'tcx Closure<'_>, - ty: Ty<'tcx>, - precedence: i8, -) -> Position { - match closure.fn_decl.output { - FnRetTy::Return(hir_ty) => { - if let Some(sig) = ty_sig(cx, ty) - && let Some(output) = sig.output() - { - binding_ty_auto_deref_stability(cx, hir_ty, precedence, output.bound_vars()) - } else { - Position::Other(precedence) - } - }, - FnRetTy::DefaultReturn(_) => Position::Other(precedence), } -} -// Checks the stability of auto-deref when assigned to a binding with the given explicit type. -// -// e.g. -// let x = Box::new(Box::new(0u32)); -// let y1: &Box<_> = x.deref(); -// let y2: &Box<_> = &x; -// -// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when -// switching to auto-dereferencing. -fn binding_ty_auto_deref_stability<'tcx>( - cx: &LateContext<'tcx>, - ty: &'tcx hir::Ty<'_>, - precedence: i8, - binder_args: &'tcx List, -) -> Position { - let TyKind::Ref(_, ty) = &ty.kind else { - return Position::Other(precedence); - }; - let mut ty = ty; + fn for_mir_ty<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>, for_return: bool) -> Self { + let ty::Ref(_, mut ty, _) = *ty.kind() else { + return Self::None; + }; - loop { - break match ty.ty.kind { - TyKind::Ref(_, ref ref_ty) => { - ty = ref_ty; - continue; - }, - TyKind::Path( - QPath::TypeRelative(_, path) - | QPath::Resolved( - _, - Path { - segments: [.., path], .. - }, - ), - ) => { - if let Some(args) = path.args - && args.args.iter().any(|arg| match arg { - GenericArg::Infer(_) => true, - GenericArg::Type(ty) => ty_contains_infer(ty), - _ => false, - }) + ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + loop { + break match *ty.kind() { + ty::Ref(_, ref_ty, _) => { + ty = ref_ty; + continue; + }, + ty::Param(_) if for_return => Self::Deref, + ty::Alias(ty::Weak | ty::Inherent, _) => unreachable!("should have been normalized away above"), + ty::Alias(ty::Projection, _) if !for_return && ty.has_non_region_param() => Self::Reborrow, + ty::Infer(_) + | ty::Error(_) + | ty::Bound(..) + | ty::Alias(ty::Opaque, ..) + | ty::Placeholder(_) + | ty::Dynamic(..) + | ty::Param(_) => Self::Reborrow, + ty::Adt(_, args) + if ty.has_placeholders() + || ty.has_opaque_types() + || (!for_return && args.has_non_region_param()) => { - Position::ReborrowStable(precedence) - } else { - Position::DerefStable( - precedence, - cx.tcx - .erase_late_bound_regions(Binder::bind_with_vars( - cx.typeck_results().node_type(ty.ty.hir_id), - binder_args, - )) - .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), - ) - } - }, - TyKind::Slice(_) => Position::DerefStable(precedence, false), - TyKind::Array(..) | TyKind::Ptr(_) | TyKind::BareFn(_) => Position::DerefStable(precedence, true), - TyKind::Never - | TyKind::Tup(_) - | TyKind::Path(_) => Position::DerefStable( - precedence, - cx.tcx - .erase_late_bound_regions(Binder::bind_with_vars( - cx.typeck_results().node_type(ty.ty.hir_id), - binder_args, - )) - .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), - ), - TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err(_) => { - Position::ReborrowStable(precedence) - }, - }; + Self::Reborrow + }, + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Array(..) + | ty::Float(_) + | ty::RawPtr(..) + | ty::FnPtr(_) + | ty::Str + | ty::Slice(..) + | ty::Adt(..) + | ty::Foreign(_) + | ty::FnDef(..) + | ty::Generator(..) + | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) + | ty::Closure(..) + | ty::Never + | ty::Tuple(_) + | ty::Alias(ty::Projection, _) => Self::Deref, + }; + } } } @@ -1084,10 +967,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { } } - fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) { - if self.0 || matches!(arg, GenericArg::Infer(_)) { + fn visit_generic_arg(&mut self, arg: &hir::GenericArg<'_>) { + if self.0 || matches!(arg, hir::GenericArg::Infer(_)) { self.0 = true; - } else if let GenericArg::Type(ty) = arg { + } else if let hir::GenericArg::Type(ty) = arg { self.visit_ty(ty); } } @@ -1097,51 +980,29 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { v.0 } -fn call_is_qualified(expr: &Expr<'_>) -> bool { - if let ExprKind::Path(path) = &expr.kind { - match path { - QPath::Resolved(_, path) => path.segments.last().map_or(false, |segment| segment.args.is_some()), - QPath::TypeRelative(_, segment) => segment.args.is_some(), - QPath::LangItem(..) => false, - } - } else { - false - } -} - -// Checks whether: -// * child is an expression of the form `&e` in an argument position requiring an `impl Trait` -// * `e`'s type implements `Trait` and is copyable -// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`. -// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to -// be moved, but it cannot be. -#[expect(clippy::too_many_arguments, clippy::too_many_lines)] -fn needless_borrow_impl_arg_position<'tcx>( +/// Checks for the number of borrow expressions which can be removed from the given expression +/// where the expression is used as an argument to a function expecting a generic type. +/// +/// The following constraints will be checked: +/// * The borrowed expression meets all the generic type's constraints. +/// * The generic type appears only once in the functions signature. +/// * The borrowed value will not be moved if it is used later in the function. +#[expect(clippy::too_many_arguments)] +fn needless_borrow_generic_arg_count<'tcx>( cx: &LateContext<'tcx>, possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, - parent: &Expr<'tcx>, + fn_id: DefId, + callee_args: &'tcx List>, arg_index: usize, param_ty: ParamTy, mut expr: &Expr<'tcx>, - precedence: i8, msrv: &Msrv, -) -> Position { +) -> usize { let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait(); let sized_trait_def_id = cx.tcx.lang_items().sized_trait(); - let Some(callee_def_id) = fn_def_id(cx, parent) else { - return Position::Other(precedence); - }; - let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder(); - let args_with_expr_ty = cx - .typeck_results() - .node_args(if let ExprKind::Call(callee, _) = parent.kind { - callee.hir_id - } else { - parent.hir_id - }); - - let predicates = cx.tcx.param_env(callee_def_id).caller_bounds(); + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder(); + let predicates = cx.tcx.param_env(fn_id).caller_bounds(); let projection_predicates = predicates .iter() .filter_map(|predicate| { @@ -1176,7 +1037,7 @@ fn needless_borrow_impl_arg_position<'tcx>( || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id) }) { - return Position::Other(precedence); + return 0; } // See: @@ -1184,14 +1045,14 @@ fn needless_borrow_impl_arg_position<'tcx>( // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232 if projection_predicates .iter() - .any(|projection_predicate| is_mixed_projection_predicate(cx, callee_def_id, projection_predicate)) + .any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate)) { - return Position::Other(precedence); + return 0; } // `args_with_referent_ty` can be constructed outside of `check_referent` because the same // elements are modified each time `check_referent` is called. - let mut args_with_referent_ty = args_with_expr_ty.to_vec(); + let mut args_with_referent_ty = callee_args.to_vec(); let mut check_reference_and_referent = |reference, referent| { let referent_ty = cx.typeck_results().expr_ty(referent); @@ -1238,20 +1099,15 @@ fn needless_borrow_impl_arg_position<'tcx>( }) }; - let mut needless_borrow = false; + let mut count = 0; while let ExprKind::AddrOf(_, _, referent) = expr.kind { if !check_reference_and_referent(expr, referent) { break; } expr = referent; - needless_borrow = true; - } - - if needless_borrow { - Position::ImplArg(expr.hir_id) - } else { - Position::Other(precedence) + count += 1; } + count } fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { @@ -1392,93 +1248,6 @@ fn replace_types<'tcx>( true } -struct TyPosition<'tcx> { - position: Position, - ty: Option>, -} -impl From for TyPosition<'_> { - fn from(position: Position) -> Self { - Self { position, ty: None } - } -} -impl<'tcx> TyPosition<'tcx> { - fn new_deref_stable_for_result(precedence: i8, ty: Ty<'tcx>) -> Self { - Self { - position: Position::ReborrowStable(precedence), - ty: Some(ty), - } - } - fn position_for_result(self, cx: &LateContext<'tcx>) -> Position { - match (self.position, self.ty) { - (Position::ReborrowStable(precedence), Some(ty)) => { - Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env)) - }, - (position, _) => position, - } - } - fn position_for_arg(self) -> Position { - self.position - } -} - -// Checks whether a type is stable when switching to auto dereferencing, -fn ty_auto_deref_stability<'tcx>( - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - ty: Ty<'tcx>, - precedence: i8, -) -> TyPosition<'tcx> { - let ty::Ref(_, mut ty, _) = *ty.kind() else { - return Position::Other(precedence).into(); - }; - - ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); - - loop { - break match *ty.kind() { - ty::Ref(_, ref_ty, _) => { - ty = ref_ty; - continue; - }, - ty::Param(_) => TyPosition::new_deref_stable_for_result(precedence, ty), - ty::Alias(ty::Weak, _) => unreachable!("should have been normalized away above"), - ty::Alias(ty::Inherent, _) => unreachable!("inherent projection should have been normalized away above"), - ty::Alias(ty::Projection, _) if ty.has_non_region_param() => { - TyPosition::new_deref_stable_for_result(precedence, ty) - }, - ty::Infer(_) - | ty::Error(_) - | ty::Bound(..) - | ty::Alias(ty::Opaque, ..) - | ty::Placeholder(_) - | ty::Dynamic(..) => Position::ReborrowStable(precedence).into(), - ty::Adt(..) if ty.has_placeholders() || ty.has_opaque_types() => { - Position::ReborrowStable(precedence).into() - }, - ty::Adt(_, args) if args.has_non_region_param() => TyPosition::new_deref_stable_for_result(precedence, ty), - ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Array(..) - | ty::Float(_) - | ty::RawPtr(..) - | ty::FnPtr(_) => Position::DerefStable(precedence, true).into(), - ty::Str | ty::Slice(..) => Position::DerefStable(precedence, false).into(), - ty::Adt(..) - | ty::Foreign(_) - | ty::FnDef(..) - | ty::Generator(..) - | ty::GeneratorWitness(..) - | ty::GeneratorWitnessMIR(..) - | ty::Closure(..) - | ty::Never - | ty::Tuple(_) - | ty::Alias(ty::Projection, _) => Position::DerefStable(precedence, ty.is_sized(tcx, param_env)).into(), - }; - } -} - fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool { if let ty::Adt(adt, _) = *ty.kind() { adt.is_struct() && adt.all_fields().any(|f| f.name == name) @@ -1488,12 +1257,12 @@ fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool { } #[expect(clippy::needless_pass_by_value, clippy::too_many_lines)] -fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) { +fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData<'tcx>) { match state { State::DerefMethod { ty_changed_count, - is_final_ufcs, - target_mut, + is_ufcs, + mutbl, } => { let mut app = Applicability::MachineApplicable; let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); @@ -1508,12 +1277,12 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }; let addr_of_str = if ty_changed_count < ref_count { // Check if a reborrow from &mut T -> &T is required. - if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { + if mutbl == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { "&*" } else { "" } - } else if target_mut == Mutability::Mut { + } else if mutbl == Mutability::Mut { "&mut " } else { "&" @@ -1530,7 +1299,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data */ // Fix #10850, do not lint if it's `Foo::deref` instead of `foo.deref()`. - if is_final_ufcs { + if is_ufcs { return; } @@ -1538,7 +1307,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data cx, EXPLICIT_DEREF_METHODS, data.span, - match target_mut { + match mutbl { Mutability::Not => "explicit `deref` method call", Mutability::Mut => "explicit `deref_mut` method call", }, @@ -1549,13 +1318,19 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }, State::DerefedBorrow(state) => { let mut app = Applicability::MachineApplicable; - let snip_expr = state.snip_expr.map_or(expr, |hir_id| cx.tcx.hir().expect_expr(hir_id)); - let (snip, snip_is_macro) = snippet_with_context(cx, snip_expr.span, data.span.ctxt(), "..", &mut app); + let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| { - let calls_field = matches!(expr.kind, ExprKind::Field(..)) && matches!(data.position, Position::Callee); + let (precedence, calls_field) = match get_parent_node(cx.tcx, data.hir_id) { + Some(Node::Expr(e)) => match e.kind { + ExprKind::Call(callee, _) if callee.hir_id != data.hir_id => (0, false), + ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))), + _ => (e.precedence().order(), false), + }, + _ => (0, false), + }; let sugg = if !snip_is_macro + && (calls_field || expr.precedence().order() < precedence) && !has_enclosing_paren(&snip) - && (expr.precedence().order() < data.position.precedence() || calls_field) { format!("({snip})") } else { @@ -1572,7 +1347,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) - ) && matches!(data.position, Position::DerefStable(_, true)) + ) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() + && ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. return; @@ -1585,9 +1361,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data Mutability::Not => "&", Mutability::Mut => "&mut ", }; - (prefix, 0) + (prefix, PREC_PREFIX) } else { - ("", data.position.precedence()) + ("", 0) }; span_lint_hir_and_then( cx, @@ -1616,7 +1392,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) - ) && matches!(data.position, Position::DerefStable(_, true)) + ) && data.adjusted_ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. return; diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs index 91bc30ab57744..e3f2026cfe9e1 100644 --- a/src/tools/clippy/clippy_lints/src/derive.rs +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -1,4 +1,6 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{ + span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, +}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{is_lint_allowed, match_def_path, paths}; use if_chain::if_chain; @@ -6,15 +8,15 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, - Unsafety, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, + UnsafeSource, Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, ToPredicate, - TraitPredicate, Ty, TyCtxt, + self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, + ToPredicate, TraitPredicate, Ty, TyCtxt, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; @@ -205,13 +207,10 @@ declare_lint_pass!(Derive => [ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), - .. - }) = item.kind - { + if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), .. }) = item.kind { let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived); + let is_automatically_derived = + cx.tcx.has_attr(item.owner_id, sym::automatically_derived); check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); @@ -328,7 +327,12 @@ fn check_ord_partial_ord<'tcx>( } /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +fn check_copy_clone<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, +) { let clone_id = match cx.tcx.lang_items().clone_trait() { Some(id) if trait_ref.trait_def_id() == Some(id) => id, _ => return, @@ -427,7 +431,14 @@ struct UnsafeVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) { if self.has_unsafe { return; } @@ -463,7 +474,12 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { } /// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +fn check_partial_eq_without_eq<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, +) { if_chain! { if let ty::Adt(adt, args) = ty.kind(); if cx.tcx.visibility(adt.did()).is_public(); @@ -471,12 +487,12 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id); let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id); - if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []); + if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]); // If all of our fields implement `Eq`, we can implement `Eq` too if adt .all_fields() .map(|f| f.ty(cx.tcx, args)) - .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, [])); + .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])); then { span_lint_and_sugg( cx, diff --git a/src/tools/clippy/clippy_lints/src/error_impl_error.rs b/src/tools/clippy/clippy_lints/src/error_impl_error.rs new file mode 100644 index 0000000000000..379af9b2234c2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/error_impl_error.rs @@ -0,0 +1,87 @@ +use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; +use clippy_utils::path_res; +use clippy_utils::ty::implements_trait; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{Item, ItemKind}; +use rustc_hir_analysis::hir_ty_to_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Visibility; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for types named `Error` that implement `Error`. + /// + /// ### Why is this bad? + /// It can become confusing when a codebase has 20 types all named `Error`, requiring either + /// aliasing them in the `use` statement or qualifying them like `my_module::Error`. This + /// hinders comprehension, as it requires you to memorize every variation of importing `Error` + /// used across a codebase. + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Debug)] + /// pub enum Error { ... } + /// + /// impl std::fmt::Display for Error { ... } + /// + /// impl std::error::Error for Error { ... } + /// ``` + #[clippy::version = "1.72.0"] + pub ERROR_IMPL_ERROR, + restriction, + "exported types named `Error` that implement `Error`" +} +declare_lint_pass!(ErrorImplError => [ERROR_IMPL_ERROR]); + +impl<'tcx> LateLintPass<'tcx> for ErrorImplError { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) else { + return; + }; + + match item.kind { + ItemKind::TyAlias(ty, _) if implements_trait(cx, hir_ty_to_ty(cx.tcx, ty), error_def_id, &[]) + && item.ident.name == sym::Error + && is_visible_outside_module(cx, item.owner_id.def_id) => + { + span_lint( + cx, + ERROR_IMPL_ERROR, + item.ident.span, + "exported type alias named `Error` that implements `Error`", + ); + }, + ItemKind::Impl(imp) if let Some(trait_def_id) = imp.of_trait.and_then(|t| t.trait_def_id()) + && error_def_id == trait_def_id + && let Some(def_id) = path_res(cx, imp.self_ty).opt_def_id().and_then(DefId::as_local) + && let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id) + && let Some(ident) = cx.tcx.opt_item_ident(def_id.to_def_id()) + && ident.name == sym::Error + && is_visible_outside_module(cx, def_id) => + { + span_lint_hir_and_then( + cx, + ERROR_IMPL_ERROR, + hir_id, + ident.span, + "exported type named `Error` that implements `Error`", + |diag| { + diag.span_note(item.span, "`Error` was implemented here"); + } + ); + } + _ => {}, + } + } +} + +/// Do not lint private `Error`s, i.e., ones without any `pub` (minus `pub(self)` of course) and +/// which aren't reexported +fn is_visible_outside_module(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { + !matches!( + cx.tcx.visibility(def_id), + Visibility::Restricted(mod_def_id) if cx.tcx.parent_module_from_def_id(def_id).to_def_id() == mod_def_id + ) +} diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 22e10accd357f..8d6fb8438b658 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -1,19 +1,22 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::usage::local_used_after_expr; +use clippy_utils::ty::type_diagnostic_name; +use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id}; -use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; -use rustc_middle::ty::binding::BindingMode; -use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TypeVisitableExt}; +use rustc_middle::ty::{ + self, Binder, BoundConstness, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, + GenericArgsRef, ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, +}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; declare_clippy_lint! { /// ### What it does @@ -72,14 +75,18 @@ declare_clippy_lint! { declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); impl<'tcx> LateLintPass<'tcx> for EtaReduction { + #[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { + let body = if let ExprKind::Closure(c) = expr.kind + && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) + && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) + && !expr.span.from_expansion() + { + cx.tcx.hir().body(c.body) + } else { return; - } - let body = match expr.kind { - ExprKind::Closure(&Closure { body, .. }) => cx.tcx.hir().body(body), - _ => return, }; + if body.value.span.from_expansion() { if body.params.is_empty() { if let Some(VecArgs::Vec(&[])) = higher::VecArgs::hir(cx, body.value) { @@ -99,140 +106,209 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { return; } - let closure_ty = cx.typeck_results().expr_ty(expr); + let typeck = cx.typeck_results(); + let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { + closure_subs.as_closure() + } else { + return; + }; - if_chain!( - if !is_adjusted(cx, body.value); - if let ExprKind::Call(callee, args) = body.value.kind; - if let ExprKind::Path(_) = callee.kind; - if check_inputs(cx, body.params, None, args); - let callee_ty = cx.typeck_results().expr_ty_adjusted(callee); - let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id) - .map_or(callee_ty, |id| cx.tcx.type_of(id).instantiate_identity()); - if check_sig(cx, closure_ty, call_ty); - let args = cx.typeck_results().node_args(callee.hir_id); - // This fixes some false positives that I don't entirely understand - if args.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions(); - // A type param function ref like `T::f` is not 'static, however - // it is if cast like `T::f as fn()`. This seems like a rustc bug. - if !args.types().any(|t| matches!(t.kind(), ty::Param(_))); - let callee_ty_unadjusted = cx.typeck_results().expr_ty(callee).peel_refs(); - if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc); - if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc); - if let ty::Closure(_, args) = *closure_ty.kind(); - // Don't lint if this is an inclusive range expression. - // They desugar to a call to `RangeInclusiveNew` which would have odd suggestions. (#10684) - if !matches!(higher::Range::hir(body.value), Some(higher::Range { - start: Some(_), - end: Some(_), - limits: rustc_ast::RangeLimits::Closed - })); - then { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { - if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait() - && let args = cx.tcx.erase_late_bound_regions(args.as_closure().sig()).inputs() - && implements_trait( - cx, - callee_ty.peel_refs(), - fn_mut_id, - &args.iter().copied().map(Into::into).collect::>(), - ) - && path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr)) - { - // Mutable closure is used after current expr; we cannot consume it. - snippet = format!("&mut {snippet}"); - } + if is_adjusted(cx, body.value) { + return; + } - diag.span_suggestion( - expr.span, - "replace the closure with the function itself", - snippet, - Applicability::MachineApplicable, - ); - } - }); - } - ); + match body.value.kind { + ExprKind::Call(callee, args) + if matches!(callee.kind, ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..))) => + { + let callee_ty = typeck.expr_ty(callee).peel_refs(); + if matches!( + type_diagnostic_name(cx, callee_ty), + Some(sym::Arc | sym::Rc) + ) || !check_inputs(typeck, body.params, None, args) { + return; + } + let callee_ty_adjusted = typeck.expr_adjustments(callee).last().map_or( + callee_ty, + |a| a.target.peel_refs(), + ); - if_chain!( - if !is_adjusted(cx, body.value); - if let ExprKind::MethodCall(path, receiver, args, _) = body.value.kind; - if check_inputs(cx, body.params, Some(receiver), args); - let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap(); - let args = cx.typeck_results().node_args(body.value.hir_id); - let call_ty = cx.tcx.type_of(method_def_id).instantiate(cx.tcx, args); - if check_sig(cx, closure_ty, call_ty); - then { - span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| { - let name = get_ufcs_type_name(cx, method_def_id, args); - diag.span_suggestion( + let sig = match callee_ty_adjusted.kind() { + ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(), + ty::FnPtr(sig) => sig.skip_binder(), + ty::Closure(_, subs) => cx + .tcx + .signature_unclosure(subs.as_closure().sig(), Unsafety::Normal) + .skip_binder(), + _ => { + if typeck.type_dependent_def_id(body.value.hir_id).is_some() + && let subs = typeck.node_args(body.value.hir_id) + && let output = typeck.expr_ty(body.value) + && let ty::Tuple(tys) = *subs.type_at(1).kind() + { + cx.tcx.mk_fn_sig(tys, output, false, Unsafety::Normal, Abi::Rust) + } else { + return; + } + }, + }; + if check_sig(cx, closure, sig) + && let generic_args = typeck.node_args(callee.hir_id) + // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not + // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result + // in a type which is `'static`. + // For now ignore all callee types which reference a type parameter. + && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE, expr.span, - "replace the closure with the method itself", - format!("{name}::{}", path.ident.name), - Applicability::MachineApplicable, + "redundant closure", + |diag| { + if let Some(mut snippet) = snippet_opt(cx, callee.span) { + if let Ok((ClosureKind::FnMut, _)) + = cx.tcx.infer_ctxt().build().type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + BoundConstness::NotConst, + ImplPolarity::Positive, + ) && path_to_local(callee) + .map_or( + false, + |l| local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr), + ) + { + // Mutable closure is used after current expr; we cannot consume it. + snippet = format!("&mut {snippet}"); + } + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + snippet, + Applicability::MachineApplicable, + ); + } + } ); - }) - } - ); + } + }, + ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { + if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) + && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.span, + "redundant closure", + |diag| { + let args = typeck.node_args(body.value.hir_id); + let name = get_ufcs_type_name(cx, method_def_id, args); + diag.span_suggestion( + expr.span, + "replace the closure with the method itself", + format!("{}::{}", name, path.ident.name), + Applicability::MachineApplicable, + ); + }, + ); + } + }, + _ => (), + } } } fn check_inputs( - cx: &LateContext<'_>, + typeck: &TypeckResults<'_>, params: &[Param<'_>], - receiver: Option<&Expr<'_>>, - call_args: &[Expr<'_>], + self_arg: Option<&Expr<'_>>, + args: &[Expr<'_>], ) -> bool { - if receiver.map_or(params.len() != call_args.len(), |_| params.len() != call_args.len() + 1) { - return false; + params.len() == self_arg.map_or(0, |_| 1) + args.len() + && params.iter().zip(self_arg.into_iter().chain(args)).all(|(p, arg)| { + matches!( + p.pat.kind,PatKind::Binding(BindingAnnotation::NONE, id, _, None) + if path_to_local_id(arg, id) + ) + // Only allow adjustments which change regions (i.e. re-borrowing). + && typeck + .expr_adjustments(arg) + .last() + .map_or(true, |a| a.target == typeck.expr_ty(arg)) + }) +} + +fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig: FnSig<'_>) -> bool { + call_sig.unsafety == Unsafety::Normal + && !has_late_bound_to_non_late_bound_regions( + cx.tcx + .signature_unclosure(closure.sig(), Unsafety::Normal) + .skip_binder(), + call_sig, + ) +} + +/// This walks through both signatures and checks for any time a late-bound region is expected by an +/// `impl Fn` type, but the target signature does not have a late-bound region in the same position. +/// +/// This is needed because rustc is unable to late bind early-bound regions in a function signature. +fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { + fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { + matches!(from_region.kind(), RegionKind::ReLateBound(..)) + && !matches!(to_region.kind(), RegionKind::ReLateBound(..)) } - let binding_modes = cx.typeck_results().pat_binding_modes(); - let check_inputs = |param: &Param<'_>, arg| { - match param.pat.kind { - PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {}, - _ => return false, - } - // checks that parameters are not bound as `ref` or `ref mut` - if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) { - return false; - } - match *cx.typeck_results().expr_adjustments(arg) { - [] => true, - [ - Adjustment { - kind: Adjust::Deref(None), - .. + fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { + if from_subs.len() != to_subs.len() { + return true; + } + for (from_arg, to_arg) in to_subs.iter().zip(from_subs) { + match (from_arg.unpack(), to_arg.unpack()) { + (GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => { + if check_region(from_region, to_region) { + return true; + } }, - Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)), - .. + (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) => { + if check_ty(from_ty, to_ty) { + return true; + } }, - ] => { - // re-borrow with the same mutability is allowed - let ty = cx.typeck_results().expr_ty(arg); - matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into()) - }, - _ => false, + (GenericArgKind::Const(_), GenericArgKind::Const(_)) => (), + _ => return true, + } } - }; - std::iter::zip(params, receiver.into_iter().chain(call_args.iter())).all(|(param, arg)| check_inputs(param, arg)) -} - -fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool { - let call_sig = call_ty.fn_sig(cx.tcx); - if call_sig.unsafety() == Unsafety::Unsafe { - return false; + false } - if !closure_ty.has_late_bound_regions() { - return true; + + fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool { + match (from_ty.kind(), to_ty.kind()) { + (&ty::Adt(_, from_subs), &ty::Adt(_, to_subs)) => check_subs(from_subs, to_subs), + (&ty::Array(from_ty, _), &ty::Array(to_ty, _)) | (&ty::Slice(from_ty), &ty::Slice(to_ty)) => { + check_ty(from_ty, to_ty) + }, + (&ty::Ref(from_region, from_ty, _), &ty::Ref(to_region, to_ty, _)) => { + check_region(from_region, to_region) || check_ty(from_ty, to_ty) + }, + (&ty::Tuple(from_tys), &ty::Tuple(to_tys)) => { + from_tys.len() != to_tys.len() + || from_tys + .iter() + .zip(to_tys) + .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) + }, + _ => from_ty.has_late_bound_regions(), + } } - let ty::Closure(_, args) = closure_ty.kind() else { - return false; - }; - let closure_sig = cx.tcx.signature_unclosure(args.as_closure().sig(), Unsafety::Normal); - cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig) + + assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len()); + from_sig + .inputs_and_output + .iter() + .zip(to_sig.inputs_and_output) + .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) } fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String { @@ -241,7 +317,7 @@ fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: match assoc_item.container { ty::TraitContainer => cx.tcx.def_path_str(def_id), ty::ImplContainer => { - let ty = cx.tcx.type_of(def_id).skip_binder(); + let ty = cx.tcx.type_of(def_id).instantiate_identity(); match ty.kind() { ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()), ty::Array(..) diff --git a/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs b/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs new file mode 100644 index 0000000000000..419c77343441c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/four_forward_slashes.rs @@ -0,0 +1,99 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::Item; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for outer doc comments written with 4 forward slashes (`////`). + /// + /// ### Why is this bad? + /// This is (probably) a typo, and results in it not being a doc comment; just a regular + /// comment. + /// + /// ### Example + /// ```rust + /// //// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// /// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub FOUR_FORWARD_SLASHES, + suspicious, + "comments with 4 forward slashes (`////`) likely intended to be doc comments (`///`)" +} +declare_lint_pass!(FourForwardSlashes => [FOUR_FORWARD_SLASHES]); + +impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if item.span.from_expansion() { + return; + } + let sm = cx.sess().source_map(); + let mut span = cx + .tcx + .hir() + .attrs(item.hir_id()) + .iter() + .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span)); + let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else { + return; + }; + let mut bad_comments = vec![]; + for line in (0..end_line.saturating_sub(1)).rev() { + let Some(contents) = file.get_line(line).map(|c| c.trim().to_owned()) else { + return; + }; + // Keep searching until we find the next item + if !contents.is_empty() && !contents.starts_with("//") && !contents.starts_with("#[") { + break; + } + + if contents.starts_with("////") && !matches!(contents.chars().nth(4), Some('/' | '!')) { + let bounds = file.line_bounds(line); + let line_span = Span::with_root_ctxt(bounds.start, bounds.end); + span = line_span.to(span); + bad_comments.push((line_span, contents)); + } + } + + if !bad_comments.is_empty() { + span_lint_and_then( + cx, + FOUR_FORWARD_SLASHES, + span, + "this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't", + |diag| { + let msg = if bad_comments.len() == 1 { + "make this a doc comment by removing one `/`" + } else { + "turn these into doc comments by removing one `/`" + }; + + diag.multipart_suggestion( + msg, + bad_comments + .into_iter() + // It's a little unfortunate but the span includes the `\n` yet the contents + // do not, so we must add it back. If some codebase uses `\r\n` instead they + // will need normalization but it should be fine + .map(|(span, c)| (span, c.replacen("////", "///", 1) + "\n")) + .collect(), + Applicability::MachineApplicable, + ); + }, + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/incorrect_impls.rs b/src/tools/clippy/clippy_lints/src/incorrect_impls.rs index e6c42ebb8329b..3c59b839a39c6 100644 --- a/src/tools/clippy/clippy_lints/src/incorrect_impls.rs +++ b/src/tools/clippy/clippy_lints/src/incorrect_impls.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::paths::ORD_CMP; use clippy_utils::ty::implements_trait; -use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, path_res}; +use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path, path_res, std_or_core}; use rustc_errors::Applicability; -use rustc_hir::def::Res; +use rustc_hir::def_id::LocalDefId; use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::EarlyBinder; @@ -59,6 +60,10 @@ declare_clippy_lint! { /// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently /// introduce an error upon refactoring. /// + /// ### Known issues + /// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()` + /// wrapping it in `Some`. + /// /// ### Limitations /// Will not lint if `Self` and `Rhs` do not have the same type. /// @@ -190,6 +195,11 @@ impl LateLintPass<'_> for IncorrectImpls { &[], ) { + // If the `cmp` call likely needs to be fully qualified in the suggestion + // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't + // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. + let mut needs_fully_qualified = false; + if block.stmts.is_empty() && let Some(expr) = block.expr && let ExprKind::Call( @@ -201,9 +211,8 @@ impl LateLintPass<'_> for IncorrectImpls { [cmp_expr], ) = expr.kind && is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) - && let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind - && cmp_path.ident.name == sym::cmp - && let Res::Local(..) = path_res(cx, other_expr) + // Fix #11178, allow `Self::cmp(self, ..)` too + && self_cmp_call(cx, cmp_expr, impl_item.owner_id.def_id, &mut needs_fully_qualified) {} else { // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid // suggestion tons more complex. @@ -220,14 +229,29 @@ impl LateLintPass<'_> for IncorrectImpls { let [_, other] = body.params else { return; }; + let Some(std_or_core) = std_or_core(cx) else { + return; + }; - let suggs = if let Some(other_ident) = other.pat.simple_ident() { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - } else { - vec![ + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] + }, + (None, true) => vec![ + ( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ (block.span, "{ Some(self.cmp(other)) }".to_owned()), (other.pat.span, "other".to_owned()), - ] + ], }; diag.multipart_suggestion( @@ -241,3 +265,31 @@ impl LateLintPass<'_> for IncorrectImpls { } } } + +/// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`. +fn self_cmp_call<'tcx>( + cx: &LateContext<'tcx>, + cmp_expr: &'tcx Expr<'tcx>, + def_id: LocalDefId, + needs_fully_qualified: &mut bool, +) -> bool { + match cmp_expr.kind { + ExprKind::Call(path, [_self, _other]) => path_res(cx, path) + .opt_def_id() + .is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP)), + ExprKind::MethodCall(_, _, [_other], ..) => { + // We can set this to true here no matter what as if it's a `MethodCall` and goes to the + // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp` + *needs_fully_qualified = true; + + // It's a bit annoying but `typeck_results` only gives us the CURRENT body, which we + // have none, not of any `LocalDefId` we want, so we must call the query itself to avoid + // an immediate ICE + cx.tcx + .typeck(def_id) + .type_dependent_def_id(cmp_expr.hir_id) + .is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP)) + }, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs index d43e5cc9b2c3d..bc4ec33b7334e 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::{implements_trait, is_type_lang_item}; use clippy_utils::{return_ty, trait_ref_of_method}; -use if_chain::if_chain; -use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem}; +use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; +use rustc_target::spec::abi::Abi; declare_clippy_lint! { /// ### What it does @@ -95,24 +95,23 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { return; } - if_chain! { - // Check if item is a method, called to_string and has a parameter 'self' - if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; - if impl_item.ident.name == sym::to_string; - let decl = &signature.decl; - if decl.implicit_self.has_implicit_self(); - if decl.inputs.len() == 1; - if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })); - + // Check if item is a method called `to_string` and has a parameter 'self' + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind + // #11201 + && let header = signature.header + && header.unsafety == Unsafety::Normal + && header.abi == Abi::Rust + && impl_item.ident.name == sym::to_string + && let decl = signature.decl + && decl.implicit_self.has_implicit_self() + && decl.inputs.len() == 1 + && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) // Check if return type is String - if is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String); - + && is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String) // Filters instances of to_string which are required by a trait - if trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none(); - - then { - show_lint(cx, impl_item); - } + && trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none() + { + show_lint(cx, impl_item); } } } diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 50c433d31613e..deba232bdd23e 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -7,11 +7,10 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; -use rustc_hir::lang_items::LangItem; use rustc_hir::{ AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, - ImplicitSelfKind, Item, ItemKind, Mutability, Node, PathSegment, PrimTy, QPath, TraitItemRef, TyKind, - TypeBindingKind, + ImplicitSelfKind, Item, ItemKind, LangItem, Mutability, Node, PatKind, PathSegment, PrimTy, QPath, TraitItemRef, + TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; @@ -171,6 +170,31 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { return; } + if let ExprKind::Let(lt) = expr.kind + && has_is_empty(cx, lt.init) + && match lt.pat.kind { + PatKind::Slice([], None, []) => true, + PatKind::Lit(lit) if is_empty_string(lit) => true, + _ => false, + } + { + let mut applicability = Applicability::MachineApplicable; + + let lit1 = peel_ref_operators(cx, lt.init); + let lit_str = + Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_par(); + + span_lint_and_sugg( + cx, + COMPARISON_TO_EMPTY, + lt.span, + "comparison to empty slice using `if let`", + "using `is_empty` is clearer and more explicit", + format!("{lit_str}.is_empty()"), + applicability, + ); + } + if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind { // expr.span might contains parenthesis, see issue #10529 let actual_span = left.span.with_hi(right.span.hi()); diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 5e62cfd70ec0f..9d6096ccb2ae8 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -65,6 +65,7 @@ mod declared_lints; mod renamed_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` +mod absolute_paths; mod allow_attributes; mod almost_complete_range; mod approx_const; @@ -120,6 +121,7 @@ mod entry; mod enum_clike; mod enum_variants; mod equatable_if_let; +mod error_impl_error; mod escape; mod eta_reduction; mod excessive_bools; @@ -136,6 +138,7 @@ mod format_args; mod format_impl; mod format_push_string; mod formatting; +mod four_forward_slashes; mod from_over_into; mod from_raw_with_void_ptr; mod from_str_radix_10; @@ -272,6 +275,7 @@ mod redundant_clone; mod redundant_closure_call; mod redundant_else; mod redundant_field_names; +mod redundant_locals; mod redundant_pub_crate; mod redundant_slicing; mod redundant_static_lifetimes; @@ -909,7 +913,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv()))); store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); store.register_early_pass(move || Box::new(module_style::ModStyle)); - store.register_late_pass(|_| Box::new(unused_async::UnusedAsync)); + store.register_late_pass(|_| Box::::default()); let disallowed_types = conf.disallowed_types.clone(); store.register_late_pass(move |_| Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone()))); let import_renames = conf.enforced_import_renames.clone(); @@ -1078,6 +1082,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(visibility::Visibility)); store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() })); store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods)); + store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); + store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError)); + let absolute_paths_max_segments = conf.absolute_paths_max_segments; + let absolute_paths_allowed_crates = conf.absolute_paths_allowed_crates.clone(); + store.register_late_pass(move |_| { + Box::new(absolute_paths::AbsolutePaths { + absolute_paths_max_segments, + absolute_paths_allowed_crates: absolute_paths_allowed_crates.clone(), + }) + }); + store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs index 852f6736585bc..0004a150d51b5 100644 --- a/src/tools/clippy/clippy_lints/src/lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -15,6 +15,7 @@ use rustc_hir::{ PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter as middle_nested_filter; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -620,7 +621,7 @@ impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F> where F: NestedFilter<'tcx>, { - type Map = rustc_middle::hir::map::Map<'tcx>; + type Map = Map<'tcx>; type NestedFilter = F; // for lifetimes as parameters of generics diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs index a84a0a6eeb82a..7b8c88235a97d 100644 --- a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs @@ -109,7 +109,7 @@ fn is_ref_iterable<'tcx>( && let sig = cx.tcx.liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder()) && let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output && let param_env = cx.tcx.param_env(fn_id) - && implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, []) + && implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, &[]) && let Some(into_iter_ty) = make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty]) && let req_res_ty = normalize_with_regions(cx.tcx, param_env, req_res_ty) diff --git a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs index 744fd61bd135c..dfb800ccf714d 100644 --- a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs @@ -9,6 +9,7 @@ use rustc_errors::Applicability; use rustc_hir::{is_range_literal, BorrowKind, Expr, ExprKind, Pat}; use rustc_lint::LateContext; use rustc_span::edition::Edition; +use rustc_span::sym; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, @@ -51,7 +52,7 @@ pub(super) fn check<'tcx>( }, [], _, - ) if method.ident.name.as_str() == "iter_mut" => (arg, "&mut "), + ) if method.ident.name == sym::iter_mut => (arg, "&mut "), ExprKind::MethodCall( method, Expr { diff --git a/src/tools/clippy/clippy_lints/src/loops/utils.rs b/src/tools/clippy/clippy_lints/src/loops/utils.rs index 28ee24309cc46..6edca2d55f649 100644 --- a/src/tools/clippy/clippy_lints/src/loops/utils.rs +++ b/src/tools/clippy/clippy_lints/src/loops/utils.rs @@ -76,7 +76,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => { *state = IncrementVisitorVarState::DontWarn; }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { *state = IncrementVisitorVarState::DontWarn; }, _ => (), @@ -226,7 +226,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { InitializeVisitorState::DontWarn } }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { self.state = InitializeVisitorState::DontWarn; }, _ => (), diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs index d1061171e4d69..6d16d18875408 100644 --- a/src/tools/clippy/clippy_lints/src/matches/mod.rs +++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs @@ -16,6 +16,7 @@ mod match_wild_enum; mod match_wild_err_arm; mod needless_match; mod overlapping_arms; +mod redundant_guards; mod redundant_pattern_match; mod rest_pat_in_fully_bound_struct; mod significant_drop_in_scrutinee; @@ -936,6 +937,36 @@ declare_clippy_lint! { "reimplementation of `filter`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary guards in match expressions. + /// + /// ### Why is this bad? + /// It's more complex and much less readable. Making it part of the pattern can improve + /// exhaustiveness checking as well. + /// + /// ### Example + /// ```rust,ignore + /// match x { + /// Some(x) if matches!(x, Some(1)) => .., + /// Some(x) if x == Some(2) => .., + /// _ => todo!(), + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// match x { + /// Some(Some(1)) => .., + /// Some(Some(2)) => .., + /// _ => todo!(), + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub REDUNDANT_GUARDS, + complexity, + "checks for unnecessary guards in match expressions" +} + #[derive(Default)] pub struct Matches { msrv: Msrv, @@ -978,6 +1009,7 @@ impl_lint_pass!(Matches => [ TRY_ERR, MANUAL_MAP, MANUAL_FILTER, + REDUNDANT_GUARDS, ]); impl<'tcx> LateLintPass<'tcx> for Matches { @@ -1025,6 +1057,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { needless_match::check_match(cx, ex, arms, expr); match_on_vec_items::check(cx, ex); match_str_case_mismatch::check(cx, ex, arms); + redundant_guards::check(cx, arms); if !in_constant(cx, expr.hir_id) { manual_unwrap_or::check(cx, expr, ex, arms); diff --git a/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs b/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs new file mode 100644 index 0000000000000..6383326aa38df --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs @@ -0,0 +1,190 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::path_to_local; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::visitors::{for_each_expr, is_local_used}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_span::Span; +use std::ops::ControlFlow; + +use super::REDUNDANT_GUARDS; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { + for outer_arm in arms { + let Some(guard) = outer_arm.guard else { + continue; + }; + + // `Some(x) if matches!(x, y)` + if let Guard::If(if_expr) = guard + && let ExprKind::Match( + scrutinee, + [ + arm, + Arm { + pat: Pat { + kind: PatKind::Wild, + .. + }, + .. + }, + ], + MatchSource::Normal, + ) = if_expr.kind + { + emit_redundant_guards( + cx, + outer_arm, + if_expr.span, + scrutinee, + arm.pat.span, + arm.guard, + ); + } + // `Some(x) if let Some(2) = x` + else if let Guard::IfLet(let_expr) = guard { + emit_redundant_guards( + cx, + outer_arm, + let_expr.span, + let_expr.init, + let_expr.pat.span, + None, + ); + } + // `Some(x) if x == Some(2)` + else if let Guard::If(if_expr) = guard + && let ExprKind::Binary(bin_op, local, pat) = if_expr.kind + && matches!(bin_op.node, BinOpKind::Eq) + && expr_can_be_pat(cx, pat) + // Ensure they have the same type. If they don't, we'd need deref coercion which isn't + // possible (currently) in a pattern. In some cases, you can use something like + // `as_deref` or similar but in general, we shouldn't lint this as it'd create an + // extraordinary amount of FPs. + // + // This isn't necessary in the other two checks, as they must be a pattern already. + && cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat) + { + emit_redundant_guards( + cx, + outer_arm, + if_expr.span, + local, + pat.span, + None, + ); + } + } +} + +fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> { + if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) { + let mut span = None; + let mut multiple_bindings = false; + // `each_binding` gives the `HirId` of the `Pat` itself, not the binding + outer_arm.pat.walk(|pat| { + if let PatKind::Binding(_, hir_id, _, _) = pat.kind + && hir_id == local + && span.replace(pat.span).is_some() + { + multiple_bindings = true; + return false; + } + + true + }); + + // Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)` + if !multiple_bindings { + return span.map(|span| { + ( + span, + !matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)), + ) + }); + } + } + + None +} + +fn emit_redundant_guards<'tcx>( + cx: &LateContext<'tcx>, + outer_arm: &Arm<'tcx>, + guard_span: Span, + local: &Expr<'_>, + pat_span: Span, + inner_guard: Option>, +) { + let mut app = Applicability::MaybeIncorrect; + let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else { + return; + }; + + span_lint_and_then( + cx, + REDUNDANT_GUARDS, + guard_span.source_callsite(), + "redundant guard", + |diag| { + let binding_replacement = snippet_with_applicability(cx, pat_span, "", &mut app); + diag.multipart_suggestion_verbose( + "try", + vec![ + if can_use_shorthand { + (pat_binding, binding_replacement.into_owned()) + } else { + (pat_binding.shrink_to_hi(), format!(": {binding_replacement}")) + }, + ( + guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()), + inner_guard.map_or_else(String::new, |guard| { + let (prefix, span) = match guard { + Guard::If(e) => ("if", e.span), + Guard::IfLet(l) => ("if let", l.span), + }; + + format!( + " {prefix} {}", + snippet_with_applicability(cx, span, "", &mut app), + ) + }), + ), + ], + app, + ); + }, + ); +} + +/// Checks if the given `Expr` can also be represented as a `Pat`. +fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + for_each_expr(expr, |expr| { + if match expr.kind { + ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat, + ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => { + // Allow ctors + matches!(cx.qpath_res(&qpath, c.hir_id), Res::Def(DefKind::Ctor(..), ..)) + }, + ExprKind::Path(qpath) => { + matches!( + cx.qpath_res(&qpath, expr.hir_id), + Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..), + ) + }, + ExprKind::AddrOf(..) + | ExprKind::Array(..) + | ExprKind::Tup(..) + | ExprKind::Struct(..) + | ExprKind::Lit(..) => true, + _ => false, + } { + return ControlFlow::Continue(()); + } + + ControlFlow::Break(()) + }) + .is_none() +} diff --git a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs index ad38f1394b463..9a7c00823b670 100644 --- a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs @@ -3,17 +3,19 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; 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; +use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr}; use clippy_utils::{higher, is_expn_of, is_trait_method}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; -use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_span::{sym, Symbol}; +use std::fmt::Write; +use std::ops::ControlFlow; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { @@ -201,30 +203,58 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op if arms.len() == 2 { let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); - if let Some(good_method) = found_good_method(cx, arms, node_pair) { + if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) { let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span)); let result_expr = match &op.kind { ExprKind::AddrOf(_, _, borrowed) => borrowed, _ => op, }; + let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_")); + + if let Some(guard) = maybe_guard { + let Guard::If(guard) = *guard else { return }; // `...is_none() && let ...` is a syntax error + + // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! + // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, + // counter to the intuition that it should be `Guard::IfLet`, so we need another check + // to see that there aren't any let chains anywhere in the guard, as that would break + // if we suggest `t.is_none() && (let X = y && z)` for: + // `match t { None if let X = y && z => true, _ => false }` + let has_nested_let_chain = for_each_expr(guard, |expr| { + if matches!(expr.kind, ExprKind::Let(..)) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some(); + + if has_nested_let_chain { + return; + } + + let guard = Sugg::hir(cx, guard, ".."); + let _ = write!(sugg, " && {}", guard.maybe_par()); + } + span_lint_and_sugg( cx, REDUNDANT_PATTERN_MATCHING, span, &format!("redundant pattern matching, consider using `{good_method}`"), "try", - format!("{}.{good_method}", snippet(cx, result_expr.span, "_")), + sugg, Applicability::MachineApplicable, ); } } } -fn found_good_method<'a>( +fn found_good_method<'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], node: (&PatKind<'_>, &PatKind<'_>), -) -> Option<&'a str> { +) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { match node { ( PatKind::TupleStruct(ref path_left, patterns_left, _), @@ -310,7 +340,11 @@ fn get_ident(path: &QPath<'_>) -> Option { } } -fn get_good_method<'a>(cx: &LateContext<'_>, arms: &[Arm<'_>], path_left: &QPath<'_>) -> Option<&'a str> { +fn get_good_method<'tcx>( + cx: &LateContext<'_>, + arms: &'tcx [Arm<'tcx>], + path_left: &QPath<'_>, +) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { if let Some(name) = get_ident(path_left) { return match name.as_str() { "Ok" => { @@ -376,16 +410,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte } #[expect(clippy::too_many_arguments)] -fn find_good_method_for_match<'a>( +fn find_good_method_for_match<'a, 'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], path_left: &QPath<'_>, path_right: &QPath<'_>, expected_item_left: Item, expected_item_right: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<&'a str> { +) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { let first_pat = arms[0].pat; let second_pat = arms[1].pat; @@ -403,22 +437,22 @@ fn find_good_method_for_match<'a>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), _ => None, }, _ => None, } } -fn find_good_method_for_matches_macro<'a>( +fn find_good_method_for_matches_macro<'a, 'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], path_left: &QPath<'_>, expected_item_left: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<&'a str> { +) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { let first_pat = arms[0].pat; let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) { @@ -429,8 +463,8 @@ fn find_good_method_for_matches_macro<'a>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), _ => None, }, _ => None, diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs index 597a423b53779..c9eaa185acce8 100644 --- a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs @@ -1,7 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::macros::{is_panic, root_macro_call}; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; +use clippy_utils::{higher, is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; +use hir::{Body, HirId, MatchSource, Pat}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -10,7 +12,7 @@ use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{sym, Ident, Symbol}; use std::borrow::Cow; use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP}; @@ -48,6 +50,214 @@ fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_ar is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) } +#[derive(Debug, Copy, Clone)] +enum OffendingFilterExpr<'tcx> { + /// `.filter(|opt| opt.is_some())` + IsSome { + /// The receiver expression + receiver: &'tcx Expr<'tcx>, + /// If `Some`, then this contains the span of an expression that possibly contains side + /// effects: `.filter(|opt| side_effect(opt).is_some())` + /// ^^^^^^^^^^^^^^^^ + /// + /// We will use this later for warning the user that the suggested fix may change + /// the behavior. + side_effect_expr_span: Option, + }, + /// `.filter(|res| res.is_ok())` + IsOk { + /// The receiver expression + receiver: &'tcx Expr<'tcx>, + /// See `IsSome` + side_effect_expr_span: Option, + }, + /// `.filter(|enum| matches!(enum, Enum::A(_)))` + Matches { + /// The DefId of the variant being matched + variant_def_id: hir::def_id::DefId, + }, +} + +#[derive(Debug)] +enum CalledMethod { + OptionIsSome, + ResultIsOk, +} + +/// The result of checking a `map` call, returned by `OffendingFilterExpr::check_map_call` +#[derive(Debug)] +enum CheckResult<'tcx> { + Method { + map_arg: &'tcx Expr<'tcx>, + /// The method that was called inside of `filter` + method: CalledMethod, + /// See `OffendingFilterExpr::IsSome` + side_effect_expr_span: Option, + }, + PatternMatching { + /// The span of the variant being matched + /// if let Some(s) = enum + /// ^^^^^^^ + variant_span: Span, + /// if let Some(s) = enum + /// ^ + variant_ident: Ident, + }, +} + +impl<'tcx> OffendingFilterExpr<'tcx> { + pub fn check_map_call( + &mut self, + cx: &LateContext<'tcx>, + map_body: &'tcx Body<'tcx>, + map_param_id: HirId, + filter_param_id: HirId, + is_filter_param_ref: bool, + ) -> Option> { + match *self { + OffendingFilterExpr::IsSome { + receiver, + side_effect_expr_span, + } + | OffendingFilterExpr::IsOk { + receiver, + side_effect_expr_span, + } => { + // check if closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind + && matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or) + // .map(|y| f(y).copied().unwrap()) + // ~~~~ + && let map_arg_peeled = match map_arg.kind { + ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => { + original_arg + }, + _ => map_arg, + } + // .map(|y| y[.acceptable_method()].unwrap()) + && let simple_equal = (path_to_local_id(receiver, filter_param_id) + && path_to_local_id(map_arg_peeled, map_param_id)) + && let eq_fallback = (|a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + path_to_local_id(a_path, filter_param_id) + && path_to_local_id(b, map_param_id) + && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) + }) + && (simple_equal + || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(receiver, map_arg_peeled)) + { + Some(CheckResult::Method { + map_arg, + side_effect_expr_span, + method: match self { + OffendingFilterExpr::IsSome { .. } => CalledMethod::OptionIsSome, + OffendingFilterExpr::IsOk { .. } => CalledMethod::ResultIsOk, + OffendingFilterExpr::Matches { .. } => unreachable!("only IsSome and IsOk can get here"), + } + }) + } else { + None + } + }, + OffendingFilterExpr::Matches { variant_def_id } => { + let expr_uses_local = |pat: &Pat<'_>, expr: &Expr<'_>| { + if let PatKind::TupleStruct(QPath::Resolved(_, path), [subpat], _) = pat.kind + && let PatKind::Binding(_, local_id, ident, _) = subpat.kind + && path_to_local_id(expr.peel_blocks(), local_id) + && let Some(local_variant_def_id) = path.res.opt_def_id() + && local_variant_def_id == variant_def_id + { + Some((ident, pat.span)) + } else { + None + } + }; + + // look for: + // `if let Variant (v) = enum { v } else { unreachable!() }` + // ^^^^^^^ ^ ^^^^ ^^^^^^^^^^^^^^^^^^ + // variant_span variant_ident scrutinee else_ (blocks peeled later) + // OR + // `match enum { Variant (v) => v, _ => unreachable!() }` + // ^^^^ ^^^^^^^ ^ ^^^^^^^^^^^^^^ + // scrutinee variant_span variant_ident else_ + let (scrutinee, else_, variant_ident, variant_span) = + match higher::IfLetOrMatch::parse(cx, map_body.value) { + // For `if let` we want to check that the variant matching arm references the local created by its pattern + Some(higher::IfLetOrMatch::IfLet(sc, pat, then, Some(else_))) + if let Some((ident, span)) = expr_uses_local(pat, then) => + { + (sc, else_, ident, span) + }, + // For `match` we want to check that the "else" arm is the wildcard (`_`) pattern + // and that the variant matching arm references the local created by its pattern + Some(higher::IfLetOrMatch::Match(sc, [arm, wild_arm], MatchSource::Normal)) + if let PatKind::Wild = wild_arm.pat.kind + && let Some((ident, span)) = expr_uses_local(arm.pat, arm.body.peel_blocks()) => + { + (sc, wild_arm.body, ident, span) + }, + _ => return None, + }; + + if path_to_local_id(scrutinee, map_param_id) + // else branch should be a `panic!` or `unreachable!` macro call + && let Some(mac) = root_macro_call(else_.peel_blocks().span) + && (is_panic(cx, mac.def_id) || cx.tcx.opt_item_name(mac.def_id) == Some(sym::unreachable)) + { + Some(CheckResult::PatternMatching { variant_span, variant_ident }) + } else { + None + } + }, + } + } + + fn hir(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, filter_param_id: HirId) -> Option { + if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind + && let Some(recv_ty) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() + { + // we still want to lint if the expression possibly contains side effects, + // *but* it can't be machine-applicable then, because that can change the behavior of the program: + // .filter(|x| effect(x).is_some()).map(|x| effect(x).unwrap()) + // vs. + // .filter_map(|x| effect(x)) + // + // the latter only calls `effect` once + let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); + + if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) + && path.ident.name == sym!(is_some) + { + Some(Self::IsSome { receiver, side_effect_expr_span }) + } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) + && path.ident.name == sym!(is_ok) + { + Some(Self::IsOk { receiver, side_effect_expr_span }) + } else { + None + } + } else if let Some(macro_call) = root_macro_call(expr.span) + && cx.tcx.get_diagnostic_name(macro_call.def_id) == Some(sym::matches_macro) + // we know for a fact that the wildcard pattern is the second arm + && let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind + && path_to_local_id(scrutinee, filter_param_id) + && let PatKind::TupleStruct(QPath::Resolved(_, path), ..) = arm.pat.kind + && let Some(variant_def_id) = path.res.opt_def_id() + { + Some(OffendingFilterExpr::Matches { variant_def_id }) + } else { + None + } + } +} + /// is `filter(|x| x.is_some()).map(|x| x.unwrap())` fn is_filter_some_map_unwrap( cx: &LateContext<'_>, @@ -102,55 +312,18 @@ pub(super) fn check( } else { (filter_param.pat, false) }; - // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; - if let ExprKind::MethodCall(path, filter_arg, [], _) = filter_body.value.kind; - if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def(); - if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) { - Some(false) - } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) { - Some(true) - } else { - None - }; - if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + if let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id); - // ...map(|x| ...unwrap()) if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind; let map_body = cx.tcx.hir().body(map_body_id); if let [map_param] = map_body.params; if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; - // closure ends with expect() or unwrap() - if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); - - // .filter(..).map(|y| f(y).copied().unwrap()) - // ~~~~ - let map_arg_peeled = match map_arg.kind { - ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => { - original_arg - }, - _ => map_arg, - }; - // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap()) - let simple_equal = path_to_local_id(filter_arg, filter_param_id) - && path_to_local_id(map_arg_peeled, map_param_id); + if let Some(check_result) = + offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref); - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - // in `filter(|x| ..)`, replace `*x` with `x` - let a_path = if_chain! { - if !is_filter_param_ref; - if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; - then { expr_path } else { a } - }; - // let the filter closure arg and the map closure arg be equal - path_to_local_id(a_path, filter_param_id) - && path_to_local_id(b, map_param_id) - && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) - }; - - if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled); then { let span = filter_span.with_hi(expr.span.hi()); let (filter_name, lint) = if is_find { @@ -159,22 +332,53 @@ pub(super) fn check( ("filter", MANUAL_FILTER_MAP) }; let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`"); - let (to_opt, deref) = if is_result { - (".ok()", String::new()) - } else { - let derefs = cx.typeck_results() - .expr_adjustments(map_arg) - .iter() - .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) - .count(); - ("", "*".repeat(derefs)) + let (sugg, note_and_span, applicability) = match check_result { + CheckResult::Method { map_arg, method, side_effect_expr_span } => { + let (to_opt, deref) = match method { + CalledMethod::ResultIsOk => (".ok()", String::new()), + CalledMethod::OptionIsSome => { + let derefs = cx.typeck_results() + .expr_adjustments(map_arg) + .iter() + .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count(); + + ("", "*".repeat(derefs)) + } + }; + + let sugg = format!( + "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})", + snippet(cx, map_arg.span, ".."), + ); + let (note_and_span, applicability) = if let Some(span) = side_effect_expr_span { + let note = "the suggestion might change the behavior of the program when merging `filter` and `map`, \ + because this expression potentially contains side effects and will only execute once"; + + (Some((note, span)), Applicability::MaybeIncorrect) + } else { + (None, Applicability::MachineApplicable) + }; + + (sugg, note_and_span, applicability) + } + CheckResult::PatternMatching { variant_span, variant_ident } => { + let pat = snippet(cx, variant_span, ""); + + (format!("{filter_name}_map(|{map_param_ident}| match {map_param_ident} {{ \ + {pat} => Some({variant_ident}), \ + _ => None \ + }})"), None, Applicability::MachineApplicable) + } }; - let sugg = format!( - "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})", - snippet(cx, map_arg.span, ".."), - ); - span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + span_lint_and_then(cx, lint, span, &msg, |diag| { + diag.span_suggestion(span, "try", sugg, applicability); + + if let Some((note, span)) = note_and_span { + diag.span_note(span, note); + } + }); } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs new file mode 100644 index 0000000000000..4aee22a4afc3b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs @@ -0,0 +1,45 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::paths::BOOL_THEN; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::is_copy; +use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_span::{sym, Span}; + +use super::FILTER_MAP_BOOL_THEN; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { + if !in_external_macro(cx.sess(), expr.span) + && is_trait_method(cx, expr, sym::Iterator) + && let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir().body(closure.body) + && let value = peel_blocks(body.value) + // Indexing should be fine as `filter_map` always has 1 input, we unfortunately need both + // `inputs` and `params` here as we need both the type and the span + && let param_ty = closure.fn_decl.inputs[0] + && let param = body.params[0] + && is_copy(cx, cx.typeck_results().node_type(param_ty.hir_id).peel_refs()) + && let ExprKind::MethodCall(_, recv, [then_arg], _) = value.kind + && let ExprKind::Closure(then_closure) = then_arg.kind + && let then_body = peel_blocks(cx.tcx.hir().body(then_closure.body).value) + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) + && match_def_path(cx, def_id, &BOOL_THEN) + && !is_from_proc_macro(cx, expr) + && let Some(param_snippet) = snippet_opt(cx, param.span) + && let Some(filter) = snippet_opt(cx, recv.span) + && let Some(map) = snippet_opt(cx, then_body.span) + { + span_lint_and_sugg( + cx, + FILTER_MAP_BOOL_THEN, + call_span, + "usage of `bool::then` in `filter_map`", + "use `filter` then `map` instead", + format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"), + Applicability::MachineApplicable, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/format_collect.rs b/src/tools/clippy/clippy_lints/src/methods/format_collect.rs new file mode 100644 index 0000000000000..1f8863f852186 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/format_collect.rs @@ -0,0 +1,33 @@ +use super::FORMAT_COLLECT; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node}; +use clippy_utils::ty::is_type_lang_item; +use rustc_hir::{Expr, ExprKind, LangItem}; +use rustc_lint::LateContext; +use rustc_span::Span; + +/// Same as `peel_blocks` but only actually considers blocks that are not from an expansion. +/// This is needed because always calling `peel_blocks` would otherwise remove parts of the +/// `format!` macro, which would cause `root_macro_call_first_node` to return `None`. +fn peel_non_expn_blocks<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match expr.kind { + ExprKind::Block(block, _) if !expr.span.from_expansion() => peel_non_expn_blocks(block.expr?), + _ => Some(expr), + } +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { + if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String) + && let ExprKind::Closure(closure) = map_arg.kind + && let body = cx.tcx.hir().body(closure.body) + && let Some(value) = peel_non_expn_blocks(body.value) + && let Some(mac) = root_macro_call_first_node(cx, value) + && is_format_macro(cx, mac.def_id) + { + span_lint_and_then(cx, FORMAT_COLLECT, expr.span, "use of `format!` to build up a string from an iterator", |diag| { + diag.span_help(map_span, "call `fold` instead") + .span_help(value.span.source_callsite(), "... and use the `write!` macro here") + .note("this can be written more efficiently by appending to a `String` directly"); + }); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_skip_zero.rs b/src/tools/clippy/clippy_lints/src/methods/iter_skip_zero.rs new file mode 100644 index 0000000000000..6b696b42a6931 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/iter_skip_zero.rs @@ -0,0 +1,34 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{is_from_proc_macro, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_SKIP_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { + if !expr.span.from_expansion() + && is_trait_method(cx, expr, sym::Iterator) + && let Some(arg) = constant(cx, cx.typeck_results(), arg_expr).and_then(|constant| { + if let Constant::Int(arg) = constant { + Some(arg) + } else { + None + } + }) + && arg == 0 + && !is_from_proc_macro(cx, expr) + { + span_lint_and_then(cx, ITER_SKIP_ZERO, arg_expr.span, "usage of `.skip(0)`", |diag| { + diag.span_suggestion( + arg_expr.span, + "if you meant to skip the first element, use", + "1", + Applicability::MaybeIncorrect, + ) + .note("this call to `skip` does nothing and is useless; remove it"); + }); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 123ab520e4859..dd694ce7393e2 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -21,11 +21,13 @@ mod expect_used; mod extend_with_drain; mod filetype_is_file; mod filter_map; +mod filter_map_bool_then; mod filter_map_identity; mod filter_map_next; mod filter_next; mod flat_map_identity; mod flat_map_option; +mod format_collect; mod from_iter_instead_of_collect; mod get_first; mod get_last_with_len; @@ -44,6 +46,7 @@ mod iter_nth_zero; mod iter_on_single_or_empty_collections; mod iter_overeager_cloned; mod iter_skip_next; +mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod manual_next_back; @@ -73,6 +76,7 @@ mod or_then_unwrap; mod path_buf_push_overwrite; mod range_zip_with_len; mod read_line_without_trim; +mod readonly_write_lock; mod repeat_once; mod search_is_some; mod seek_from_current; @@ -85,6 +89,7 @@ mod skip_while_next; mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; +mod string_lit_chars_any; mod suspicious_command_arg_space; mod suspicious_map; mod suspicious_splitn; @@ -100,7 +105,6 @@ mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; mod utils; @@ -114,7 +118,7 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty}; +use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; @@ -473,29 +477,40 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `_.unwrap_or_else(Default::default)` on `Option` and - /// `Result` values. + /// Checks for usages of the following functions with an argument that constructs a default value + /// (e.g., `Default::default` or `String::new`): + /// - `unwrap_or` + /// - `unwrap_or_else` + /// - `or_insert` + /// - `or_insert_with` /// /// ### Why is this bad? - /// Readability, these can be written as `_.unwrap_or_default`, which is - /// simpler and more concise. + /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` + /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. + /// + /// ### Known problems + /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a + /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. /// /// ### Examples /// ```rust /// # let x = Some(1); - /// x.unwrap_or_else(Default::default); - /// x.unwrap_or_else(u32::default); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or(Default::default()); + /// map.entry(42).or_insert_with(String::new); /// ``` /// /// Use instead: /// ```rust /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); /// x.unwrap_or_default(); + /// map.entry(42).or_default(); /// ``` #[clippy::version = "1.56.0"] - pub UNWRAP_OR_ELSE_DEFAULT, + pub UNWRAP_OR_DEFAULT, style, - "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`" + "using `.unwrap_or`, etc. with an argument that constructs a default value" } declare_clippy_lint! { @@ -3378,6 +3393,152 @@ declare_clippy_lint! { "calling `Stdin::read_line`, then trying to parse it without first trimming" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.chars().any(|i| i == c)`. + /// + /// ### Why is this bad? + /// It's significantly slower than using a pattern instead, like + /// `matches!(c, '\\' | '.' | '+')`. + /// + /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice + /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available + /// for situations where that additional performance is absolutely necessary. + /// + /// ### Example + /// ```rust + /// # let c = 'c'; + /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + /// ``` + /// Use instead: + /// ```rust + /// # let c = 'c'; + /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + /// ``` + #[clippy::version = "1.72.0"] + pub STRING_LIT_CHARS_ANY, + restriction, + "checks for `.chars().any(|i| i == c)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(|_| format!(..)).collect::()`. + /// + /// ### Why is this bad? + /// This allocates a new string for every element in the iterator. + /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, + /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, + /// or concatenating with `+` in case the iterator yields `&str`/`String`. + /// + /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, + /// so it can be safely ignored or unwrapped. + /// + /// ### Example + /// ```rust + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().map(|b| format!("{b:02X}")).collect() + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::fmt::Write; + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().fold(String::new(), |mut output, b| { + /// let _ = write!(output, "{b:02X}"); + /// output + /// }) + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub FORMAT_COLLECT, + perf, + "`format!`ing every element in a collection, then collecting the strings into a new `String`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(0)` on iterators. + /// + /// ### Why is this bad? + /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does + /// nothing. If not, the call should be removed. + /// + /// ### Example + /// ```rust + /// let v = vec![1, 2, 3]; + /// let x = v.iter().skip(0).collect::>(); + /// let y = v.iter().collect::>(); + /// assert_eq!(x, y); + /// ``` + #[clippy::version = "1.72.0"] + pub ITER_SKIP_ZERO, + correctness, + "disallows `.skip(0)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `bool::then` in `Iterator::filter_map`. + /// + /// ### Why is this bad? + /// This can be written with `filter` then `map` instead, which would reduce nesting and + /// separates the filtering from the transformation phase. This comes with no cost to + /// performance and is just cleaner. + /// + /// ### Limitations + /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. + /// This can create differing behavior, so better safe than sorry. + /// + /// ### Example + /// ```rust + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); + /// ``` + /// Use instead: + /// ```rust + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); + /// ``` + #[clippy::version = "1.72.0"] + pub FILTER_MAP_BOOL_THEN, + style, + "checks for usage of `bool::then` in `Iterator::filter_map`" +} + +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `RwLock::write` where the lock is only used for reading. + /// + /// ### Why is this bad? + /// The write portion of `RwLock` is exclusive, meaning that no other thread + /// can access the lock while this writer is active. + /// + /// ### Example + /// ```rust + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.write().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.read().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub READONLY_WRITE_LOCK, + nursery, + "acquiring a write lock when a read lock would work" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3408,7 +3569,7 @@ impl_lint_pass!(Methods => [ SHOULD_IMPLEMENT_TRAIT, WRONG_SELF_CONVENTION, OK_EXPECT, - UNWRAP_OR_ELSE_DEFAULT, + UNWRAP_OR_DEFAULT, MAP_UNWRAP_OR, RESULT_MAP_OR_INTO_OPTION, OPTION_MAP_OR_NONE, @@ -3512,6 +3673,11 @@ impl_lint_pass!(Methods => [ UNNECESSARY_LITERAL_UNWRAP, DRAIN_COLLECT, MANUAL_TRY_FOLD, + FORMAT_COLLECT, + STRING_LIT_CHARS_ANY, + ITER_SKIP_ZERO, + FILTER_MAP_BOOL_THEN, + READONLY_WRITE_LOCK ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -3666,8 +3832,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { then { let first_arg_span = first_arg_ty.span; let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()) - .self_ty(); + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); wrong_self_convention::check( cx, item.ident.name.as_str(), @@ -3684,8 +3849,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if item.ident.name == sym::new; if let TraitItemKind::Fn(_, _) = item.kind; let ret_ty = return_ty(cx, item.owner_id); - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()) - .self_ty(); + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); if !ret_ty.contains(self_ty); then { @@ -3733,8 +3897,9 @@ impl Methods { Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => { iter_cloned_collect::check(cx, name, expr, recv2); }, - Some(("map", m_recv, [m_arg], _, _)) => { + Some(("map", m_recv, [m_arg], m_ident_span, _)) => { map_collect_result_unit::check(cx, expr, m_recv, m_arg); + format_collect::check(cx, expr, m_arg, m_ident_span); }, Some(("take", take_self_arg, [take_arg], _, _)) => { if self.msrv.meets(msrvs::STR_REPEAT) { @@ -3790,6 +3955,7 @@ impl Methods { }, ("filter_map", [arg]) => { unnecessary_filter_map::check(cx, expr, arg, name); + filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); }, ("find_map", [arg]) => { @@ -3833,7 +3999,16 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("last", []) | ("skip", [_]) => { + ("skip", [arg]) => { + iter_skip_zero::check(cx, expr, arg); + + if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + } + } + } + ("last", []) => { if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); @@ -3885,6 +4060,13 @@ impl Methods { } } }, + ("any", [arg]) if let ExprKind::Closure(arg) = arg.kind + && let body = cx.tcx.hir().body(arg.body) + && let [param] = body.params + && let Some(("chars", recv, _, _, _)) = method_call(recv) => + { + string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); + } ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), @@ -4027,7 +4209,6 @@ impl Methods { Some(("map", recv, [map_arg], _, _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {}, _ => { - unwrap_or_else_default::check(cx, expr, recv, u_arg); unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); }, } @@ -4040,6 +4221,9 @@ impl Methods { range_zip_with_len::check(cx, expr, iter_recv, arg); } }, + ("write", []) => { + readonly_write_lock::check(cx, expr, recv); + } _ => {}, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs index 9165c1248f0aa..8b2f57160af48 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs @@ -1,16 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; 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 clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; +use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::source_map::Span; -use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::symbol::{self, sym, Symbol}; +use {rustc_ast as ast, rustc_hir as hir}; -use super::OR_FUN_CALL; +use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; /// Checks for the `OR_FUN_CALL` lint. #[allow(clippy::too_many_lines)] @@ -24,53 +25,72 @@ pub(super) fn check<'tcx>( ) { /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, /// `or_insert(T::new())` or `or_insert(T::default())`. + /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, + /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. #[allow(clippy::too_many_arguments)] fn check_unwrap_or_default( cx: &LateContext<'_>, name: &str, + receiver: &hir::Expr<'_>, fun: &hir::Expr<'_>, - arg: &hir::Expr<'_>, - or_has_args: bool, + call_expr: Option<&hir::Expr<'_>>, span: Span, method_span: Span, ) -> bool { - let is_default_default = || is_trait_item(cx, fun, sym::Default); + if !expr_type_is_certain(cx, receiver) { + return false; + } - let implements_default = |arg, default_trait_id| { - let arg_ty = cx.typeck_results().expr_ty(arg); - implements_trait(cx, arg_ty, default_trait_id, &[]) + let is_new = |fun: &hir::Expr<'_>| { + if let hir::ExprKind::Path(ref qpath) = fun.kind { + let path = last_path_segment(qpath).ident.name; + matches!(path, sym::new) + } else { + false + } }; - if_chain! { - if !or_has_args; - if let Some(sugg) = match name { - "unwrap_or" => Some("unwrap_or_default"), - "or_insert" => Some("or_default"), - _ => None, - }; - if let hir::ExprKind::Path(ref qpath) = fun.kind; - if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); - let path = last_path_segment(qpath).ident.name; - // needs to target Default::default in particular or be *::new and have a Default impl - // available - if (matches!(path, kw::Default) && is_default_default()) - || (matches!(path, sym::new) && implements_default(arg, default_trait_id)); - - then { - span_lint_and_sugg( - cx, - OR_FUN_CALL, - method_span.with_hi(span.hi()), - &format!("use of `{name}` followed by a call to `{path}`"), - "try", - format!("{sugg}()"), - Applicability::MachineApplicable, - ); - - true + let output_type_implements_default = |fun| { + let fun_ty = cx.typeck_results().expr_ty(fun); + if let ty::FnDef(def_id, args) = fun_ty.kind() { + let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); + cx.tcx + .get_diagnostic_item(sym::Default) + .map_or(false, |default_trait_id| { + implements_trait(cx, output_ty, default_trait_id, &[]) + }) } else { false } + }; + + let sugg = match (name, call_expr.is_some()) { + ("unwrap_or", true) | ("unwrap_or_else", false) => "unwrap_or_default", + ("or_insert", true) | ("or_insert_with", false) => "or_default", + _ => return false, + }; + + // needs to target Default::default in particular or be *::new and have a Default impl + // available + if (is_new(fun) && output_type_implements_default(fun)) + || match call_expr { + Some(call_expr) => is_default_equivalent(cx, call_expr), + None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun), + } + { + span_lint_and_sugg( + cx, + UNWRAP_OR_DEFAULT, + method_span.with_hi(span.hi()), + &format!("use of `{name}` to construct default value"), + "try", + format!("{sugg}()"), + Applicability::MachineApplicable, + ); + + true + } else { + false } } @@ -168,11 +188,16 @@ pub(super) fn check<'tcx>( match inner_arg.kind { hir::ExprKind::Call(fun, or_args) => { let or_has_args = !or_args.is_empty(); - if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) { + if or_has_args + || !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span) + { let fun_span = if or_has_args { None } else { Some(fun.span) }; check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span); } }, + hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => { + check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span); + }, hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None); }, @@ -189,3 +214,22 @@ pub(super) fn check<'tcx>( } } } + +fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { + let body = cx.tcx.hir().body(body); + + if body.params.is_empty() + && let hir::Expr{ kind, .. } = &body.value + && let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind + && ident.name == sym::to_string + && let hir::Expr{ kind, .. } = self_arg + && let hir::ExprKind::Lit(lit) = kind + && let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node + { + return true; + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs index 8add0656101e5..81f9e2a77fce7 100644 --- a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs +++ b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs @@ -35,8 +35,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< && segment.ident.name == sym!(parse) && let parse_result_ty = cx.typeck_results().expr_ty(parent) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) - && let ty::Adt(_, substs) = parse_result_ty.kind() - && let Some(ok_ty) = substs[0].as_type() + && let ty::Adt(_, args) = parse_result_ty.kind() + && let Some(ok_ty) = args[0].as_type() && parse_fails_on_trailing_newline(ok_ty) { let local_snippet = snippet(cx, expr.span, ""); diff --git a/src/tools/clippy/clippy_lints/src/methods/readonly_write_lock.rs b/src/tools/clippy/clippy_lints/src/methods/readonly_write_lock.rs new file mode 100644 index 0000000000000..e3ec921da0ce4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/readonly_write_lock.rs @@ -0,0 +1,52 @@ +use super::READONLY_WRITE_LOCK; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::mir::{enclosing_mir, visit_local_usage}; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; +use rustc_middle::mir::{Location, START_BLOCK}; +use rustc_span::sym; + +fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::MethodCall(path, receiver, ..) = expr.kind + && path.ident.name == sym::unwrap + { + is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result) + } else { + false + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, receiver: &Expr<'_>) { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::RwLock) + && let Node::Expr(unwrap_call_expr) = cx.tcx.hir().get_parent(expr.hir_id) + && is_unwrap_call(cx, unwrap_call_expr) + && let parent = cx.tcx.hir().get_parent(unwrap_call_expr.hir_id) + && let Node::Local(local) = parent + && let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id) + && let Some((local, _)) = mir.local_decls.iter_enumerated().find(|(_, decl)| { + local.span.contains(decl.source_info.span) + }) + && let Some(usages) = visit_local_usage(&[local], mir, Location { + block: START_BLOCK, + statement_index: 0, + }) + && let [usage] = usages.as_slice() + { + let writer_never_mutated = usage.local_consume_or_mutate_locs.is_empty(); + + if writer_never_mutated { + span_lint_and_sugg( + cx, + READONLY_WRITE_LOCK, + expr.span, + "this write lock is used only for reading", + "consider using a read lock instead", + format!("{}.read()", snippet(cx, receiver.span, "")), + Applicability::MaybeIncorrect // write lock might be intentional for enforcing exclusiveness + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/string_lit_chars_any.rs b/src/tools/clippy/clippy_lints/src/methods/string_lit_chars_any.rs new file mode 100644 index 0000000000000..70da6ad58bdcf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/string_lit_chars_any.rs @@ -0,0 +1,58 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{Msrv, MATCHES_MACRO}; +use clippy_utils::source::snippet_opt; +use clippy_utils::{is_from_proc_macro, is_trait_method, path_to_local}; +use itertools::Itertools; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::STRING_LIT_CHARS_ANY; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &Expr<'_>, + param: &'tcx Param<'tcx>, + body: &Expr<'_>, + msrv: &Msrv, +) { + if msrv.meets(MATCHES_MACRO) + && is_trait_method(cx, expr, sym::Iterator) + && let PatKind::Binding(_, arg, _, _) = param.pat.kind + && let ExprKind::Lit(lit_kind) = recv.kind + && let LitKind::Str(val, _) = lit_kind.node + && let ExprKind::Binary(kind, lhs, rhs) = body.kind + && let BinOpKind::Eq = kind.node + && let Some(lhs_path) = path_to_local(lhs) + && let Some(rhs_path) = path_to_local(rhs) + && let scrutinee = match (lhs_path == arg, rhs_path == arg) { + (true, false) => rhs, + (false, true) => lhs, + _ => return, + } + && !is_from_proc_macro(cx, expr) + && let Some(scrutinee_snip) = snippet_opt(cx, scrutinee.span) + { + // Normalize the char using `map` so `join` doesn't use `Display`, if we don't then + // something like `r"\"` will become `'\'`, which is of course invalid + let pat_snip = val.as_str().chars().map(|c| format!("{c:?}")).join(" | "); + + span_lint_and_then( + cx, + STRING_LIT_CHARS_ANY, + expr.span, + "usage of `.chars().any(...)` to check if a char matches any from a string literal", + |diag| { + diag.span_suggestion_verbose( + expr.span, + "use `matches!(...)` instead", + format!("matches!({scrutinee_snip}, {pat_snip})"), + Applicability::MachineApplicable, + ); + } + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/type_id_on_box.rs b/src/tools/clippy/clippy_lints/src/methods/type_id_on_box.rs index 35137c97101e6..3404bdfe79beb 100644 --- a/src/tools/clippy/clippy_lints/src/methods/type_id_on_box.rs +++ b/src/tools/clippy/clippy_lints/src/methods/type_id_on_box.rs @@ -24,9 +24,9 @@ pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span) if let Some(Adjustment { target: recv_ty, .. }) = recv_adjusts.last() && let ty::Ref(_, ty, _) = recv_ty.kind() - && let ty::Adt(adt, substs) = ty.kind() + && let ty::Adt(adt, args) = ty.kind() && adt.is_box() - && is_dyn_any(cx, substs.type_at(0)) + && is_dyn_any(cx, args.type_at(0)) { span_lint_and_then( cx, diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs index cc64a2e794875..fabf3fa0c0c8b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -77,6 +77,16 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc } (true, true) }, + hir::ExprKind::MethodCall(segment, recv, [arg], _) => { + if segment.ident.name == sym!(then_some) + && cx.typeck_results().expr_ty(recv).is_bool() + && path_to_local_id(arg, arg_id) + { + (false, true) + } else { + (true, true) + } + }, hir::ExprKind::Block(block, _) => block .expr .as_ref() diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 111bcaaecec37..937aac8d25ef0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -3,6 +3,8 @@ use clippy_utils::{is_res_lang_ctor, last_path_segment, path_res, MaybePath}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_middle::ty::print::with_forced_trimmed_paths; use super::UNNECESSARY_LITERAL_UNWRAP; @@ -22,6 +24,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -84,6 +87,34 @@ pub(super) fn check( } Some(suggs) }, + ("None", "unwrap_or_default", _) => { + let ty = cx.typeck_results().expr_ty(expr); + let default_ty_string = if let ty::Adt(def, ..) = ty.kind() { + with_forced_trimmed_paths!(format!("{}", cx.tcx.def_path_str(def.did()))) + } else { + "Default".to_string() + }; + Some(vec![(expr.span, format!("{default_ty_string}::default()"))]) + }, + ("None", "unwrap_or", _) => Some(vec![ + (expr.span.with_hi(args[0].span.lo()), String::new()), + (expr.span.with_lo(args[0].span.hi()), String::new()), + ]), + ("None", "unwrap_or_else", _) => match args[0].kind { + hir::ExprKind::Closure(hir::Closure { + fn_decl: + hir::FnDecl { + output: hir::FnRetTy::DefaultReturn(span) | hir::FnRetTy::Return(hir::Ty { span, .. }), + .. + }, + .. + }) => Some(vec![ + (expr.span.with_hi(span.hi()), String::new()), + (expr.span.with_lo(args[0].span.hi()), String::new()), + ]), + _ => None, + }, + _ if call_args.is_empty() => None, (_, _, Some(_)) => None, ("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![ ( diff --git a/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs b/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs deleted file mode 100644 index 474a33b67e1c6..0000000000000 --- a/src/tools/clippy/clippy_lints/src/methods/unwrap_or_else_default.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Lint for `some_result_or_option.unwrap_or_else(Default::default)` - -use super::UNWRAP_OR_ELSE_DEFAULT; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_default_equivalent_call; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::{sym, symbol}; - -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - u_arg: &'tcx hir::Expr<'_>, -) { - // something.unwrap_or_else(Default::default) - // ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr - let recv_ty = cx.typeck_results().expr_ty(recv); - let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option); - let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result); - - if_chain! { - if is_option || is_result; - if closure_body_returns_empty_to_string(cx, u_arg) || is_default_equivalent_call(cx, u_arg); - then { - let mut applicability = Applicability::MachineApplicable; - - span_lint_and_sugg( - cx, - UNWRAP_OR_ELSE_DEFAULT, - expr.span, - "use of `.unwrap_or_else(..)` to construct default value", - "try", - format!( - "{}.unwrap_or_default()", - snippet_with_applicability(cx, recv.span, "..", &mut applicability) - ), - applicability, - ); - } - } -} - -fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { - if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { - let body = cx.tcx.hir().body(body); - - if body.params.is_empty() - && let hir::Expr{ kind, .. } = &body.value - && let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind - && ident == &symbol::Ident::from_str("to_string") - && let hir::Expr{ kind, .. } = self_arg - && let hir::ExprKind::Lit(lit) = kind - && let LitKind::Str(symbol::kw::Empty, _) = lit.node - { - return true; - } - } - - false -} diff --git a/src/tools/clippy/clippy_lints/src/min_ident_chars.rs b/src/tools/clippy/clippy_lints/src/min_ident_chars.rs index 2a60f2faca0ae..c79a1a7b9d428 100644 --- a/src/tools/clippy/clippy_lints/src/min_ident_chars.rs +++ b/src/tools/clippy/clippy_lints/src/min_ident_chars.rs @@ -129,6 +129,14 @@ impl Visitor<'_> for IdentVisitor<'_, '_> { return; } + // `struct Array([T; N])` + // ^ + if let Node::GenericParam(generic_param) = node + && let GenericParamKind::Const { .. } = generic_param.kind + { + return; + } + if is_from_proc_macro(cx, &ident) { return; } diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs index d323a16c2a736..c634de960d137 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -1,16 +1,18 @@ use super::needless_pass_by_value::requires_exact_signature; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; -use clippy_utils::{is_from_proc_macro, is_self}; -use if_chain::if_chain; +use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::Applicability; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind}; +use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor}; +use rustc_hir::{Body, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath}; use rustc_hir_typeck::expr_use_visitor as euv; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::associated_body; +use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, UpvarId, UpvarPath}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; @@ -46,20 +48,24 @@ declare_clippy_lint! { "using a `&mut` argument when it's not mutated" } -#[derive(Copy, Clone)] -pub struct NeedlessPassByRefMut { +#[derive(Clone)] +pub struct NeedlessPassByRefMut<'tcx> { avoid_breaking_exported_api: bool, + used_fn_def_ids: FxHashSet, + fn_def_ids_to_maybe_unused_mut: FxIndexMap>>, } -impl NeedlessPassByRefMut { +impl NeedlessPassByRefMut<'_> { pub fn new(avoid_breaking_exported_api: bool) -> Self { Self { avoid_breaking_exported_api, + used_fn_def_ids: FxHashSet::default(), + fn_def_ids_to_maybe_unused_mut: FxIndexMap::default(), } } } -impl_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]); +impl_lint_pass!(NeedlessPassByRefMut<'_> => [NEEDLESS_PASS_BY_REF_MUT]); fn should_skip<'tcx>( cx: &LateContext<'tcx>, @@ -87,12 +93,12 @@ fn should_skip<'tcx>( is_from_proc_macro(cx, &input) } -impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { +impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { fn check_fn( &mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, + decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'_>, span: Span, fn_def_id: LocalDefId, @@ -102,17 +108,17 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { } let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id); - - match kind { + let is_async = match kind { FnKind::ItemFn(.., header) => { let attrs = cx.tcx.hir().attrs(hir_id); if header.abi != Abi::Rust || requires_exact_signature(attrs) { return; } + header.is_async() }, - FnKind::Method(..) => (), + FnKind::Method(.., sig) => sig.header.is_async(), FnKind::Closure => return, - } + }; // Exclude non-inherent impls if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) { @@ -128,63 +134,89 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig); // If there are no `&mut` argument, no need to go any further. - if !decl + let mut it = decl .inputs .iter() .zip(fn_sig.inputs()) .zip(body.params) - .any(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) - { + .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) + .peekable(); + if it.peek().is_none() { return; } - // Collect variables mutably used and spans which will need dereferencings from the // function body. let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = { let mut ctx = MutablyUsedVariablesCtxt::default(); let infcx = cx.tcx.infer_ctxt().build(); euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body); + if is_async { + let closures = ctx.async_closures.clone(); + let hir = cx.tcx.hir(); + for closure in closures { + ctx.prev_bind = None; + ctx.prev_move_to_closure.clear(); + if let Some(body) = hir + .find_by_def_id(closure) + .and_then(associated_body) + .map(|(_, body_id)| hir.body(body_id)) + { + euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results()) + .consume_body(body); + } + } + } ctx }; - - let mut it = decl - .inputs - .iter() - .zip(fn_sig.inputs()) - .zip(body.params) - .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) - .peekable(); - if it.peek().is_none() { - return; - } - let show_semver_warning = self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id); for ((&input, &_), arg) in it { // Only take `&mut` arguments. - if_chain! { - if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind; - if !mutably_used_vars.contains(&canonical_id); - if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind; - then { - // If the argument is never used mutably, we emit the warning. - let sp = input.span; - span_lint_and_then( + if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind + && !mutably_used_vars.contains(&canonical_id) + { + self.fn_def_ids_to_maybe_unused_mut.entry(fn_def_id).or_default().push(input); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + cx.tcx.hir().visit_all_item_likes_in_crate(&mut FnNeedsMutVisitor { + cx, + used_fn_def_ids: &mut self.used_fn_def_ids, + }); + + for (fn_def_id, unused) in self + .fn_def_ids_to_maybe_unused_mut + .iter() + .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) + { + let show_semver_warning = + self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id); + + let mut is_cfged = None; + for input in unused { + // If the argument is never used mutably, we emit the warning. + let sp = input.span; + if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind { + let is_cfged = is_cfged.get_or_insert_with(|| inherits_cfg(cx.tcx, *fn_def_id)); + span_lint_hir_and_then( cx, NEEDLESS_PASS_BY_REF_MUT, + cx.tcx.hir().local_def_id_to_hir_id(*fn_def_id), sp, "this argument is a mutable reference, but not used mutably", |diag| { diag.span_suggestion( sp, "consider changing to".to_string(), - format!( - "&{}", - snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"), - ), + format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), Applicability::Unspecified, ); if show_semver_warning { diag.warn("changing this function will impact semver compatibility"); } + if *is_cfged { + diag.note("this is cfg-gated and may require further changes"); + } }, ); } @@ -197,7 +229,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { struct MutablyUsedVariablesCtxt { mutably_used_vars: HirIdSet, prev_bind: Option, + prev_move_to_closure: HirIdSet, aliases: HirIdMap, + async_closures: FxHashSet, } impl MutablyUsedVariablesCtxt { @@ -213,16 +247,27 @@ impl MutablyUsedVariablesCtxt { impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt { fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) { if let euv::Place { - base: euv::PlaceBase::Local(vid), + base: + euv::PlaceBase::Local(vid) + | euv::PlaceBase::Upvar(UpvarId { + var_path: UpvarPath { hir_id: vid }, + .. + }), base_ty, .. } = &cmt.place { if let Some(bind_id) = self.prev_bind.take() { - self.aliases.insert(bind_id, *vid); - } else if matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) { + if bind_id != *vid { + self.aliases.insert(bind_id, *vid); + } + } else if !self.prev_move_to_closure.contains(vid) + && matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) + { self.add_mutably_used_var(*vid); } + self.prev_bind = None; + self.prev_move_to_closure.remove(vid); } } @@ -265,9 +310,73 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt { self.prev_bind = None; } - fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} + fn fake_read( + &mut self, + cmt: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, + cause: FakeReadCause, + _id: HirId, + ) { + if let euv::Place { + base: + euv::PlaceBase::Upvar(UpvarId { + var_path: UpvarPath { hir_id: vid }, + .. + }), + .. + } = &cmt.place + { + if let FakeReadCause::ForLet(Some(inner)) = cause { + // Seems like we are inside an async function. We need to store the closure `DefId` + // to go through it afterwards. + self.async_closures.insert(inner); + self.aliases.insert(cmt.hir_id, *vid); + self.prev_move_to_closure.insert(*vid); + } + } + } fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) { self.prev_bind = Some(id); } } + +/// A final pass to check for paths referencing this function that require the argument to be +/// `&mut`, basically if the function is ever used as a `fn`-like argument. +struct FnNeedsMutVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + used_fn_def_ids: &'a mut FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for FnNeedsMutVisitor<'_, 'tcx> { + type NestedFilter = OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_qpath(&mut self, qpath: &'tcx QPath<'tcx>, hir_id: HirId, _: Span) { + walk_qpath(self, qpath, hir_id); + + let Self { cx, used_fn_def_ids } = self; + + // #11182; do not lint if mutability is required elsewhere + if let Node::Expr(expr) = cx.tcx.hir().get(hir_id) + && let Some(parent) = get_parent_node(cx.tcx, expr.hir_id) + && let ty::FnDef(def_id, _) = cx.tcx.typeck(cx.tcx.hir().enclosing_body_owner(hir_id)).expr_ty(expr).kind() + && let Some(def_id) = def_id.as_local() + { + if let Node::Expr(e) = parent + && let ExprKind::Call(call, _) = e.kind + && call.hir_id == expr.hir_id + { + return; + } + + // We don't need to check each argument individually as you cannot coerce a function + // taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's + // passed as a `fn`-like argument (or is unified) and should ignore every "unused" + // argument entirely + used_fn_def_ids.insert(def_id); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 5e26601537f31..5ee26966fa716 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::ptr::get_spans; use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::ty::{ - implements_trait, implements_trait_with_env, is_copy, is_type_diagnostic_item, is_type_lang_item, + implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item, }; use clippy_utils::{get_trait_def_id, is_self, paths}; use if_chain::if_chain; @@ -182,7 +182,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { if !ty.is_mutable_ptr(); if !is_copy(cx, ty); if ty.is_sized(cx.tcx, cx.param_env); - if !allowed_traits.iter().any(|&t| implements_trait_with_env(cx.tcx, cx.param_env, ty, t, [None])); + if !allowed_traits.iter().any(|&t| implements_trait_with_env_from_iter( + cx.tcx, + cx.param_env, + ty, + t, + [Option::>::None], + )); if !implements_borrow_trait; if !all_borrowable_trait; diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs index 8bb2fa9258562..a70692d8ff864 100644 --- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -154,7 +154,7 @@ fn is_value_unfrozen_raw<'tcx>( ty::Adt(def, ..) if def.is_union() => false, ty::Array(ty, _) => val.unwrap_branch().iter().any(|field| inner(cx, *field, ty)), ty::Adt(def, _) if def.is_union() => false, - ty::Adt(def, substs) if def.is_enum() => { + ty::Adt(def, args) if def.is_enum() => { let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap(); let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); fields @@ -164,19 +164,14 @@ fn is_value_unfrozen_raw<'tcx>( def.variants()[variant_index] .fields .iter() - .map(|field| field.ty(cx.tcx, substs)), + .map(|field| field.ty(cx.tcx, args)), ) .any(|(field, ty)| inner(cx, field, ty)) }, - ty::Adt(def, substs) => val + ty::Adt(def, args) => val .unwrap_branch() .iter() - .zip( - def.non_enum_variant() - .fields - .iter() - .map(|field| field.ty(cx.tcx, substs)), - ) + .zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args))) .any(|(field, ty)| inner(cx, *field, ty)), ty::Tuple(tys) => val .unwrap_branch() diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs index 35dd8fabe6e60..f9108145cdb6d 100644 --- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,7 +1,7 @@ use super::ARITHMETIC_SIDE_EFFECTS; use clippy_utils::consts::{constant, constant_simple, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; +use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; @@ -138,8 +138,10 @@ impl ArithmeticSideEffects { ) { return; }; - let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); - let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); + let (mut actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); + let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); + actual_lhs = expr_or_init(cx, actual_lhs); + actual_rhs = expr_or_init(cx, actual_rhs); let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty(actual_rhs).peel_refs(); if self.has_allowed_binary(lhs_ty, rhs_ty) { diff --git a/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs index 377bddeaa5fea..9c7f7e1cd7f8e 100644 --- a/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_direct_expn_of; -use if_chain::if_chain; use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; +use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does @@ -36,21 +35,27 @@ declare_lint_pass!(OptionEnvUnwrap => [OPTION_ENV_UNWRAP]); impl EarlyLintPass for OptionEnvUnwrap { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if_chain! { - if let ExprKind::MethodCall(box MethodCall { seg, receiver, .. }) = &expr.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap); - if let ExprKind::Call(caller, _) = &receiver.kind; - if is_direct_expn_of(caller.span, "option_env").is_some(); - then { - span_lint_and_help( - cx, - OPTION_ENV_UNWRAP, - expr.span, - "this will panic at run-time if the environment variable doesn't exist at compile-time", - None, - "consider using the `env!` macro instead" - ); - } + fn lint(cx: &EarlyContext<'_>, span: Span) { + span_lint_and_help( + cx, + OPTION_ENV_UNWRAP, + span, + "this will panic at run-time if the environment variable doesn't exist at compile-time", + None, + "consider using the `env!` macro instead", + ); } + + if let ExprKind::MethodCall(box MethodCall { seg, receiver, .. }) = &expr.kind && + matches!(seg.ident.name, sym::expect | sym::unwrap) { + if let ExprKind::Call(caller, _) = &receiver.kind && + // If it exists, it will be ::core::option::Option::Some("").unwrap() (A method call in the HIR) + is_direct_expn_of(caller.span, "option_env").is_some() { + lint(cx, expr.span); + } else if let ExprKind::Path(_, caller) = &receiver.kind && // If it doesn't exist, it will be ::core::option::Option::None::<&'static str>.unwrap() (A path in the HIR) + is_direct_expn_of(caller.span, "option_env").is_some() { + lint(cx, expr.span); + } + } } } diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index 264c38df11f51..42299d8d42f54 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -26,6 +26,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::sym; use rustc_span::symbol::Symbol; +use rustc_target::spec::abi::Abi; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; @@ -163,6 +164,12 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { } check_mut_from_ref(cx, sig, None); + + if !matches!(sig.header.abi, Abi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + for arg in check_fn_args( cx, cx.tcx @@ -222,6 +229,12 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { }; check_mut_from_ref(cx, sig, Some(body)); + + if !matches!(sig.header.abi, Abi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + let decl = sig.decl; let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, &decl.output, body.params) diff --git a/src/tools/clippy/clippy_lints/src/redundant_locals.rs b/src/tools/clippy/clippy_lints/src/redundant_locals.rs new file mode 100644 index 0000000000000..140ae837a1723 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_locals.rs @@ -0,0 +1,103 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_from_proc_macro; +use clippy_utils::ty::needs_ordered_drop; +use rustc_hir::def::Res; +use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// ### What it does + /// Checks for redundant redefinitions of local bindings. + /// + /// ### Why is this bad? + /// Redundant redefinitions of local bindings do not change behavior and are likely to be unintended. + /// + /// Note that although these bindings do not affect your code's meaning, they _may_ affect `rustc`'s stack allocation. + /// + /// ### Example + /// ```rust + /// let a = 0; + /// let a = a; + /// + /// fn foo(b: i32) { + /// let b = b; + /// } + /// ``` + /// Use instead: + /// ```rust + /// let a = 0; + /// // no redefinition with the same name + /// + /// fn foo(b: i32) { + /// // no redefinition with the same name + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub REDUNDANT_LOCALS, + correctness, + "redundant redefinition of a local binding" +} +declare_lint_pass!(RedundantLocals => [REDUNDANT_LOCALS]); + +impl<'tcx> LateLintPass<'tcx> for RedundantLocals { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + if_chain! { + // the pattern is a single by-value binding + if let PatKind::Binding(BindingAnnotation(ByRef::No, mutability), _, ident, None) = local.pat.kind; + // the binding is not type-ascribed + if local.ty.is_none(); + // the expression is a resolved path + if let Some(expr) = local.init; + if let ExprKind::Path(qpath @ QPath::Resolved(None, path)) = expr.kind; + // the path is a single segment equal to the local's name + if let [last_segment] = path.segments; + if last_segment.ident == ident; + // resolve the path to its defining binding pattern + if let Res::Local(binding_id) = cx.qpath_res(&qpath, expr.hir_id); + if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id); + // the previous binding has the same mutability + if find_binding(binding_pat, ident).unwrap().1 == mutability; + // the local does not affect the code's drop behavior + if !affects_drop_behavior(cx, binding_id, local.hir_id, expr); + // the local is user-controlled + if !in_external_macro(cx.sess(), local.span); + if !is_from_proc_macro(cx, expr); + then { + span_lint_and_help( + cx, + REDUNDANT_LOCALS, + vec![binding_pat.span, local.span], + "redundant redefinition of a binding", + None, + &format!("remove the redefinition of `{ident}`"), + ); + } + } + } +} + +/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced. +fn find_binding(pat: &Pat<'_>, name: Ident) -> Option { + let mut ret = None; + + pat.each_binding_or_first(&mut |annotation, _, _, ident| { + if ident == name { + ret = Some(annotation); + } + }); + + ret +} + +/// Check if a rebinding of a local affects the code's drop behavior. +fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool { + let hir = cx.tcx.hir(); + + // the rebinding is in a different scope than the original binding + // and the type of the binding cares about drop order + hir.get_enclosing_scope(bind) != hir.get_enclosing_scope(rebind) + && needs_ordered_drop(cx, cx.typeck_results().expr_ty(rebind_expr)) +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs index 038dfe8e4803c..ed42a422b4b5a 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs @@ -5,6 +5,7 @@ use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; declare_clippy_lint! { /// ### What it does @@ -64,7 +65,7 @@ impl RedundantStaticLifetimes { if let Some(lifetime) = *optional_lifetime { match borrow_type.ty.kind { TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => { - if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime { + if lifetime.ident.name == kw::StaticLifetime { let snip = snippet(cx, borrow_type.ty.span, ""); let sugg = format!("&{}{snip}", borrow_type.mutbl.prefix_str()); span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/renamed_lints.rs b/src/tools/clippy/clippy_lints/src/renamed_lints.rs index e532dd61a829b..49bdc6796049a 100644 --- a/src/tools/clippy/clippy_lints/src/renamed_lints.rs +++ b/src/tools/clippy/clippy_lints/src/renamed_lints.rs @@ -30,6 +30,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::single_char_push_str", "clippy::single_char_add_str"), ("clippy::stutter", "clippy::module_name_repetitions"), ("clippy::to_string_in_display", "clippy::recursive_format_impl"), + ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), ("clippy::zero_width_space", "clippy::invisible_characters"), ("clippy::cast_ref_to_mut", "invalid_reference_casting"), ("clippy::clone_double_ref", "suspicious_double_ref_op"), diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs index 2a494ff1fa674..351bacf5691f3 100644 --- a/src/tools/clippy/clippy_lints/src/returns.rs +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -1,12 +1,14 @@ -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; -use clippy_utils::{fn_def_id, path_to_local_id, span_find_starting_semi}; +use clippy_utils::{fn_def_id, is_from_proc_macro, path_to_local_id, span_find_starting_semi}; use core::ops::ControlFlow; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind}; +use rustc_hir::{ + Block, Body, Expr, ExprKind, FnDecl, ItemKind, LangItem, MatchSource, OwnerNode, PatKind, QPath, Stmt, StmtKind, +}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, GenericArgKind, Ty}; @@ -76,6 +78,46 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements on `Err` paired with the `?` operator. + /// + /// ### Why is this bad? + /// The `return` is unnecessary. + /// + /// ### Example + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// simplify to + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// if paired with `try_err`, use instead: + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...); + /// } + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub NEEDLESS_RETURN_WITH_QUESTION_MARK, + style, + "using a return statement like `return Err(expr)?;` where removing it would suffice" +} + #[derive(PartialEq, Eq)] enum RetReplacement<'tcx> { Empty, @@ -115,9 +157,35 @@ impl<'tcx> ToString for RetReplacement<'tcx> { } } -declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]); +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); impl<'tcx> LateLintPass<'tcx> for Return { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if !in_external_macro(cx.sess(), stmt.span) + && let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::Ret(Some(ret)) = expr.kind + && let ExprKind::Match(.., MatchSource::TryDesugar) = ret.kind + // Ensure this is not the final stmt, otherwise removing it would cause a compile error + && let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id)) + && let ItemKind::Fn(_, _, body) = item.kind + && let block = cx.tcx.hir().body(body).value + && let ExprKind::Block(block, _) = block.kind + && let [.., final_stmt] = block.stmts + && final_stmt.hir_id != stmt.hir_id + && !is_from_proc_macro(cx, expr) + { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN_WITH_QUESTION_MARK, + expr.span.until(ret.span), + "unneeded `return` statement with `?` operator", + "remove it", + String::new(), + Applicability::MachineApplicable, + ); + } + } + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { // we need both a let-binding stmt and an expr if_chain! { @@ -173,6 +241,10 @@ impl<'tcx> LateLintPass<'tcx> for Return { sp: Span, _: LocalDefId, ) { + if sp.from_expansion() { + return; + } + match kind { FnKind::Closure => { // when returning without value in closure, replace this `return` diff --git a/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs index 355f907e2577b..c9547cd95dca1 100644 --- a/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs +++ b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs @@ -43,7 +43,7 @@ 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(); - let mut app = Applicability::MaybeIncorrect; + let mut app = Applicability::MachineApplicable; 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); diff --git a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs index 3b282dbfa0480..4b248c9c7900b 100644 --- a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs +++ b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, path_to_local}; +use clippy_utils::{expr_or_init, get_attr, path_to_local, peel_hir_expr_unary}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -235,7 +235,7 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx fn manage_has_expensive_expr_after_last_attr(&mut self) { let has_expensive_stmt = match self.ap.curr_stmt.kind { - hir::StmtKind::Expr(expr) if !is_expensive_expr(expr) => false, + hir::StmtKind::Expr(expr) if is_inexpensive_expr(expr) => false, hir::StmtKind::Local(local) if let Some(expr) = local.init && let hir::ExprKind::Path(_) = expr.kind => false, _ => true @@ -330,13 +330,13 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> Visitor<'tcx> for StmtsChecker<'ap, 'lc, 'o apa.last_method_span = span; } }, - hir::StmtKind::Semi(expr) => { - if has_drop(expr, &apa.first_bind_ident, self.cx) { + hir::StmtKind::Semi(semi_expr) => { + if has_drop(semi_expr, &apa.first_bind_ident, self.cx) { apa.has_expensive_expr_after_last_attr = false; apa.last_stmt_span = DUMMY_SP; return; } - if let hir::ExprKind::MethodCall(_, _, _, span) = expr.kind { + if let hir::ExprKind::MethodCall(_, _, _, span) = semi_expr.kind { apa.last_method_span = span; } }, @@ -434,16 +434,31 @@ fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: &Ident, lcx: &LateContext<'_ && let Res::Def(DefKind::Fn, did) = fun_path.res && lcx.tcx.is_diagnostic_item(sym::mem_drop, did) && let [first_arg, ..] = args - && let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &first_arg.kind - && let [first_arg_ps, .. ] = arg_path.segments { - &first_arg_ps.ident == first_bind_ident - } - else { - false + let has_ident = |local_expr: &hir::Expr<'_>| { + if let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &local_expr.kind + && let [first_arg_ps, .. ] = arg_path.segments + && &first_arg_ps.ident == first_bind_ident + { + true + } + else { + false + } + }; + if has_ident(first_arg) { + return true; + } + if let hir::ExprKind::Tup(value) = &first_arg.kind && value.iter().any(has_ident) { + return true; + } } + false } -fn is_expensive_expr(expr: &hir::Expr<'_>) -> bool { - !matches!(expr.kind, hir::ExprKind::Path(_)) +fn is_inexpensive_expr(expr: &hir::Expr<'_>) -> bool { + let actual = peel_hir_expr_unary(expr).0; + let is_path = matches!(actual.kind, hir::ExprKind::Path(_)); + let is_lit = matches!(actual.kind, hir::ExprKind::Lit(_)); + is_path || is_lit } diff --git a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs index 858135c8d4647..54a33eb2986d3 100644 --- a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs +++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ - get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq, + get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local, + path_to_local_id, paths, SpanlessEq, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor}; -use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -60,7 +60,24 @@ struct VecAllocation<'tcx> { /// Reference to the expression used as argument on `with_capacity` call. This is used /// to only match slow zero-filling idioms of the same length than vector initialization. - len_expr: &'tcx Expr<'tcx>, + size_expr: InitializedSize<'tcx>, +} + +/// Initializer for the creation of the vector. +/// +/// When `Vec::with_capacity(size)` is found, the `size` expression will be in +/// `InitializedSize::Initialized`. +/// +/// Otherwise, for `Vec::new()` calls, there is no allocation initializer yet, so +/// `InitializedSize::Uninitialized` is used. +/// Later, when a call to `.resize(size, 0)` or similar is found, it's set +/// to `InitializedSize::Initialized(size)`. +/// +/// Since it will be set to `InitializedSize::Initialized(size)` when a slow initialization is +/// found, it is always safe to "unwrap" it at lint time. +enum InitializedSize<'tcx> { + Initialized(&'tcx Expr<'tcx>), + Uninitialized, } /// Type of slow initialization @@ -77,18 +94,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { // Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)` if_chain! { if let ExprKind::Assign(left, right, _) = expr.kind; - - // Extract variable if let Some(local_id) = path_to_local(left); - - // Extract len argument - if let Some(len_arg) = Self::is_vec_with_capacity(cx, right); + if let Some(size_expr) = Self::as_vec_initializer(cx, right); then { let vi = VecAllocation { local_id, allocation_expr: right, - len_expr: len_arg, + size_expr, }; Self::search_initialization(cx, vi, expr.hir_id); @@ -98,17 +111,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` + // or `Vec::new()` if_chain! { if let StmtKind::Local(local) = stmt.kind; if let PatKind::Binding(BindingAnnotation::MUT, local_id, _, None) = local.pat.kind; if let Some(init) = local.init; - if let Some(len_arg) = Self::is_vec_with_capacity(cx, init); + if let Some(size_expr) = Self::as_vec_initializer(cx, init); then { let vi = VecAllocation { local_id, allocation_expr: init, - len_expr: len_arg, + size_expr, }; Self::search_initialization(cx, vi, stmt.hir_id); @@ -118,19 +132,20 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { } impl SlowVectorInit { - /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression - /// of the first argument of `with_capacity` call if it matches or `None` if it does not. - fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if_chain! { - if let ExprKind::Call(func, [arg]) = expr.kind; - if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind; - if name.ident.as_str() == "with_capacity"; - if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec); - then { - Some(arg) - } else { - None - } + /// Looks for `Vec::with_capacity(size)` or `Vec::new()` calls and returns the initialized size, + /// if any. More specifically, it returns: + /// - `Some(InitializedSize::Initialized(size))` for `Vec::with_capacity(size)` + /// - `Some(InitializedSize::Uninitialized)` for `Vec::new()` + /// - `None` for other, unrelated kinds of expressions + fn as_vec_initializer<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option> { + if let ExprKind::Call(func, [len_expr]) = expr.kind + && is_expr_path_def_path(cx, func, &paths::VEC_WITH_CAPACITY) + { + Some(InitializedSize::Initialized(len_expr)) + } else if matches!(expr.kind, ExprKind::Call(func, _) if is_expr_path_def_path(cx, func, &paths::VEC_NEW)) { + Some(InitializedSize::Uninitialized) + } else { + None } } @@ -169,12 +184,19 @@ impl SlowVectorInit { } fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) { - let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); + let len_expr = Sugg::hir( + cx, + match vec_alloc.size_expr { + InitializedSize::Initialized(expr) => expr, + InitializedSize::Uninitialized => unreachable!("size expression must be set by this point"), + }, + "len", + ); span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| { diag.span_suggestion( vec_alloc.allocation_expr.span, - "consider replace allocation with", + "consider replacing this with", format!("vec![0; {len_expr}]"), Applicability::Unspecified, ); @@ -214,36 +236,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { } /// Checks if the given expression is resizing a vector with 0 - fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { + fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) && path.ident.name == sym!(resize) // Check that is filled with 0 - && is_integer_literal(fill_arg, 0) { - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { - self.slow_expression = Some(InitializationType::Resize(expr)); - } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" { - self.slow_expression = Some(InitializationType::Resize(expr)); - } + && is_integer_literal(fill_arg, 0) + { + let is_matching_resize = if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr { + // If we have a size expression, check that it is equal to what's passed to `resize` + SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr) + || matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity") + } else { + self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg); + true + }; + + if is_matching_resize { + self.slow_expression = Some(InitializationType::Resize(expr)); } + } } /// Returns `true` if give expression is `repeat(0).take(...)` - fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { + fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { if_chain! { if let ExprKind::MethodCall(take_path, recv, [len_arg, ..], _) = expr.kind; if take_path.ident.name == sym!(take); // Check that take is applied to `repeat(0)` if self.is_repeat_zero(recv); then { - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { - return true; - } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" { - return true; + if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr { + // Check that len expression is equals to `with_capacity` expression + return SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr) + || matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity") } + + self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg); + return true; } } diff --git a/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs b/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs index 2e00ed042a8b6..7eec69820924d 100644 --- a/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs +++ b/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs @@ -1,21 +1,29 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::visitors::for_each_local_use_after_expr; use clippy_utils::{is_from_proc_macro, path_to_local}; +use itertools::Itertools; use rustc_ast::LitKind; -use rustc_hir::{Expr, ExprKind, HirId, Node, Pat}; +use rustc_hir::{Expr, ExprKind, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use std::iter::once; +use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does /// Checks for tuple<=>array conversions that are not done with `.into()`. /// /// ### Why is this bad? - /// It may be unnecessary complexity. `.into()` works for converting tuples - /// <=> arrays of up to 12 elements and may convey intent more clearly. + /// It may be unnecessary complexity. `.into()` works for converting tuples<=> arrays of up to + /// 12 elements and conveys the intent more clearly, while also leaving less room for hard to + /// spot bugs! + /// + /// ### Known issues + /// The suggested code may hide potential asymmetry in some cases. See + /// [#11085](https://github.com/rust-lang/rust-clippy/issues/11085) for more info. /// /// ### Example /// ```rust,ignore @@ -29,7 +37,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.72.0"] pub TUPLE_ARRAY_CONVERSIONS, - pedantic, + nursery, "checks for tuple<=>array conversions that are not done with `.into()`" } impl_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]); @@ -41,130 +49,152 @@ pub struct TupleArrayConversions { impl LateLintPass<'_> for TupleArrayConversions { fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if !in_external_macro(cx.sess(), expr.span) && self.msrv.meets(msrvs::TUPLE_ARRAY_CONVERSIONS) { - match expr.kind { - ExprKind::Array(elements) if (1..=12).contains(&elements.len()) => check_array(cx, expr, elements), - ExprKind::Tup(elements) if (1..=12).contains(&elements.len()) => check_tuple(cx, expr, elements), - _ => {}, - } + if in_external_macro(cx.sess(), expr.span) || !self.msrv.meets(msrvs::TUPLE_ARRAY_CONVERSIONS) { + return; + } + + match expr.kind { + ExprKind::Array(elements) if (1..=12).contains(&elements.len()) => check_array(cx, expr, elements), + ExprKind::Tup(elements) if (1..=12).contains(&elements.len()) => check_tuple(cx, expr, elements), + _ => {}, } } extract_msrv_attr!(LateContext); } -#[expect( - clippy::blocks_in_if_conditions, - reason = "not a FP, but this is much easier to understand" -)] fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { - if should_lint( - cx, - elements, - // This is cursed. - Some, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Tuple(len) if len.len() == elements.len() - ); - } - - false - }, - ) || should_lint( - cx, - elements, - |(i, expr)| { - if let ExprKind::Field(path, field) = expr.kind && field.as_str() == i.to_string() { - return Some((i, path)); - }; - - None - }, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Tuple(len) if len.len() == elements.len() - ); - } + let (ty::Array(ty, _) | ty::Slice(ty)) = cx.typeck_results().expr_ty(expr).kind() else { + unreachable!("`expr` must be an array or slice due to `ExprKind::Array`"); + }; + + if let [first, ..] = elements + && let Some(locals) = (match first.kind { + ExprKind::Field(_, _) => elements + .iter() + .enumerate() + .map(|(i, f)| -> Option<&'tcx Expr<'tcx>> { + let ExprKind::Field(lhs, ident) = f.kind else { + return None; + }; + (ident.name.as_str() == i.to_string()).then_some(lhs) + }) + .collect::>>(), + ExprKind::Path(_) => Some(elements.iter().collect()), + _ => None, + }) + && all_bindings_are_for_conv(cx, &[*ty], expr, elements, &locals, ToType::Array) + && !is_from_proc_macro(cx, expr) + { + span_lint_and_help( + cx, + TUPLE_ARRAY_CONVERSIONS, + expr.span, + "it looks like you're trying to convert a tuple to an array", + None, + "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", + ); + } +} - false - }, - ) { - emit_lint(cx, expr, ToType::Array); +fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { + if let ty::Tuple(tys) = cx.typeck_results().expr_ty(expr).kind() + && let [first, ..] = elements + // Fix #11100 + && tys.iter().all_equal() + && let Some(locals) = (match first.kind { + ExprKind::Index(_, _) => elements + .iter() + .enumerate() + .map(|(i, i_expr)| -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Index(lhs, index) = i_expr.kind + && let ExprKind::Lit(lit) = index.kind + && let LitKind::Int(val, _) = lit.node + { + return (val == i as u128).then_some(lhs); + }; + + None + }) + .collect::>>(), + ExprKind::Path(_) => Some(elements.iter().collect()), + _ => None, + }) + && all_bindings_are_for_conv(cx, tys, expr, elements, &locals, ToType::Tuple) + && !is_from_proc_macro(cx, expr) + { + span_lint_and_help( + cx, + TUPLE_ARRAY_CONVERSIONS, + expr.span, + "it looks like you're trying to convert an array to a tuple", + None, + "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", + ); } } -#[expect( - clippy::blocks_in_if_conditions, - reason = "not a FP, but this is much easier to understand" -)] +/// Checks that every binding in `elements` comes from the same parent `Pat` with the kind if there +/// is a parent `Pat`. Returns false in any of the following cases: +/// * `kind` does not match `pat.kind` +/// * one or more elements in `elements` is not a binding +/// * one or more bindings does not have the same parent `Pat` +/// * one or more bindings are used after `expr` +/// * the bindings do not all have the same type #[expect(clippy::cast_possible_truncation)] -fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { - if should_lint(cx, elements, Some, |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Array(_, len) if len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() - ); +fn all_bindings_are_for_conv<'tcx>( + cx: &LateContext<'tcx>, + final_tys: &[Ty<'tcx>], + expr: &Expr<'_>, + elements: &[Expr<'_>], + locals: &[&Expr<'_>], + kind: ToType, +) -> bool { + let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::>>() else { + return false; + }; + let Some(local_parents) = locals + .iter() + .map(|&l| cx.tcx.hir().find_parent(l)) + .collect::>>() + else { + return false; + }; + + local_parents + .iter() + .map(|node| match node { + Node::Pat(pat) => kind.eq(&pat.kind).then_some(pat.hir_id), + Node::Local(l) => Some(l.hir_id), + _ => None, + }) + .all_equal() + // Fix #11124, very convenient utils function! ❤️ + && locals + .iter() + .all(|&l| for_each_local_use_after_expr(cx, l, expr.hir_id, |_| ControlFlow::Break::<()>(())).is_continue()) + && local_parents.first().is_some_and(|node| { + let Some(ty) = match node { + Node::Pat(pat) => Some(pat.hir_id), + Node::Local(l) => Some(l.hir_id), + _ => None, } - - false - }) || should_lint( - cx, - elements, - |(i, expr)| { - if let ExprKind::Index(path, index) = expr.kind - && let ExprKind::Lit(lit) = index.kind - && let LitKind::Int(val, _) = lit.node - && val as usize == i - { - return Some((i, path)); + .map(|hir_id| cx.typeck_results().node_type(hir_id)) else { + return false; }; - - None - }, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Array(_, len) if len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() - ); + match (kind, ty.kind()) { + // Ensure the final type and the original type have the same length, and that there + // is no implicit `&mut`<=>`&` anywhere (#11100). Bit ugly, I know, but it works. + (ToType::Array, ty::Tuple(tys)) => { + tys.len() == elements.len() && tys.iter().chain(final_tys.iter().copied()).all_equal() + }, + (ToType::Tuple, ty::Array(ty, len)) => { + len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() + && final_tys.iter().chain(once(ty)).all_equal() + }, + _ => false, } - - false - }, - ) { - emit_lint(cx, expr, ToType::Tuple); - } -} - -/// Walks up the `Pat` until it's reached the final containing `Pat`. -fn parent_pat<'tcx>(cx: &LateContext<'tcx>, start: &'tcx Pat<'tcx>) -> &'tcx Pat<'tcx> { - let mut end = start; - for (_, node) in cx.tcx.hir().parent_iter(start.hir_id) { - if let Node::Pat(pat) = node { - end = pat; - } else { - break; - } - } - end + }) } #[derive(Clone, Copy)] @@ -173,61 +203,11 @@ enum ToType { Tuple, } -impl ToType { - fn msg(self) -> &'static str { - match self { - ToType::Array => "it looks like you're trying to convert a tuple to an array", - ToType::Tuple => "it looks like you're trying to convert an array to a tuple", - } - } - - fn help(self) -> &'static str { +impl PartialEq> for ToType { + fn eq(&self, other: &PatKind<'_>) -> bool { match self { - ToType::Array => "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", - ToType::Tuple => "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", + ToType::Array => matches!(other, PatKind::Tuple(_, _)), + ToType::Tuple => matches!(other, PatKind::Slice(_, _, _)), } } } - -fn emit_lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, to_type: ToType) -> bool { - if !is_from_proc_macro(cx, expr) { - span_lint_and_help( - cx, - TUPLE_ARRAY_CONVERSIONS, - expr.span, - to_type.msg(), - None, - to_type.help(), - ); - - return true; - } - - false -} - -fn should_lint<'tcx>( - cx: &LateContext<'tcx>, - elements: &'tcx [Expr<'tcx>], - map: impl FnMut((usize, &'tcx Expr<'tcx>)) -> Option<(usize, &Expr<'_>)>, - predicate: impl FnMut((HirId, &Node<'tcx>)) -> bool, -) -> bool { - if let Some(elements) = elements - .iter() - .enumerate() - .map(map) - .collect::>>() - && let Some(locals) = elements - .iter() - .map(|(_, element)| path_to_local(element).and_then(|local| cx.tcx.hir().find(local))) - .collect::>>() - && let [first, rest @ ..] = &*locals - && let Node::Pat(first_pat) = first - && let parent = parent_pat(cx, first_pat).hir_id - && rest.iter().chain(once(first)).map(|i| (parent, i)).all(predicate) - { - return true; - } - - false -} diff --git a/src/tools/clippy/clippy_lints/src/unused_async.rs b/src/tools/clippy/clippy_lints/src/unused_async.rs index 5e42cf7e4f3f6..bc7c3897a6e8d 100644 --- a/src/tools/clippy/clippy_lints/src/unused_async.rs +++ b/src/tools/clippy/clippy_lints/src/unused_async.rs @@ -1,11 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_def_id_trait_method; +use rustc_hir::def::DefKind; use rustc_hir::intravisit::{walk_body, walk_expr, walk_fn, FnKind, Visitor}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::def_id::LocalDefId; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::{LocalDefId, LocalDefIdSet}; use rustc_span::Span; declare_clippy_lint! { @@ -38,7 +39,24 @@ declare_clippy_lint! { "finds async functions with no await statements" } -declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); +#[derive(Default)] +pub struct UnusedAsync { + /// Keeps track of async functions used as values (i.e. path expressions to async functions that + /// are not immediately called) + async_fns_as_value: LocalDefIdSet, + /// Functions with unused `async`, linted post-crate after we've found all uses of local async + /// functions + unused_async_fns: Vec, +} + +#[derive(Copy, Clone)] +struct UnusedAsyncFn { + def_id: LocalDefId, + fn_span: Span, + await_in_async_block: Option, +} + +impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); struct AsyncFnVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, @@ -101,24 +119,70 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { }; walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id); if !visitor.found_await { - span_lint_and_then( - cx, - UNUSED_ASYNC, - span, - "unused `async` for function with no await statements", - |diag| { - diag.help("consider removing the `async` from this function"); - - if let Some(span) = visitor.await_in_async_block { - diag.span_note( - span, - "`await` used in an async block, which does not require \ - the enclosing function to be `async`", - ); - } - }, - ); + // Don't lint just yet, but store the necessary information for later. + // The actual linting happens in `check_crate_post`, once we've found all + // uses of local async functions that do require asyncness to pass typeck + self.unused_async_fns.push(UnusedAsyncFn { + await_in_async_block: visitor.await_in_async_block, + fn_span: span, + def_id, + }); } } } + + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) { + fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool { + matches!( + node, + Node::Expr(Expr { + kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..), + .. + }) if *span == expected_receiver + ) + } + + // Find paths to local async functions that aren't immediately called. + // E.g. `async fn f() {}; let x = f;` + // Depending on how `x` is used, f's asyncness might be required despite not having any `await` + // statements, so don't lint at all if there are any such paths. + if let Some(def_id) = path.res.opt_def_id() + && let Some(local_def_id) = def_id.as_local() + && let Some(DefKind::Fn) = cx.tcx.opt_def_kind(def_id) + && cx.tcx.asyncness(def_id).is_async() + && !is_node_func_call(cx.tcx.hir().get_parent(hir_id), path.span) + { + self.async_fns_as_value.insert(local_def_id); + } + } + + // After collecting all unused `async` and problematic paths to such functions, + // lint those unused ones that didn't have any path expressions to them. + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + let iter = self + .unused_async_fns + .iter() + .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id))); + + for fun in iter { + span_lint_hir_and_then( + cx, + UNUSED_ASYNC, + cx.tcx.local_def_id_to_hir_id(fun.def_id), + fun.fn_span, + "unused `async` for function with no await statements", + |diag| { + diag.help("consider removing the `async` from this function"); + + if let Some(span) = fun.await_in_async_block { + diag.span_note( + span, + "`await` used in an async block, which does not require \ + the enclosing function to be `async`", + ); + } + }, + ); + } + } } diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs index 76654bfe53602..58ae0656db73e 100644 --- a/src/tools/clippy/clippy_lints/src/utils/conf.rs +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -551,6 +551,16 @@ define_Conf! { /// /// Whether to allow `r#""#` when `r""` can be used (allow_one_hash_in_raw_strings: bool = false), + /// Lint: ABSOLUTE_PATHS. + /// + /// The maximum number of segments a path can have before being linted, anything above this will + /// be linted. + (absolute_paths_max_segments: u64 = 2), + /// Lint: ABSOLUTE_PATHS. + /// + /// Which crates to allow absolute paths from + (absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet = + rustc_data_structures::fx::FxHashSet::default()), } /// Search for the configuration file. diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs index fb08256931f66..4fef8c0717d8a 100644 --- a/src/tools/clippy/clippy_lints/src/utils/mod.rs +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -18,7 +18,7 @@ const BOOK_CONFIGS_PATH: &str = "https://doc.rust-lang.org/clippy/lint_configura // ================================================================== // Configuration // ================================================================== -#[derive(Debug, Clone, Default)] //~ ERROR no such field +#[derive(Debug, Clone, Default)] pub struct ClippyConfiguration { pub name: String, #[allow(dead_code)] diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs index 357ac40b45594..802adbd4d2d56 100644 --- a/src/tools/clippy/clippy_utils/src/higher.rs +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -138,6 +138,7 @@ impl<'hir> IfLet<'hir> { } /// An `if let` or `match` expression. Useful for lints that trigger on one or the other. +#[derive(Debug)] pub enum IfLetOrMatch<'hir> { /// Any `match` expression Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource), diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index fb359ee3bbe01..85b3b005f93df 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -252,15 +252,15 @@ impl HirEqInterExpr<'_, '_, '_> { return false; } - if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results { - if let (Some(l), Some(r)) = ( + if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results + && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) + && let (Some(l), Some(r)) = ( constant_simple(self.inner.cx, typeck_lhs, left), constant_simple(self.inner.cx, typeck_rhs, right), - ) { - if l == r { - return true; - } - } + ) + && l == r + { + return true; } let is_eq = match ( @@ -494,10 +494,13 @@ impl HirEqInterExpr<'_, '_, '_> { loop { use TokenKind::{BlockComment, LineComment, Whitespace}; if left_data.macro_def_id != right_data.macro_def_id - || (matches!(left_data.kind, ExpnKind::Macro(MacroKind::Bang, name) if name == sym::cfg) - && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| { - !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }) - })) + || (matches!( + left_data.kind, + ExpnKind::Macro(MacroKind::Bang, name) + if name == sym::cfg || name == sym::option_env + ) && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| { + !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }) + })) { // Either a different chain of macro calls, or different arguments to the `cfg` macro. return false; diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 45b99df46b010..cf30930b76eac 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -89,21 +89,21 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::{ self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr, - ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local, - MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, - TraitItem, TraitItemRef, TraitRef, TyKind, UnOp, + ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, + ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, + QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, }; 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; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ - BorrowKind, ClosureKind, FloatTy, IntTy, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture, + self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut, + TypeVisitableExt, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; @@ -113,7 +113,10 @@ use rustc_target::abi::Integer; 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::ty::{ + adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, + ty_is_fn_once_param, +}; use crate::visitors::for_each_expr; use rustc_middle::hir::nested_filter; @@ -2445,6 +2448,17 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { .any(is_cfg_test) } +/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied. +pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + let hir = tcx.hir(); + + tcx.has_attr(def_id, sym::cfg) + || hir + .parent_iter(hir.local_def_id_to_hir_id(def_id)) + .flat_map(|(parent_id, _)| hir.attrs(parent_id)) + .any(|attr| attr.has_name(sym::cfg)) +} + /// Checks whether item either has `test` attribute applied, or /// is a module with `test` in its name. /// @@ -2499,6 +2513,262 @@ pub fn walk_to_expr_usage<'tcx, T>( None } +/// A type definition as it would be viewed from within a function. +#[derive(Clone, Copy)] +pub enum DefinedTy<'tcx> { + // Used for locals and closures defined within the function. + Hir(&'tcx hir::Ty<'tcx>), + /// Used for function signatures, and constant and static values. This includes the `ParamEnv` + /// from the definition site. + Mir(ParamEnvAnd<'tcx, Binder<'tcx, Ty<'tcx>>>), +} + +/// The context an expressions value is used in. +pub struct ExprUseCtxt<'tcx> { + /// The parent node which consumes the value. + pub node: ExprUseNode<'tcx>, + /// Any adjustments applied to the type. + pub adjustments: &'tcx [Adjustment<'tcx>], + /// Whether or not the type must unify with another code path. + pub is_ty_unified: bool, + /// Whether or not the value will be moved before it's used. + pub moved_before_use: bool, +} + +/// The node which consumes a value. +pub enum ExprUseNode<'tcx> { + /// Assignment to, or initializer for, a local + Local(&'tcx Local<'tcx>), + /// Initializer for a const or static item. + ConstStatic(OwnerId), + /// Implicit or explicit return from a function. + Return(OwnerId), + /// Initialization of a struct field. + Field(&'tcx ExprField<'tcx>), + /// An argument to a function. + FnArg(&'tcx Expr<'tcx>, usize), + /// An argument to a method. + MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize), + /// The callee of a function call. + Callee, + /// Access of a field. + FieldAccess(Ident), +} +impl<'tcx> ExprUseNode<'tcx> { + /// Checks if the value is returned from the function. + pub fn is_return(&self) -> bool { + matches!(self, Self::Return(_)) + } + + /// Checks if the value is used as a method call receiver. + pub fn is_recv(&self) -> bool { + matches!(self, Self::MethodArg(_, _, 0)) + } + + /// Gets the needed type as it's defined without any type inference. + pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option> { + match *self { + Self::Local(Local { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)), + Self::ConstStatic(id) => Some(DefinedTy::Mir( + cx.param_env + .and(Binder::dummy(cx.tcx.type_of(id).instantiate_identity())), + )), + Self::Return(id) => { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(id.def_id); + if let Some(Node::Expr(Expr { + kind: ExprKind::Closure(c), + .. + })) = cx.tcx.hir().find(hir_id) + { + match c.fn_decl.output { + FnRetTy::DefaultReturn(_) => None, + FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)), + } + } else { + Some(DefinedTy::Mir( + cx.param_env.and(cx.tcx.fn_sig(id).instantiate_identity().output()), + )) + } + }, + Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) { + Some(Expr { + hir_id, + kind: ExprKind::Struct(path, ..), + .. + }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) + .and_then(|(adt, variant)| { + variant + .fields + .iter() + .find(|f| f.name == field.ident.name) + .map(|f| (adt, f)) + }) + .map(|(adt, field_def)| { + DefinedTy::Mir( + cx.tcx + .param_env(adt.did()) + .and(Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity())), + ) + }), + _ => None, + }, + Self::FnArg(callee, i) => { + let sig = expr_sig(cx, callee)?; + let (hir_ty, ty) = sig.input_with_hir(i)?; + Some(match hir_ty { + Some(hir_ty) => DefinedTy::Hir(hir_ty), + None => DefinedTy::Mir( + sig.predicates_id() + .map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)) + .and(ty), + ), + }) + }, + Self::MethodArg(id, _, i) => { + let id = cx.typeck_results().type_dependent_def_id(id)?; + let sig = cx.tcx.fn_sig(id).skip_binder(); + Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) + }, + Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None, + } + } +} + +/// Gets the context an expression's value is used in. +#[expect(clippy::too_many_lines)] +pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option> { + let mut adjustments = [].as_slice(); + let mut is_ty_unified = false; + let mut moved_before_use = false; + let ctxt = e.span.ctxt(); + walk_to_expr_usage(cx, e, &mut |parent, child_id| { + // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. + if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) { + adjustments = cx.typeck_results().expr_adjustments(e); + } + match parent { + Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Local(l), + adjustments, + is_ty_unified, + moved_before_use, + }), + Node::Item(&Item { + kind: ItemKind::Static(..) | ItemKind::Const(..), + owner_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + owner_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + owner_id, + span, + .. + }) if span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::ConstStatic(owner_id), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::Item(&Item { + kind: ItemKind::Fn(..), + owner_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(..), + owner_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(..), + owner_id, + span, + .. + }) if span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Return(owner_id), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Field(field), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { + ExprKind::Ret(_) => Some(ExprUseCtxt { + node: ExprUseNode::Return(OwnerId { + def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + }), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Closure(closure) => Some(ExprUseCtxt { + node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Call(func, args) => Some(ExprUseCtxt { + node: match args.iter().position(|arg| arg.hir_id == child_id) { + Some(i) => ExprUseNode::FnArg(func, i), + None => ExprUseNode::Callee, + }, + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt { + node: ExprUseNode::MethodArg( + parent.hir_id, + name.args, + args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), + ), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt { + node: ExprUseNode::FieldAccess(name), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { + is_ty_unified = true; + moved_before_use = true; + None + }, + ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { + is_ty_unified = true; + moved_before_use = true; + None + }, + ExprKind::Block(..) => { + moved_before_use = true; + None + }, + _ => None, + }, + _ => None, + } + }) +} + /// Tokenizes the input while keeping the text associated with each token. pub fn tokenize_with_text(s: &str) -> impl Iterator { let mut pos = 0; diff --git a/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs b/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs index 8e7513d740ab3..da04266863f94 100644 --- a/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs +++ b/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs @@ -44,7 +44,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> { let lhs = place.local; match rvalue { // Only consider `&mut`, which can modify origin place - mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) | + mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) | // _2: &mut _; // _3 = move _2 mir::Rvalue::Use(mir::Operand::Move(borrowed)) | diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index f3677e6f61417..914ea85ac2809 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -149,6 +149,7 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"]; pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; +pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"]; pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; @@ -161,3 +162,6 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"]; pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"]; pub const FORMATTER: [&str; 3] = ["core", "fmt", "Formatter"]; pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"]; +pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"]; +#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so +pub const BOOL_THEN: [&str; 4] = ["core", "bool", "", "then"]; diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index fd39a246f48de..d0e15aa8bb328 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -3,6 +3,7 @@ #![allow(clippy::module_name_repetitions)] use core::ops::ControlFlow; +use itertools::Itertools; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; @@ -13,21 +14,26 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ - self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, IntTy, - List, ParamEnv, Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - UintTy, VariantDef, VariantDiscr, + self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, + GenericParamDefKind, IntTy, List, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt, + TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Size, VariantIdx}; -use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; +use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::iter; use crate::{match_def_path, path_res, paths}; +mod type_certainty; +pub use type_certainty::expr_type_is_certain; + /// Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_copy_modulo_regions(cx.tcx, cx.param_env) @@ -204,15 +210,9 @@ pub fn implements_trait<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, trait_id: DefId, - ty_params: &[GenericArg<'tcx>], + args: &[GenericArg<'tcx>], ) -> bool { - implements_trait_with_env( - cx.tcx, - cx.param_env, - ty, - trait_id, - ty_params.iter().map(|&arg| Some(arg)), - ) + implements_trait_with_env_from_iter(cx.tcx, cx.param_env, ty, trait_id, args.iter().map(|&x| Some(x))) } /// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context. @@ -221,7 +221,18 @@ pub fn implements_trait_with_env<'tcx>( param_env: ParamEnv<'tcx>, ty: Ty<'tcx>, trait_id: DefId, - ty_params: impl IntoIterator>>, + args: &[GenericArg<'tcx>], +) -> bool { + implements_trait_with_env_from_iter(tcx, param_env, ty, trait_id, args.iter().map(|&x| Some(x))) +} + +/// Same as `implements_trait_from_env` but takes the arguments as an iterator. +pub fn implements_trait_with_env_from_iter<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + args: impl IntoIterator>>>, ) -> bool { // Clippy shouldn't have infer types assert!(!ty.has_infer()); @@ -230,19 +241,37 @@ pub fn implements_trait_with_env<'tcx>( if ty.has_escaping_bound_vars() { return false; } + let infcx = tcx.infer_ctxt().build(); - let orig = TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span: DUMMY_SP, - }; - let ty_params = tcx.mk_args_from_iter( - ty_params + let trait_ref = TraitRef::new( + tcx, + trait_id, + Some(GenericArg::from(ty)) .into_iter() - .map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())), + .chain(args.into_iter().map(|arg| { + arg.into().unwrap_or_else(|| { + let orig = TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }; + infcx.next_ty_var(orig).into() + }) + })), ); + + debug_assert_eq!(tcx.def_kind(trait_id), DefKind::Trait); + #[cfg(debug_assertions)] + assert_generic_args_match(tcx, trait_id, trait_ref.args); + + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx), + }; infcx - .type_implements_trait(trait_id, [ty.into()].into_iter().chain(ty_params), param_env) - .must_apply_modulo_regions() + .evaluate_obligation(&obligation) + .is_ok_and(EvaluationResult::must_apply_modulo_regions) } /// Checks whether this type implements `Drop`. @@ -390,6 +419,11 @@ pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangI } } +/// Gets the diagnostic name of the type, if it has one +pub fn type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option { + ty.ty_adt_def().and_then(|adt| cx.tcx.get_diagnostic_name(adt.did())) +} + /// Return `true` if the passed `typ` is `isize` or `usize`. pub fn is_isize_or_usize(typ: Ty<'_>) -> bool { matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) @@ -739,7 +773,11 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option let mut output = None; let lang_items = cx.tcx.lang_items(); - for (pred, _) in cx.tcx.explicit_item_bounds(ty.def_id).iter_instantiated_copied(cx.tcx, ty.args) { + for (pred, _) in cx + .tcx + .explicit_item_bounds(ty.def_id) + .iter_instantiated_copied(cx.tcx, ty.args) + { match pred.kind().skip_binder() { ty::ClauseKind::Trait(p) if (lang_items.fn_trait() == Some(p.def_id()) @@ -1007,12 +1045,60 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { } } +/// Asserts that the given arguments match the generic parameters of the given item. +#[allow(dead_code)] +fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[GenericArg<'tcx>]) { + let g = tcx.generics_of(did); + let parent = g.parent.map(|did| tcx.generics_of(did)); + let count = g.parent_count + g.params.len(); + let params = parent + .map_or([].as_slice(), |p| p.params.as_slice()) + .iter() + .chain(&g.params) + .map(|x| &x.kind); + + assert!( + count == args.len(), + "wrong number of arguments for `{did:?}`: expected `{count}`, found {}\n\ + note: the expected arguments are: `[{}]`\n\ + the given arguments are: `{args:#?}`", + args.len(), + params.clone().map(GenericParamDefKind::descr).format(", "), + ); + + if let Some((idx, (param, arg))) = + params + .clone() + .zip(args.iter().map(|&x| x.unpack())) + .enumerate() + .find(|(_, (param, arg))| match (param, arg) { + (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) + | (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) + | (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false, + ( + GenericParamDefKind::Lifetime + | GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. }, + _, + ) => true, + }) + { + panic!( + "incorrect argument for `{did:?}` at index `{idx}`: expected a {}, found `{arg:?}`\n\ + note: the expected arguments are `[{}]`\n\ + the given arguments are `{args:#?}`", + param.descr(), + params.clone().map(GenericParamDefKind::descr).format(", "), + ); + } +} + /// Makes the projection type for the named associated type in the given impl or trait impl. /// /// This function is for associated types which are "known" to exist, and as such, will only return /// `None` when debug assertions are disabled in order to prevent ICE's. With debug assertions /// enabled this will check that the named associated type exists, the correct number of -/// substitutions are given, and that the correct kinds of substitutions are given (lifetime, +/// arguments are given, and that the correct kinds of arguments are given (lifetime, /// constant or type). This will not check if type normalization would succeed. pub fn make_projection<'tcx>( tcx: TyCtxt<'tcx>, @@ -1036,49 +1122,7 @@ pub fn make_projection<'tcx>( return None; }; #[cfg(debug_assertions)] - { - let generics = tcx.generics_of(assoc_item.def_id); - let generic_count = generics.parent_count + generics.params.len(); - let params = generics - .parent - .map_or([].as_slice(), |id| &*tcx.generics_of(id).params) - .iter() - .chain(&generics.params) - .map(|x| &x.kind); - - debug_assert!( - generic_count == args.len(), - "wrong number of args for `{:?}`: found `{}` expected `{generic_count}`.\n\ - note: the expected parameters are: {:#?}\n\ - the given arguments are: `{args:#?}`", - assoc_item.def_id, - args.len(), - params.map(ty::GenericParamDefKind::descr).collect::>(), - ); - - if let Some((idx, (param, arg))) = params - .clone() - .zip(args.iter().map(GenericArg::unpack)) - .enumerate() - .find(|(_, (param, arg))| { - !matches!( - (param, arg), - (ty::GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) - | (ty::GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) - | (ty::GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) - ) - }) - { - debug_assert!( - false, - "mismatched subst type at index {idx}: expected a {}, found `{arg:?}`\n\ - note: the expected parameters are {:#?}\n\ - the given arguments are {args:#?}", - param.descr(), - params.map(ty::GenericParamDefKind::descr).collect::>() - ); - } - } + assert_generic_args_match(tcx, assoc_item.def_id, args); Some(tcx.mk_alias_ty(assoc_item.def_id, args)) } @@ -1093,7 +1137,7 @@ pub fn make_projection<'tcx>( /// Normalizes the named associated type in the given impl or trait impl. /// /// This function is for associated types which are "known" to be valid with the given -/// substitutions, and as such, will only return `None` when debug assertions are disabled in order +/// arguments, and as such, will only return `None` when debug assertions are disabled in order /// to prevent ICE's. With debug assertions enabled this will check that type normalization /// succeeds as well as everything checked by `make_projection`. pub fn make_normalized_projection<'tcx>( @@ -1105,17 +1149,12 @@ pub fn make_normalized_projection<'tcx>( ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] - if let Some((i, subst)) = ty - .args - .iter() - .enumerate() - .find(|(_, subst)| subst.has_late_bound_regions()) - { + if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) { debug_assert!( false, "args contain late-bound region at index `{i}` which can't be normalized.\n\ use `TyCtxt::erase_late_bound_regions`\n\ - note: subst is `{subst:#?}`", + note: arg is `{arg:#?}`", ); return None; } @@ -1183,17 +1222,12 @@ pub fn make_normalized_projection_with_regions<'tcx>( ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] - if let Some((i, subst)) = ty - .args - .iter() - .enumerate() - .find(|(_, subst)| subst.has_late_bound_regions()) - { + if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) { debug_assert!( false, "args contain late-bound region at index `{i}` which can't be normalized.\n\ use `TyCtxt::erase_late_bound_regions`\n\ - note: subst is `{subst:#?}`", + note: arg is `{arg:#?}`", ); return None; } diff --git a/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs b/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs new file mode 100644 index 0000000000000..0e69ffa2212d8 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs @@ -0,0 +1,122 @@ +use rustc_hir::def_id::DefId; +use std::fmt::Debug; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Certainty { + /// Determining the type requires contextual information. + Uncertain, + + /// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the + /// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is + /// `Res::Err`. + Certain(Option), + + /// The heuristic believes that more than one `DefId` applies to a type---this is a bug. + Contradiction, +} + +pub trait Meet { + fn meet(self, other: Self) -> Self; +} + +pub trait TryJoin: Sized { + fn try_join(self, other: Self) -> Option; +} + +impl Meet for Option { + fn meet(self, other: Self) -> Self { + match (self, other) { + (None, _) | (_, None) => None, + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(lhs), + } + } +} + +impl TryJoin for Option { + fn try_join(self, other: Self) -> Option { + match (self, other) { + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)), + (Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)), + (None, None) => Some(None), + } + } +} + +impl Meet for Certainty { + fn meet(self, other: Self) -> Self { + match (self, other) { + (Certainty::Uncertain, _) | (_, Certainty::Uncertain) => Certainty::Uncertain, + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => Certainty::Certain(lhs.meet(rhs)), + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + (Certainty::Contradiction, Certainty::Contradiction) => Certainty::Contradiction, + } + } +} + +impl Certainty { + /// Join two `Certainty`s preserving their `DefId`s (if any). Generally speaking, this method + /// should be used only when `self` and `other` refer directly to types. Otherwise, + /// `join_clearing_def_ids` should be used. + pub fn join(self, other: Self) -> Self { + match (self, other) { + (Certainty::Contradiction, _) | (_, Certainty::Contradiction) => Certainty::Contradiction, + + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => { + if let Some(inner) = lhs.try_join(rhs) { + Certainty::Certain(inner) + } else { + debug_assert!(false, "Contradiction with {lhs:?} and {rhs:?}"); + Certainty::Contradiction + } + }, + + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + + (Certainty::Uncertain, Certainty::Uncertain) => Certainty::Uncertain, + } + } + + /// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self` + /// or `other` do not necessarily refer to types, e.g., when they are aggregations of other + /// `Certainty`s. + pub fn join_clearing_def_ids(self, other: Self) -> Self { + self.clear_def_id().join(other.clear_def_id()) + } + + pub fn clear_def_id(self) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(None) + } else { + self + } + } + + pub fn with_def_id(self, def_id: DefId) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(Some(def_id)) + } else { + self + } + } + + pub fn to_def_id(self) -> Option { + match self { + Certainty::Certain(inner) => inner, + _ => None, + } + } + + pub fn is_certain(self) -> bool { + matches!(self, Self::Certain(_)) + } +} + +/// Think: `iter.all(/* is certain */)` +pub fn meet(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Certain(None), Certainty::meet) +} + +/// Think: `iter.any(/* is certain */)` +pub fn join(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Uncertain, Certainty::join) +} diff --git a/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs new file mode 100644 index 0000000000000..45b5483e323d5 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs @@ -0,0 +1,320 @@ +//! A heuristic to tell whether an expression's type can be determined purely from its +//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain` +//! tries to tell whether an expression's type can be determined without appeal to the surrounding +//! context. +//! +//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`. +//! Intuitively, that function determines whether an expression's type is needed for type inference, +//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's +//! type. +//! +//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should +//! be considered a bug. + +use crate::def_path_res; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{walk_qpath, walk_ty, Visitor}; +use rustc_hir::{self as hir, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty}; +use rustc_span::{Span, Symbol}; + +mod certainty; +use certainty::{join, meet, Certainty, Meet}; + +pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr_type_certainty(cx, expr).is_certain() +} + +fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { + let certainty = match &expr.kind { + ExprKind::Unary(_, expr) + | ExprKind::Field(expr, _) + | ExprKind::Index(expr, _) + | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), + + ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Call(callee, args) => { + let lhs = expr_type_certainty(cx, callee); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) + } else { + Certainty::Uncertain + }; + lhs.join_clearing_def_ids(rhs) + }, + + ExprKind::MethodCall(method, receiver, args, _) => { + let mut receiver_type_certainty = expr_type_certainty(cx, receiver); + // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method + // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`, + // for example. So update the `DefId` in `receiver_type_certainty` (if any). + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id)) + { + receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id); + }; + let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet( + std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), + ) + } else { + Certainty::Uncertain + }; + lhs.join(rhs) + }, + + ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)), + + ExprKind::Lit(_) => Certainty::Certain(None), + + ExprKind::Cast(_, ty) => type_certainty(cx, ty), + + ExprKind::If(_, if_expr, Some(else_expr)) => { + expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr)) + }, + + ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false), + + ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true), + + _ => Certainty::Uncertain, + }; + + let expr_ty = cx.typeck_results().expr_ty(expr); + if let Some(def_id) = adt_def_id(expr_ty) { + certainty.with_def_id(def_id) + } else { + certainty + } +} + +struct CertaintyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + certainty: Certainty, +} + +impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> { + fn new(cx: &'cx LateContext<'tcx>) -> Self { + Self { + cx, + certainty: Certainty::Certain(None), + } + } +} + +impl<'cx, 'tcx> Visitor<'cx> for CertaintyVisitor<'cx, 'tcx> { + fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) { + self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true)); + if self.certainty != Certainty::Uncertain { + walk_qpath(self, qpath, hir_id); + } + } + + fn visit_ty(&mut self, ty: &'cx hir::Ty<'_>) { + if matches!(ty.kind, TyKind::Infer) { + self.certainty = Certainty::Uncertain; + } + if self.certainty != Certainty::Uncertain { + walk_ty(self, ty); + } + } +} + +fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty { + // Handle `TyKind::Path` specially so that its `DefId` can be preserved. + // + // Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to + // `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing + // `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal + // certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here, + // the resulting certainty would be `Certainty::Certain(None)`. + if let TyKind::Path(qpath) = &ty.kind { + return qpath_certainty(cx, qpath, true); + } + + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_ty(ty); + visitor.certainty +} + +fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty { + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_generic_args(args); + visitor.certainty +} + +/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path +/// segments generic arguments are are instantiated. +/// +/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a +/// value. So `DefId`s are retained only when `resolves_to_type` is true. +fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty { + let certainty = match qpath { + QPath::Resolved(ty, path) => { + let len = path.segments.len(); + path.segments.iter().enumerate().fold( + ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)), + |parent_certainty, (i, path_segment)| { + path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type) + }, + ) + }, + + QPath::TypeRelative(ty, path_segment) => { + path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type) + }, + + QPath::LangItem(lang_item, _, _) => { + cx.tcx + .lang_items() + .get(*lang_item) + .map_or(Certainty::Uncertain, |def_id| { + let generics = cx.tcx.generics_of(def_id); + if generics.parent_count == 0 && generics.params.is_empty() { + Certainty::Certain(if resolves_to_type { Some(def_id) } else { None }) + } else { + Certainty::Uncertain + } + }) + }, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +fn path_segment_certainty( + cx: &LateContext<'_>, + parent_certainty: Certainty, + path_segment: &PathSegment<'_>, + resolves_to_type: bool, +) -> Certainty { + let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) { + // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or + // an unparameterized type), or the generics are instantiated with arguments that are certain. + // + // If the parent is uncertain, then the current path segment must account for the parent's generic arguments. + // Consider the following examples, where the current path segment is `None`: + // - `Option::None` // uncertain; parent (i.e., `Option`) is uncertain + // - `Option::>::None` // certain; parent (i.e., `Option::<..>`) is certain + // - `Option::None::>` // certain; parent (i.e., `Option`) is uncertain + Res::Def(_, def_id) => { + // Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE. + if cx.tcx.res_generics_def_id(path_segment.res).is_some() { + let generics = cx.tcx.generics_of(def_id); + let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && generics.params.is_empty() + { + Certainty::Certain(None) + } else { + Certainty::Uncertain + }; + let rhs = path_segment + .args + .map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args)); + // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value. + let certainty = lhs.join_clearing_def_ids(rhs); + if resolves_to_type { + if cx.tcx.def_kind(def_id) == DefKind::TyAlias { + adt_def_id(cx.tcx.type_of(def_id).instantiate_identity()) + .map_or(certainty, |def_id| certainty.with_def_id(def_id)) + } else { + certainty.with_def_id(def_id) + } + } else { + certainty + } + } else { + Certainty::Certain(None) + } + }, + + Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => { + Certainty::Certain(None) + }, + + // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`. + Res::Local(hir_id) => match cx.tcx.hir().get_parent(hir_id) { + // An argument's type is always certain. + Node::Param(..) => Certainty::Certain(None), + // A local's type is certain if its type annotation is certain or it has an initializer whose + // type is certain. + Node::Local(local) => { + let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)); + let rhs = local + .init + .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init)); + let certainty = lhs.join(rhs); + if resolves_to_type { + certainty + } else { + certainty.clear_def_id() + } + }, + _ => Certainty::Uncertain, + }, + + _ => Certainty::Uncertain, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`. +/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`. +fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option { + if path_segment.res == Res::Err && let Some(def_id) = parent_certainty.to_def_id() { + let mut def_path = cx.get_def_path(def_id); + def_path.push(path_segment.ident.name); + let reses = def_path_res(cx, &def_path.iter().map(Symbol::as_str).collect::>()); + if let [res] = reses.as_slice() { Some(*res) } else { None } + } else { + None + } +} + +#[allow(clippy::cast_possible_truncation)] +fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let Some(callee_def_id) = (match expr.kind { + ExprKind::Call(callee, _) => { + let callee_ty = cx.typeck_results().expr_ty(callee); + if let ty::FnDef(callee_def_id, _) = callee_ty.kind() { + Some(*callee_def_id) + } else { + None + } + }, + ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }) else { + return false; + }; + + let generics = cx.tcx.generics_of(callee_def_id); + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + + // Check that all type parameters appear in the functions input types. + (0..(generics.parent_count + generics.params.len()) as u32).all(|index| { + fn_sig + .inputs() + .iter() + .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) + }) +} + +fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> { + cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0] +} + +fn adt_def_id(ty: Ty<'_>) -> Option { + ty.peel_refs().ty_adt_def().map(AdtDef::did) +} + +fn contains_param(ty: Ty<'_>, index: u32) -> bool { + ty.walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Type(ty) if ty.is_param(index))) +} diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index 20993398d1b78..39ef76348d751 100644 --- a/src/tools/clippy/clippy_utils/src/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -1,14 +1,15 @@ -use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend}; +use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend, Visitable}; +use crate::{self as utils, get_enclosing_loop_or_multi_call_closure}; use core::ops::ControlFlow; +use hir::def::Res; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node}; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty; -use {crate as utils, rustc_hir as hir}; /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option { @@ -127,7 +128,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> { } fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) { - if let hir::def::Res::Local(id) = path.res { + if let Res::Local(id) = path.res { if self.binding_ids.contains(&id) { self.usage_found = true; } @@ -153,6 +154,17 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { .is_some() } +pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool { + for_each_expr_with_closures(cx, v, |e| { + if utils::path_to_local_id(e, local_id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool { let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false; @@ -165,32 +177,21 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr // let closure = || local; // closure(); // closure(); - let in_loop_or_closure = cx - .tcx - .hir() - .parent_iter(after.hir_id) - .take_while(|&(id, _)| id != block.hir_id) - .any(|(_, node)| { - matches!( - node, - Node::Expr(Expr { - kind: ExprKind::Loop(..) | ExprKind::Closure { .. }, - .. - }) - ) - }); - if in_loop_or_closure { - return true; - } + let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id); let mut past_expr = false; for_each_expr_with_closures(cx, block, |e| { - if e.hir_id == after.hir_id { + if past_expr { + if utils::path_to_local_id(e, local_id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + } else if e.hir_id == after.hir_id { past_expr = true; ControlFlow::Continue(Descend::No) - } else if past_expr && utils::path_to_local_id(e, local_id) { - ControlFlow::Break(()) } else { + past_expr = Some(e.hir_id) == loop_start; ControlFlow::Continue(Descend::Yes) } }) diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 8dafa723afa00..09f447b27eb4f 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -52,6 +52,16 @@ pub trait Visitable<'tcx> { /// Calls the corresponding `visit_*` function on the visitor. fn visit>(self, visitor: &mut V); } +impl<'tcx, T> Visitable<'tcx> for &'tcx [T] +where + &'tcx T: Visitable<'tcx>, +{ + fn visit>(self, visitor: &mut V) { + for x in self { + x.visit(visitor); + } + } +} macro_rules! visitable_ref { ($t:ident, $f:ident) => { impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> { diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain index b658101a10d83..833608faff06f 100644 --- a/src/tools/clippy/rust-toolchain +++ b/src/tools/clippy/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-07-14" +channel = "nightly-2023-07-28" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index ee17feed77a00..1d89477dcc16b 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -192,7 +192,7 @@ You can use tool lints to allow or deny lints from your code, eg.: ); } -const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; +const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml"; #[allow(clippy::too_many_lines)] pub fn main() { diff --git a/src/tools/clippy/src/main.rs b/src/tools/clippy/src/main.rs index cdc85cb33ca2b..26b655076cf65 100644 --- a/src/tools/clippy/src/main.rs +++ b/src/tools/clippy/src/main.rs @@ -132,8 +132,7 @@ impl ClippyCmd { let clippy_args: String = self .clippy_args .iter() - .map(|arg| format!("{arg}__CLIPPY_HACKERY__")) - .collect(); + .fold(String::new(), |s, arg| s + arg + "__CLIPPY_HACKERY__"); // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages. let terminal_width = termize::dimensions().map_or(0, |(w, _)| w); diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index f714b2962331d..385e25ce6b8f0 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -5,7 +5,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] #![allow(unused_extern_crates)] -use compiletest::{status_emitter, CommandBuilder}; +use compiletest::{status_emitter, CommandBuilder, OutputConflictHandling}; use ui_test as compiletest; use ui_test::Mode as TestMode; @@ -115,12 +115,12 @@ fn base_config(test_dir: &str) -> compiletest::Config { mode: TestMode::Yolo, stderr_filters: vec![], stdout_filters: vec![], - // FIXME(tgross35): deduplicate bless env once clippy can update output_conflict_handling: if var_os("RUSTC_BLESS").is_some_and(|v| v != "0") - || env::args().any(|arg| arg == "--bless") { - compiletest::OutputConflictHandling::Bless + || env::args().any(|arg| arg == "--bless") + { + OutputConflictHandling::Bless } else { - compiletest::OutputConflictHandling::Error("cargo test -- -- --bless".into()) + OutputConflictHandling::Error("cargo uibless".into()) }, target: None, out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap_or("target".into())).join("ui_test"), @@ -189,9 +189,6 @@ fn run_ui() { .to_string() }), ); - eprintln!(" Compiler: {}", config.program.display()); - - let name = config.root_dir.display().to_string(); let test_filter = test_filter(); @@ -199,7 +196,7 @@ fn run_ui() { config, move |path| compiletest::default_file_filter(path) && test_filter(path), compiletest::default_per_file_config, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); check_rustfix_coverage(); @@ -210,8 +207,19 @@ fn run_internal_tests() { if !RUN_INTERNAL_TESTS { return; } - let config = base_config("ui-internal"); - compiletest::run_tests(config).unwrap(); + let mut config = base_config("ui-internal"); + if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling { + *err = "cargo uitest --features internal -- -- --bless".into(); + } + let test_filter = test_filter(); + + compiletest::run_tests_generic( + config, + move |path| compiletest::default_file_filter(path) && test_filter(path), + compiletest::default_per_file_config, + status_emitter::Text, + ) + .unwrap(); } fn run_ui_toml() { @@ -230,13 +238,11 @@ fn run_ui_toml() { "$$DIR", ); - let name = config.root_dir.display().to_string(); - let test_filter = test_filter(); ui_test::run_tests_generic( config, - |path| test_filter(path) && path.extension() == Some("rs".as_ref()), + |path| compiletest::default_file_filter(path) && test_filter(path), |config, path| { let mut config = config.clone(); config @@ -245,7 +251,7 @@ fn run_ui_toml() { .push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into()))); Some(config) }, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); } @@ -286,8 +292,6 @@ fn run_ui_cargo() { "$$DIR", ); - let name = config.root_dir.display().to_string(); - let test_filter = test_filter(); ui_test::run_tests_generic( @@ -298,7 +302,7 @@ fn run_ui_cargo() { config.out_dir = PathBuf::from("target/ui_test_cargo/").join(path.parent().unwrap()); Some(config) }, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); } diff --git a/src/tools/clippy/tests/integration.rs b/src/tools/clippy/tests/integration.rs index a771d8b87c81a..031982edbe9e1 100644 --- a/src/tools/clippy/tests/integration.rs +++ b/src/tools/clippy/tests/integration.rs @@ -65,6 +65,30 @@ fn integration_test() { .expect("unable to run clippy"); let stderr = String::from_utf8_lossy(&output.stderr); + + // debug: + eprintln!("{stderr}"); + + // this is an internal test to make sure we would correctly panic on a delay_span_bug + if repo_name == "matthiaskrgr/clippy_ci_panic_test" { + // we need to kind of switch around our logic here: + // if we find a panic, everything is fine, if we don't panic, SOMETHING is broken about our testing + + // the repo basically just contains a delay_span_bug that forces rustc/clippy to panic: + /* + #![feature(rustc_attrs)] + #[rustc_error(delay_span_bug_from_inside_query)] + fn main() {} + */ + + if stderr.find("error: internal compiler error").is_some() { + eprintln!("we saw that we intentionally panicked, yay"); + return; + } + + panic!("panic caused by delay_span_bug was NOT detected! Something is broken!"); + } + if let Some(backtrace_start) = stderr.find("error: internal compiler error") { static BACKTRACE_END_MSG: &str = "end of query stack"; let backtrace_end = stderr[backtrace_start..] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/feature_name/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/feature_name/fail/src/main.rs index 4dd9582aff89c..74e40c09ebc83 100644 --- a/src/tools/clippy/tests/ui-cargo/feature_name/fail/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/feature_name/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=feature_name #![warn(clippy::redundant_feature_names)] #![warn(clippy::negative_feature_names)] diff --git a/src/tools/clippy/tests/ui-cargo/feature_name/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/feature_name/pass/src/main.rs index 4dd9582aff89c..74e40c09ebc83 100644 --- a/src/tools/clippy/tests/ui-cargo/feature_name/pass/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/feature_name/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=feature_name #![warn(clippy::redundant_feature_names)] #![warn(clippy::negative_feature_names)] diff --git a/src/tools/clippy/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs b/src/tools/clippy/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs index c70d92e359e44..ac21b3a442298 100644 --- a/src/tools/clippy/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs @@ -1,3 +1,4 @@ +// FIXME: find a way to add rustflags to ui-cargo tests //@compile-flags: --remap-path-prefix {{src-base}}=/remapped #![warn(clippy::self_named_module_files)] diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs index bb3a39d0720a9..3491ccb0d47d5 100644 --- a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=wildcard_dependencies #![warn(clippy::wildcard_dependencies)] fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs index bb3a39d0720a9..3491ccb0d47d5 100644 --- a/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs +++ b/src/tools/clippy/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=wildcard_dependencies #![warn(clippy::wildcard_dependencies)] fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr index b88aeae2a9b8e..31df0ebd9fd6b 100644 --- a/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr +++ b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr @@ -3,7 +3,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: the compiler unexpectedly panicked. this is a bug. -note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new +note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml note: rustc running on diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr new file mode 100644 index 0000000000000..a8900da4e6eb4 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr @@ -0,0 +1,28 @@ +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:40:5 + | +LL | std::f32::MAX; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::absolute-paths` implied by `-D warnings` + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:41:5 + | +LL | core::f32::MAX; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:42:5 + | +LL | ::core::f32::MAX; + | ^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:58:5 + | +LL | ::std::f32::MAX; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr new file mode 100644 index 0000000000000..41b70644be8c3 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr @@ -0,0 +1,70 @@ +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:40:5 + | +LL | std::f32::MAX; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::absolute-paths` implied by `-D warnings` + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:41:5 + | +LL | core::f32::MAX; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:42:5 + | +LL | ::core::f32::MAX; + | ^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:43:5 + | +LL | crate::a::b::c::C; + | ^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:44:5 + | +LL | crate::a::b::c::d::e::f::F; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:45:5 + | +LL | crate::a::A; + | ^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:46:5 + | +LL | crate::a::b::B; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:47:5 + | +LL | crate::a::b::c::C::ZERO; + | ^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:48:5 + | +LL | helper::b::c::d::e::f(); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:49:5 + | +LL | ::helper::b::c::d::e::f(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:58:5 + | +LL | ::std::f32::MAX; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.rs b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.rs new file mode 100644 index 0000000000000..d4c250a8ff231 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/absolute_paths.rs @@ -0,0 +1,97 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs:proc-macro +//@aux-build:helper.rs +//@revisions: allow_crates disallow_crates +//@[allow_crates] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/absolute_paths/allow_crates +//@[disallow_crates] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/absolute_paths/disallow_crates +#![allow(clippy::no_effect, unused)] +#![warn(clippy::absolute_paths)] +#![feature(decl_macro)] + +extern crate helper; +#[macro_use] +extern crate proc_macros; + +pub mod a { + pub mod b { + pub mod c { + pub struct C; + + impl C { + pub const ZERO: u32 = 0; + } + + pub mod d { + pub mod e { + pub mod f { + pub struct F; + } + } + } + } + + pub struct B; + } + + pub struct A; +} + +fn main() { + f32::max(1.0, 2.0); + std::f32::MAX; + core::f32::MAX; + ::core::f32::MAX; + crate::a::b::c::C; + crate::a::b::c::d::e::f::F; + crate::a::A; + crate::a::b::B; + crate::a::b::c::C::ZERO; + helper::b::c::d::e::f(); + ::helper::b::c::d::e::f(); + fn b() -> a::b::B { + todo!() + } + std::println!("a"); + let x = 1; + std::ptr::addr_of!(x); + // Test we handle max segments with `PathRoot` properly; this has 4 segments but we should say it + // has 3 + ::std::f32::MAX; + // Do not lint due to the above + ::helper::a(); + // Do not lint + helper::a(); + use crate::a::b::c::C; + use a::b; + use std::f32::MAX; + a::b::c::d::e::f::F; + b::c::C; + fn a() -> a::A { + todo!() + } + use a::b::c; + + fn c() -> c::C { + todo!() + } + fn d() -> Result<(), ()> { + todo!() + } + external! { + crate::a::b::c::C::ZERO; + } + // For some reason, `path.span.from_expansion()` takes care of this for us + with_span! { + span + crate::a::b::c::C::ZERO; + } + macro_rules! local_crate { + () => { + crate::a::b::c::C::ZERO; + }; + } + macro local_crate_2_0() { + crate::a::b::c::C::ZERO; + } + local_crate!(); + local_crate_2_0!(); +} diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/allow_crates/clippy.toml b/src/tools/clippy/tests/ui-toml/absolute_paths/allow_crates/clippy.toml new file mode 100644 index 0000000000000..59a621e9d1dba --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/allow_crates/clippy.toml @@ -0,0 +1,2 @@ +absolute-paths-max-segments = 2 +absolute-paths-allowed-crates = ["crate", "helper"] diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/auxiliary/helper.rs b/src/tools/clippy/tests/ui-toml/absolute_paths/auxiliary/helper.rs new file mode 100644 index 0000000000000..8e2678f5fe697 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/auxiliary/helper.rs @@ -0,0 +1,11 @@ +pub fn a() {} + +pub mod b { + pub mod c { + pub mod d { + pub mod e { + pub fn f() {} + } + } + } +} diff --git a/src/tools/clippy/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml b/src/tools/clippy/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml new file mode 100644 index 0000000000000..d44d648c64118 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml @@ -0,0 +1 @@ +absolute-paths-max-segments = 2 diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6ba26e9773023..cdabe6460cdca 100644 --- a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -1,4 +1,6 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expected one of + absolute-paths-allowed-crates + absolute-paths-max-segments accept-comment-above-attributes accept-comment-above-statement allow-dbg-in-tests @@ -68,6 +70,8 @@ LL | foobar = 42 | ^^^^^^ error: error reading Clippy's configuration file: unknown field `barfoo`, expected one of + absolute-paths-allowed-crates + absolute-paths-max-segments accept-comment-above-attributes accept-comment-above-statement allow-dbg-in-tests diff --git a/src/tools/clippy/tests/ui/arc_with_non_send_sync.rs b/src/tools/clippy/tests/ui/arc_with_non_send_sync.rs index b6fcca0a7919e..2940c27325526 100644 --- a/src/tools/clippy/tests/ui/arc_with_non_send_sync.rs +++ b/src/tools/clippy/tests/ui/arc_with_non_send_sync.rs @@ -1,6 +1,12 @@ +//@aux-build:proc_macros.rs:proc-macro #![warn(clippy::arc_with_non_send_sync)] #![allow(unused_variables)] + +#[macro_use] +extern crate proc_macros; + use std::cell::RefCell; +use std::ptr::{null, null_mut}; use std::sync::{Arc, Mutex}; fn foo(x: T) { @@ -11,14 +17,32 @@ fn issue11076() { let a: Arc> = Arc::new(Vec::new()); } +fn issue11232() { + external! { + let a: Arc<*const u8> = Arc::new(null()); + let a: Arc<*mut u8> = Arc::new(null_mut()); + } + with_span! { + span + let a: Arc<*const u8> = Arc::new(null()); + let a: Arc<*mut u8> = Arc::new(null_mut()); + } +} + fn main() { let _ = Arc::new(42); - // !Sync let _ = Arc::new(RefCell::new(42)); + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Sync` is not implemented for `RefCell` + let mutex = Mutex::new(1); - // !Send let _ = Arc::new(mutex.lock().unwrap()); - // !Send + !Sync + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Send` is not implemented for `MutexGuard<'_, i32>` + let _ = Arc::new(&42 as *const i32); + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Send` is not implemented for `*const i32` + //~| NOTE: the trait `Sync` is not implemented for `*const i32` } diff --git a/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr b/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr index 7633b38dfb597..de3f2fb9e16e6 100644 --- a/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr +++ b/src/tools/clippy/tests/ui/arc_with_non_send_sync.stderr @@ -1,5 +1,5 @@ error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:18:13 + --> $DIR/arc_with_non_send_sync.rs:35:13 | LL | let _ = Arc::new(RefCell::new(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | let _ = Arc::new(RefCell::new(42)); = note: `-D clippy::arc-with-non-send-sync` implied by `-D warnings` error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:21:13 + --> $DIR/arc_with_non_send_sync.rs:40:13 | LL | let _ = Arc::new(mutex.lock().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | let _ = Arc::new(mutex.lock().unwrap()); = help: consider using an `Rc` instead or wrapping the inner type with a `Mutex` error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:23:13 + --> $DIR/arc_with_non_send_sync.rs:44:13 | LL | let _ = Arc::new(&42 as *const i32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs index ed75acee8a287..2ac2fa22086b8 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs @@ -486,4 +486,11 @@ pub fn issue_11145() { x += 1; } +pub fn issue_11262() { + let one = 1; + let zero = 0; + let _ = 2 / one; + let _ = 2 / zero; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.fixed b/src/tools/clippy/tests/ui/comparison_to_empty.fixed index c92dd509ebb2d..af219eed0b845 100644 --- a/src/tools/clippy/tests/ui/comparison_to_empty.fixed +++ b/src/tools/clippy/tests/ui/comparison_to_empty.fixed @@ -1,7 +1,8 @@ //@run-rustfix #![warn(clippy::comparison_to_empty)] -#![allow(clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![feature(let_chains)] fn main() { // Disallow comparisons to empty @@ -12,6 +13,11 @@ fn main() { let v = vec![0]; let _ = v.is_empty(); let _ = !v.is_empty(); + if (*v).is_empty() {} + let s = [0].as_slice(); + if s.is_empty() {} + if s.is_empty() {} + if s.is_empty() && s.is_empty() {} // Allow comparisons to non-empty let s = String::new(); @@ -21,4 +27,8 @@ fn main() { let v = vec![0]; let _ = v == [0]; let _ = v != [0]; + if let [0] = &*v {} + let s = [0].as_slice(); + if let [0] = s {} + if let [0] = &*s && s == [0] {} } diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.rs b/src/tools/clippy/tests/ui/comparison_to_empty.rs index b3489714380aa..21e65184d50d4 100644 --- a/src/tools/clippy/tests/ui/comparison_to_empty.rs +++ b/src/tools/clippy/tests/ui/comparison_to_empty.rs @@ -1,7 +1,8 @@ //@run-rustfix #![warn(clippy::comparison_to_empty)] -#![allow(clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![feature(let_chains)] fn main() { // Disallow comparisons to empty @@ -12,6 +13,11 @@ fn main() { let v = vec![0]; let _ = v == []; let _ = v != []; + if let [] = &*v {} + let s = [0].as_slice(); + if let [] = s {} + if let [] = &*s {} + if let [] = &*s && s == [] {} // Allow comparisons to non-empty let s = String::new(); @@ -21,4 +27,8 @@ fn main() { let v = vec![0]; let _ = v == [0]; let _ = v != [0]; + if let [0] = &*v {} + let s = [0].as_slice(); + if let [0] = s {} + if let [0] = &*s && s == [0] {} } diff --git a/src/tools/clippy/tests/ui/comparison_to_empty.stderr b/src/tools/clippy/tests/ui/comparison_to_empty.stderr index cc09b17eb8951..f29782ed80d1f 100644 --- a/src/tools/clippy/tests/ui/comparison_to_empty.stderr +++ b/src/tools/clippy/tests/ui/comparison_to_empty.stderr @@ -1,5 +1,5 @@ error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:9:13 + --> $DIR/comparison_to_empty.rs:10:13 | LL | let _ = s == ""; | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` @@ -7,22 +7,52 @@ LL | let _ = s == ""; = note: `-D clippy::comparison-to-empty` implied by `-D warnings` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:10:13 + --> $DIR/comparison_to_empty.rs:11:13 | LL | let _ = s != ""; | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:13:13 + --> $DIR/comparison_to_empty.rs:14:13 | LL | let _ = v == []; | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:14:13 + --> $DIR/comparison_to_empty.rs:15:13 | LL | let _ = v != []; | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()` -error: aborting due to 4 previous errors +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:16:8 + | +LL | if let [] = &*v {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(*v).is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:18:8 + | +LL | if let [] = s {} + | ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:19:8 + | +LL | if let [] = &*s {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:20:8 + | +LL | if let [] = &*s && s == [] {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:20:24 + | +LL | if let [] = &*s && s == [] {} + | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/error_impl_error.rs b/src/tools/clippy/tests/ui/error_impl_error.rs new file mode 100644 index 0000000000000..40ce4181bf34a --- /dev/null +++ b/src/tools/clippy/tests/ui/error_impl_error.rs @@ -0,0 +1,90 @@ +#![allow(unused)] +#![warn(clippy::error_impl_error)] +#![no_main] + +pub mod a { + #[derive(Debug)] + pub struct Error; + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +mod b { + #[derive(Debug)] + pub(super) enum Error {} + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +pub mod c { + pub union Error { + a: u32, + b: u32, + } + + impl std::fmt::Debug for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +pub mod d { + pub type Error = std::fmt::Error; +} + +mod e { + #[derive(Debug)] + pub(super) struct MyError; + + impl std::fmt::Display for MyError { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for MyError {} +} + +pub mod f { + pub type MyError = std::fmt::Error; +} + +// Do not lint module-private types + +mod g { + #[derive(Debug)] + enum Error {} + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +mod h { + type Error = std::fmt::Error; +} diff --git a/src/tools/clippy/tests/ui/error_impl_error.stderr b/src/tools/clippy/tests/ui/error_impl_error.stderr new file mode 100644 index 0000000000000..f3e04b6416731 --- /dev/null +++ b/src/tools/clippy/tests/ui/error_impl_error.stderr @@ -0,0 +1,45 @@ +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:7:16 + | +LL | pub struct Error; + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:15:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::error-impl-error` implied by `-D warnings` + +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:20:21 + | +LL | pub(super) enum Error {} + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:28:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:32:15 + | +LL | pub union Error { + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:49:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: exported type alias named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:53:14 + | +LL | pub type Error = std::fmt::Error; + | ^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed index db7bd99e0ae6c..ddabe7616d093 100644 --- a/src/tools/clippy/tests/ui/eta.fixed +++ b/src/tools/clippy/tests/ui/eta.fixed @@ -345,3 +345,58 @@ fn angle_brackets_and_args() { let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct); dyn_opt.map(::method_on_dyn); } + +fn _late_bound_to_early_bound_regions() { + struct Foo<'a>(&'a u32); + impl<'a> Foo<'a> { + fn f(x: &'a u32) -> Self { + Foo(x) + } + } + fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> { + f(&0) + } + + let _ = f(|x| Foo::f(x)); + + struct Bar; + impl<'a> From<&'a u32> for Bar { + fn from(x: &'a u32) -> Bar { + Bar + } + } + fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar { + f(&0) + } + + let _ = f2(|x| ::from(x)); + + struct Baz<'a>(&'a u32); + fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> { + f(&0) + } + + let _ = f3(|x| Baz(x)); +} + +fn _mixed_late_bound_and_early_bound_regions() { + fn f(t: T, f: impl Fn(T, &u32) -> u32) -> u32 { + f(t, &0) + } + fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 { + *y + } + let _ = f(&0, f2); +} + +fn _closure_with_types() { + fn f(x: T) -> T { + x + } + fn f2(f: impl Fn(T) -> T) -> T { + f(T::default()) + } + + let _ = f2(|x: u32| f(x)); + let _ = f2(|x| -> u32 { f(x) }); +} diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs index 52fc17686fdfc..92ecff6eb1afb 100644 --- a/src/tools/clippy/tests/ui/eta.rs +++ b/src/tools/clippy/tests/ui/eta.rs @@ -345,3 +345,58 @@ fn angle_brackets_and_args() { let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct); dyn_opt.map(|d| d.method_on_dyn()); } + +fn _late_bound_to_early_bound_regions() { + struct Foo<'a>(&'a u32); + impl<'a> Foo<'a> { + fn f(x: &'a u32) -> Self { + Foo(x) + } + } + fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> { + f(&0) + } + + let _ = f(|x| Foo::f(x)); + + struct Bar; + impl<'a> From<&'a u32> for Bar { + fn from(x: &'a u32) -> Bar { + Bar + } + } + fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar { + f(&0) + } + + let _ = f2(|x| ::from(x)); + + struct Baz<'a>(&'a u32); + fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> { + f(&0) + } + + let _ = f3(|x| Baz(x)); +} + +fn _mixed_late_bound_and_early_bound_regions() { + fn f(t: T, f: impl Fn(T, &u32) -> u32) -> u32 { + f(t, &0) + } + fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 { + *y + } + let _ = f(&0, |x, y| f2(x, y)); +} + +fn _closure_with_types() { + fn f(x: T) -> T { + x + } + fn f2(f: impl Fn(T) -> T) -> T { + f(T::default()) + } + + let _ = f2(|x: u32| f(x)); + let _ = f2(|x| -> u32 { f(x) }); +} diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr index 0ac0b901df446..ff40a2074e561 100644 --- a/src/tools/clippy/tests/ui/eta.stderr +++ b/src/tools/clippy/tests/ui/eta.stderr @@ -158,5 +158,11 @@ error: redundant closure LL | dyn_opt.map(|d| d.method_on_dyn()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `::method_on_dyn` -error: aborting due to 26 previous errors +error: redundant closure + --> $DIR/eta.rs:389:19 + | +LL | let _ = f(&0, |x, y| f2(x, y)); + | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` + +error: aborting due to 27 previous errors diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.fixed b/src/tools/clippy/tests/ui/filter_map_bool_then.fixed new file mode 100644 index 0000000000000..3e72fee4b072a --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.fixed @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::clone_on_copy, + clippy::map_identity, + clippy::unnecessary_lazy_evaluations, + unused +)] +#![warn(clippy::filter_map_bool_then)] + +#[macro_use] +extern crate proc_macros; + +#[derive(Clone, PartialEq)] +struct NonCopy; + +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().iter().filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone().into_iter().filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone() + .into_iter() + .filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone() + .into_iter() + .filter(|&i| i != 1000) + .filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.iter() + .copied() + .filter(|&i| i != 1000) + .filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1); + // Do not lint + let v = vec![NonCopy, NonCopy]; + v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + external! { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } + with_span! { + span + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } +} diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.rs b/src/tools/clippy/tests/ui/filter_map_bool_then.rs new file mode 100644 index 0000000000000..38a04e57de456 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.rs @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::clone_on_copy, + clippy::map_identity, + clippy::unnecessary_lazy_evaluations, + unused +)] +#![warn(clippy::filter_map_bool_then)] + +#[macro_use] +extern crate proc_macros; + +#[derive(Clone, PartialEq)] +struct NonCopy; + +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.clone() + .into_iter() + .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) }); + v.clone() + .into_iter() + .filter(|&i| i != 1000) + .filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.iter() + .copied() + .filter(|&i| i != 1000) + .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); + // Do not lint + let v = vec![NonCopy, NonCopy]; + v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + external! { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } + with_span! { + span + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } +} diff --git a/src/tools/clippy/tests/ui/filter_map_bool_then.stderr b/src/tools/clippy/tests/ui/filter_map_bool_then.stderr new file mode 100644 index 0000000000000..b411cd83dfd29 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_bool_then.stderr @@ -0,0 +1,34 @@ +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:19:22 + | +LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + | + = note: `-D clippy::filter-map-bool-then` implied by `-D warnings` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:20:27 + | +LL | v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:23:10 + | +LL | .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:27:10 + | +LL | .filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:31:10 + | +LL | .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/format_collect.rs b/src/tools/clippy/tests/ui/format_collect.rs new file mode 100644 index 0000000000000..c7f2b7b695074 --- /dev/null +++ b/src/tools/clippy/tests/ui/format_collect.rs @@ -0,0 +1,31 @@ +#![allow(unused, dead_code)] +#![warn(clippy::format_collect)] + +fn hex_encode(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02X}")).collect() +} + +#[rustfmt::skip] +fn hex_encode_deep(bytes: &[u8]) -> String { + bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() +} + +macro_rules! fmt { + ($x:ident) => { + format!("{x:02X}", x = $x) + }; +} + +fn from_macro(bytes: &[u8]) -> String { + bytes.iter().map(|x| fmt!(x)).collect() +} + +fn with_block() -> String { + (1..10) + .map(|s| { + let y = 1; + format!("{s} {y}") + }) + .collect() +} +fn main() {} diff --git a/src/tools/clippy/tests/ui/format_collect.stderr b/src/tools/clippy/tests/ui/format_collect.stderr new file mode 100644 index 0000000000000..d918f1ed466b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/format_collect.stderr @@ -0,0 +1,62 @@ +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:5:5 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:5:18 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:5:26 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + = note: `-D clippy::format-collect` implied by `-D warnings` + +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:10:5 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:10:18 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:10:32 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:24:5 + | +LL | / (1..10) +LL | | .map(|s| { +LL | | let y = 1; +LL | | format!("{s} {y}") +LL | | }) +LL | | .collect() + | |__________________^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:25:10 + | +LL | .map(|s| { + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:27:13 + | +LL | format!("{s} {y}") + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/four_forward_slashes.fixed b/src/tools/clippy/tests/ui/four_forward_slashes.fixed new file mode 100644 index 0000000000000..54b2c414b6208 --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes.fixed @@ -0,0 +1,48 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +/// whoops +fn a() {} + +/// whoops +#[allow(dead_code)] +fn b() {} + +/// whoops +/// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +/// between attributes +#[allow(dead_code)] +fn g() {} + +/// not very start of contents +fn h() {} + +fn i() { + //// don't lint me bozo + todo!() +} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/src/tools/clippy/tests/ui/four_forward_slashes.rs b/src/tools/clippy/tests/ui/four_forward_slashes.rs new file mode 100644 index 0000000000000..facdc8cb17dba --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes.rs @@ -0,0 +1,48 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +//// whoops +fn a() {} + +//// whoops +#[allow(dead_code)] +fn b() {} + +//// whoops +//// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +//// between attributes +#[allow(dead_code)] +fn g() {} + + //// not very start of contents +fn h() {} + +fn i() { + //// don't lint me bozo + todo!() +} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/src/tools/clippy/tests/ui/four_forward_slashes.stderr b/src/tools/clippy/tests/ui/four_forward_slashes.stderr new file mode 100644 index 0000000000000..89162e6b010e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes.stderr @@ -0,0 +1,68 @@ +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:12:1 + | +LL | / //// whoops +LL | | fn a() {} + | |_ + | + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:15:1 + | +LL | / //// whoops +LL | | #[allow(dead_code)] +LL | | fn b() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:19:1 + | +LL | / //// whoops +LL | | //// two borked comments! +LL | | #[track_caller] +LL | | fn c() {} + | |_ + | +help: turn these into doc comments by removing one `/` + | +LL + /// whoops +LL ~ /// two borked comments! + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:27:1 + | +LL | / //// between attributes +LL | | #[allow(dead_code)] +LL | | fn g() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// between attributes + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:31:1 + | +LL | / //// not very start of contents +LL | | fn h() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// not very start of contents + | + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/four_forward_slashes_first_line.fixed b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.fixed new file mode 100644 index 0000000000000..ce272b4c6cd78 --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.fixed @@ -0,0 +1,7 @@ +/// borked doc comment on the first line. doesn't combust! +fn a() {} + +//@run-rustfix +// This test's entire purpose is to make sure we don't panic if the comment with four slashes +// extends to the first line of the file. This is likely pretty rare in production, but an ICE is an +// ICE. diff --git a/src/tools/clippy/tests/ui/four_forward_slashes_first_line.rs b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.rs new file mode 100644 index 0000000000000..d8f82d4410b8a --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.rs @@ -0,0 +1,7 @@ +//// borked doc comment on the first line. doesn't combust! +fn a() {} + +//@run-rustfix +// This test's entire purpose is to make sure we don't panic if the comment with four slashes +// extends to the first line of the file. This is likely pretty rare in production, but an ICE is an +// ICE. diff --git a/src/tools/clippy/tests/ui/four_forward_slashes_first_line.stderr b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.stderr new file mode 100644 index 0000000000000..7944da14feb0e --- /dev/null +++ b/src/tools/clippy/tests/ui/four_forward_slashes_first_line.stderr @@ -0,0 +1,15 @@ +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes_first_line.rs:1:1 + | +LL | / //// borked doc comment on the first line. doesn't combust! +LL | | fn a() {} + | |_ + | + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` +help: make this a doc comment by removing one `/` + | +LL + /// borked doc comment on the first line. doesn't combust! + | + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/if_same_then_else.rs b/src/tools/clippy/tests/ui/if_same_then_else.rs index dad4543f84c09..e84b20e9fef07 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else.rs +++ b/src/tools/clippy/tests/ui/if_same_then_else.rs @@ -214,4 +214,45 @@ mod issue_8836 { } } +mod issue_11213 { + fn reproducer(x: bool) -> bool { + if x { + 0_u8.is_power_of_two() + } else { + 0_u16.is_power_of_two() + } + } + + // a more obvious reproducer that shows + // why the code above is problematic: + fn v2(x: bool) -> bool { + trait Helper { + fn is_u8(&self) -> bool; + } + impl Helper for u8 { + fn is_u8(&self) -> bool { + true + } + } + impl Helper for u16 { + fn is_u8(&self) -> bool { + false + } + } + + // this is certainly not the same code in both branches + // it returns a different bool depending on the branch. + if x { 0_u8.is_u8() } else { 0_u16.is_u8() } + } + + fn do_lint(x: bool) -> bool { + // but do lint if the type of the literal is the same + if x { + 0_u8.is_power_of_two() + } else { + 0_u8.is_power_of_two() + } + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else.stderr b/src/tools/clippy/tests/ui/if_same_then_else.stderr index a34fc565590b0..774cc08685a7f 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else.stderr +++ b/src/tools/clippy/tests/ui/if_same_then_else.stderr @@ -108,5 +108,23 @@ LL | | bar + 1; LL | | } | |_____^ -error: aborting due to 5 previous errors +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:250:14 + | +LL | if x { + | ______________^ +LL | | 0_u8.is_power_of_two() +LL | | } else { + | |_________^ + | +note: same as this + --> $DIR/if_same_then_else.rs:252:16 + | +LL | } else { + | ________________^ +LL | | 0_u8.is_power_of_two() +LL | | } + | |_________^ + +error: aborting due to 6 previous errors diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.rs b/src/tools/clippy/tests/ui/ifs_same_cond.rs index 5c338e3c5c8f2..ad77346b75f24 100644 --- a/src/tools/clippy/tests/ui/ifs_same_cond.rs +++ b/src/tools/clippy/tests/ui/ifs_same_cond.rs @@ -46,6 +46,10 @@ fn ifs_same_cond() { // ok, functions } else if v.len() == 42 { } + + if let Some(env1) = option_env!("ENV1") { + } else if let Some(env2) = option_env!("ENV2") { + } } fn issue10272() { diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.stderr b/src/tools/clippy/tests/ui/ifs_same_cond.stderr index 8d70934476cbc..3f52c10b7620a 100644 --- a/src/tools/clippy/tests/ui/ifs_same_cond.stderr +++ b/src/tools/clippy/tests/ui/ifs_same_cond.stderr @@ -36,13 +36,13 @@ LL | if 2 * a == 1 { | ^^^^^^^^^^ error: this `if` has the same condition as a previous `if` - --> $DIR/ifs_same_cond.rs:54:15 + --> $DIR/ifs_same_cond.rs:58:15 | LL | } else if a.contains("ah") { | ^^^^^^^^^^^^^^^^ | note: same as this - --> $DIR/ifs_same_cond.rs:53:8 + --> $DIR/ifs_same_cond.rs:57:8 | LL | if a.contains("ah") { | ^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed index dd4fdd98822c7..2f51bf274804f 100644 --- a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed +++ b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed @@ -1,5 +1,4 @@ //@run-rustfix -#![allow(unused)] #![no_main] use std::cmp::Ordering; @@ -112,3 +111,35 @@ impl PartialOrd for F { todo!(); } } + +// #11178, do not lint + +#[derive(Eq, PartialEq)] +struct G(u32); + +impl Ord for G { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for G { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Self::cmp(self, other)) + } +} + +#[derive(Eq, PartialEq)] +struct H(u32); + +impl Ord for H { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for H { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} diff --git a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs index 522e82299c0a5..47127bdaec229 100644 --- a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs +++ b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs @@ -1,5 +1,4 @@ //@run-rustfix -#![allow(unused)] #![no_main] use std::cmp::Ordering; @@ -116,3 +115,35 @@ impl PartialOrd for F { todo!(); } } + +// #11178, do not lint + +#[derive(Eq, PartialEq)] +struct G(u32); + +impl Ord for G { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for G { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Self::cmp(self, other)) + } +} + +#[derive(Eq, PartialEq)] +struct H(u32); + +impl Ord for H { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for H { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} diff --git a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr index 0e477798c406e..66048fc90005b 100644 --- a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr +++ b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr @@ -1,5 +1,5 @@ error: incorrect implementation of `partial_cmp` on an `Ord` type - --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:18:1 + --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:17:1 | LL | / impl PartialOrd for A { LL | | fn partial_cmp(&self, other: &Self) -> Option { @@ -13,7 +13,7 @@ LL | | } = note: `#[deny(clippy::incorrect_partial_ord_impl_on_ord_type)]` on by default error: incorrect implementation of `partial_cmp` on an `Ord` type - --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:52:1 + --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:51:1 | LL | / impl PartialOrd for C { LL | | fn partial_cmp(&self, _: &Self) -> Option { diff --git a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs new file mode 100644 index 0000000000000..3a3b84f93c462 --- /dev/null +++ b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs @@ -0,0 +1,51 @@ +// This test's filename is... a bit verbose. But it ensures we suggest the correct code when `Ord` +// is not in scope. +#![no_main] +#![no_implicit_prelude] + +extern crate std; + +use std::cmp::{self, Eq, Ordering, PartialEq, PartialOrd}; +use std::option::Option::{self, Some}; +use std::todo; + +// lint + +#[derive(Eq, PartialEq)] +struct A(u32); + +impl cmp::Ord for A { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for A { + fn partial_cmp(&self, other: &Self) -> Option { + // NOTE: This suggestion is wrong, as `Ord` is not in scope. But this should be fine as it isn't + // automatically applied + todo!(); + } +} + +#[derive(Eq, PartialEq)] +struct B(u32); + +impl B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl cmp::Ord for B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for B { + fn partial_cmp(&self, other: &Self) -> Option { + // This calls `B.cmp`, not `Ord::cmp`! + Some(self.cmp(other)) + } +} diff --git a/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr new file mode 100644 index 0000000000000..f4374c2812877 --- /dev/null +++ b/src/tools/clippy/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr @@ -0,0 +1,31 @@ +error: incorrect implementation of `partial_cmp` on an `Ord` type + --> $DIR/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs:23:1 + | +LL | / impl PartialOrd for A { +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________- +LL | || // NOTE: This suggestion is wrong, as `Ord` is not in scope. But this should be fine as it isn't +LL | || // automatically applied +LL | || todo!(); +LL | || } + | ||_____- help: change this to: `{ Some(self.cmp(other)) }` +LL | | } + | |__^ + | + = note: `#[deny(clippy::incorrect_partial_ord_impl_on_ord_type)]` on by default + +error: incorrect implementation of `partial_cmp` on an `Ord` type + --> $DIR/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs:46:1 + | +LL | / impl PartialOrd for B { +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________- +LL | || // This calls `B.cmp`, not `Ord::cmp`! +LL | || Some(self.cmp(other)) +LL | || } + | ||_____- help: change this to: `{ Some(std::cmp::Ord::cmp(self, other)) }` +LL | | } + | |__^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/infinite_loop.stderr b/src/tools/clippy/tests/ui/infinite_loop.stderr index 701b3cd190410..04559f9ada431 100644 --- a/src/tools/clippy/tests/ui/infinite_loop.stderr +++ b/src/tools/clippy/tests/ui/infinite_loop.stderr @@ -1,11 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/infinite_loop.rs:7:17 - | -LL | fn fn_mutref(i: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: variables in the condition are not mutated in the loop body --> $DIR/infinite_loop.rs:20:11 | @@ -99,5 +91,13 @@ LL | while y < 10 { = note: this loop contains `return`s or `break`s = help: rewrite it as `if cond { loop { } }` +error: this argument is a mutable reference, but not used mutably + --> $DIR/infinite_loop.rs:7:17 + | +LL | fn fn_mutref(i: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/inherent_to_string.rs b/src/tools/clippy/tests/ui/inherent_to_string.rs index aeb0a0c1e2e84..adb0389a043f8 100644 --- a/src/tools/clippy/tests/ui/inherent_to_string.rs +++ b/src/tools/clippy/tests/ui/inherent_to_string.rs @@ -1,5 +1,4 @@ -#![warn(clippy::inherent_to_string)] -#![deny(clippy::inherent_to_string_shadow_display)] +#![allow(improper_ctypes_definitions)] use std::fmt; @@ -14,6 +13,9 @@ struct D; struct E; struct F; struct G; +struct H; +struct I; +struct J; impl A { // Should be detected; emit warning @@ -80,6 +82,26 @@ impl G { } } +// Issue #11201 + +impl H { + unsafe fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + +impl I { + extern "C" fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + +impl J { + unsafe extern "C" fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + fn main() { let a = A; a.to_string(); diff --git a/src/tools/clippy/tests/ui/inherent_to_string.stderr b/src/tools/clippy/tests/ui/inherent_to_string.stderr index 443fecae1aadf..579b3c8c56f7c 100644 --- a/src/tools/clippy/tests/ui/inherent_to_string.stderr +++ b/src/tools/clippy/tests/ui/inherent_to_string.stderr @@ -1,5 +1,5 @@ error: implementation of inherent method `to_string(&self) -> String` for type `A` - --> $DIR/inherent_to_string.rs:20:5 + --> $DIR/inherent_to_string.rs:22:5 | LL | / fn to_string(&self) -> String { LL | | "A.to_string()".to_string() @@ -10,7 +10,7 @@ LL | | } = note: `-D clippy::inherent-to-string` implied by `-D warnings` error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display` - --> $DIR/inherent_to_string.rs:44:5 + --> $DIR/inherent_to_string.rs:46:5 | LL | / fn to_string(&self) -> String { LL | | "C.to_string()".to_string() @@ -18,11 +18,7 @@ LL | | } | |_____^ | = help: remove the inherent method from type `C` -note: the lint level is defined here - --> $DIR/inherent_to_string.rs:2:9 - | -LL | #![deny(clippy::inherent_to_string_shadow_display)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::inherent_to_string_shadow_display)]` on by default error: aborting due to 2 previous errors diff --git a/src/tools/clippy/tests/ui/iter_skip_zero.fixed b/src/tools/clippy/tests/ui/iter_skip_zero.fixed new file mode 100644 index 0000000000000..1eb0984fe073f --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_zero.fixed @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + let _ = once([1, 2, 3]).skip(1); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(1)).skip(1); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/src/tools/clippy/tests/ui/iter_skip_zero.rs b/src/tools/clippy/tests/ui/iter_skip_zero.rs new file mode 100644 index 0000000000000..8c103ab1d5b32 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_zero.rs @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(0); + let _ = vec![1, 2, 3].iter().skip(0); + let _ = once([1, 2, 3]).skip(0); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/src/tools/clippy/tests/ui/iter_skip_zero.stderr b/src/tools/clippy/tests/ui/iter_skip_zero.stderr new file mode 100644 index 0000000000000..80fecd59e6d10 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_zero.stderr @@ -0,0 +1,43 @@ +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:12:35 + | +LL | let _ = [1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + = note: `-D clippy::iter-skip-zero` implied by `-D warnings` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:13:39 + | +LL | let _ = vec![1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:14:34 + | +LL | let _ = once([1, 2, 3]).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:71 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:62 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/let_and_return.rs b/src/tools/clippy/tests/ui/let_and_return.rs index 7e4d783a026c3..64665cc906f61 100644 --- a/src/tools/clippy/tests/ui/let_and_return.rs +++ b/src/tools/clippy/tests/ui/let_and_return.rs @@ -169,4 +169,14 @@ mod issue_5729 { } } +// https://github.com/rust-lang/rust-clippy/issues/11167 +macro_rules! fn_in_macro { + ($b:block) => { + fn f() -> usize $b + } +} +fn_in_macro!({ + return 1; +}); + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_underscore_future.stderr b/src/tools/clippy/tests/ui/let_underscore_future.stderr index 9e69fb0413309..ff1e2b8c90195 100644 --- a/src/tools/clippy/tests/ui/let_underscore_future.stderr +++ b/src/tools/clippy/tests/ui/let_underscore_future.stderr @@ -1,11 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/let_underscore_future.rs:11:35 - | -LL | fn do_something_to_future(future: &mut impl Future) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&impl Future` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: non-binding `let` on a future --> $DIR/let_underscore_future.rs:14:5 | @@ -31,5 +23,13 @@ LL | let _ = future; | = help: consider awaiting the future or dropping explicitly with `std::mem::drop` +error: this argument is a mutable reference, but not used mutably + --> $DIR/let_underscore_future.rs:11:35 + | +LL | fn do_something_to_future(future: &mut impl Future) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&impl Future` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/manual_filter_map.fixed b/src/tools/clippy/tests/ui/manual_filter_map.fixed index 9dd376df2b48a..35872a39a7117 100644 --- a/src/tools/clippy/tests/ui/manual_filter_map.fixed +++ b/src/tools/clippy/tests/ui/manual_filter_map.fixed @@ -120,3 +120,27 @@ fn issue_8920() { .iter() .filter_map(|f| f.result_field.to_owned().ok()); } + +fn issue8010() { + #[derive(Clone)] + enum Enum { + A(i32), + B, + } + + let iter = [Enum::A(123), Enum::B].into_iter(); + + let _x = iter.clone().filter_map(|x| match x { Enum::A(s) => Some(s), _ => None }); + let _x = iter.clone().filter(|x| matches!(x, Enum::B)).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter + .clone() + .filter_map(|x| match x { Enum::A(s) => Some(s), _ => None }); + #[allow(clippy::unused_unit)] + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::B)) + .map(|x| if let Enum::B = x { () } else { unreachable!() }); +} diff --git a/src/tools/clippy/tests/ui/manual_filter_map.rs b/src/tools/clippy/tests/ui/manual_filter_map.rs index 6dd1e066aebdb..50d8d2722c238 100644 --- a/src/tools/clippy/tests/ui/manual_filter_map.rs +++ b/src/tools/clippy/tests/ui/manual_filter_map.rs @@ -133,3 +133,31 @@ fn issue_8920() { .filter(|f| f.result_field.is_ok()) .map(|f| f.result_field.to_owned().unwrap()); } + +fn issue8010() { + #[derive(Clone)] + enum Enum { + A(i32), + B, + } + + let iter = [Enum::A(123), Enum::B].into_iter(); + + let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter.clone().filter(|x| matches!(x, Enum::B)).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::A(_))) + .map(|x| if let Enum::A(s) = x { s } else { unreachable!() }); + #[allow(clippy::unused_unit)] + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::B)) + .map(|x| if let Enum::B = x { () } else { unreachable!() }); +} diff --git a/src/tools/clippy/tests/ui/manual_filter_map.stderr b/src/tools/clippy/tests/ui/manual_filter_map.stderr index 882468b0f5f19..0e8672c029309 100644 --- a/src/tools/clippy/tests/ui/manual_filter_map.stderr +++ b/src/tools/clippy/tests/ui/manual_filter_map.stderr @@ -4,6 +4,11 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:9:30 + | +LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^ = note: `-D clippy::manual-filter-map` implied by `-D warnings` error: `filter(..).map(..)` can be simplified as `filter_map(..)` @@ -11,12 +16,24 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` | LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:12:31 + | +LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:15:19 | LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:15:31 + | +LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:18:10 @@ -25,6 +42,12 @@ LL | .filter(|&x| to_ref(to_opt(x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:18:22 + | +LL | .filter(|&x| to_ref(to_opt(x)).is_some()) + | ^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:21:10 @@ -33,6 +56,12 @@ LL | .filter(|x| to_ref(to_opt(*x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:21:21 + | +LL | .filter(|x| to_ref(to_opt(*x)).is_some()) + | ^^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:25:10 @@ -41,6 +70,12 @@ LL | .filter(|&x| to_ref(to_res(x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:25:22 + | +LL | .filter(|&x| to_ref(to_res(x)).is_ok()) + | ^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:28:10 @@ -49,6 +84,12 @@ LL | .filter(|x| to_ref(to_res(*x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:28:21 + | +LL | .filter(|x| to_ref(to_res(*x)).is_ok()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_filter_map.rs:34:27 @@ -75,6 +116,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:37:41 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_filter_map.rs:39:30 @@ -117,6 +164,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:45:45 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:93:10 @@ -190,5 +243,23 @@ LL | .filter(|f| f.result_field.is_ok()) LL | | .map(|f| f.result_field.to_owned().unwrap()); | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` -error: aborting due to 27 previous errors +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:146:27 + | +LL | let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x { + | ___________________________^ +LL | | Enum::A(s) => s, +LL | | _ => unreachable!(), +LL | | }); + | |______^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:156:10 + | +LL | .filter(|x| matches!(x, Enum::A(_))) + | __________^ +LL | | .map(|x| if let Enum::A(s) = x { s } else { unreachable!() }); + | |_____________________________________________________________________^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })` + +error: aborting due to 29 previous errors diff --git a/src/tools/clippy/tests/ui/manual_find_map.stderr b/src/tools/clippy/tests/ui/manual_find_map.stderr index 693a06bb5590b..4e52b5efacf15 100644 --- a/src/tools/clippy/tests/ui/manual_find_map.stderr +++ b/src/tools/clippy/tests/ui/manual_find_map.stderr @@ -4,6 +4,11 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:9:28 + | +LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^ = note: `-D clippy::manual-find-map` implied by `-D warnings` error: `find(..).map(..)` can be simplified as `find_map(..)` @@ -11,12 +16,24 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:12:29 + | +LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:15:19 | LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:15:29 + | +LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:18:10 @@ -25,6 +42,12 @@ LL | .find(|&x| to_ref(to_opt(x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:18:20 + | +LL | .find(|&x| to_ref(to_opt(x)).is_some()) + | ^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:21:10 @@ -33,6 +56,12 @@ LL | .find(|x| to_ref(to_opt(*x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:21:19 + | +LL | .find(|x| to_ref(to_opt(*x)).is_some()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:25:10 @@ -41,6 +70,12 @@ LL | .find(|&x| to_ref(to_res(x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:25:20 + | +LL | .find(|&x| to_ref(to_res(x)).is_ok()) + | ^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:28:10 @@ -49,6 +84,12 @@ LL | .find(|x| to_ref(to_res(*x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:28:19 + | +LL | .find(|x| to_ref(to_res(*x)).is_ok()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:34:26 @@ -91,6 +132,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:40:41 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:42:30 @@ -133,6 +180,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:48:45 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:96:10 diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed index 60f590661735c..f19149cf9def9 100644 --- a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.fixed @@ -5,7 +5,8 @@ unreachable_patterns, dead_code, clippy::equatable_if_let, - clippy::needless_borrowed_reference + clippy::needless_borrowed_reference, + clippy::redundant_guards )] fn main() { diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs index afdf1069f5e4a..8f4e58981ea6a 100644 --- a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.rs @@ -5,7 +5,8 @@ unreachable_patterns, dead_code, clippy::equatable_if_let, - clippy::needless_borrowed_reference + clippy::needless_borrowed_reference, + clippy::redundant_guards )] fn main() { diff --git a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr index c8c1e5da05fe0..b57b26284ff36 100644 --- a/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr +++ b/src/tools/clippy/tests/ui/match_expr_like_matches_macro.stderr @@ -1,5 +1,5 @@ error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:15:14 + --> $DIR/match_expr_like_matches_macro.rs:16:14 | LL | let _y = match x { | ______________^ @@ -11,7 +11,7 @@ LL | | }; = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` error: redundant pattern matching, consider using `is_some()` - --> $DIR/match_expr_like_matches_macro.rs:21:14 + --> $DIR/match_expr_like_matches_macro.rs:22:14 | LL | let _w = match x { | ______________^ @@ -23,7 +23,7 @@ LL | | }; = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` error: redundant pattern matching, consider using `is_none()` - --> $DIR/match_expr_like_matches_macro.rs:27:14 + --> $DIR/match_expr_like_matches_macro.rs:28:14 | LL | let _z = match x { | ______________^ @@ -33,7 +33,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:33:15 + --> $DIR/match_expr_like_matches_macro.rs:34:15 | LL | let _zz = match x { | _______________^ @@ -43,13 +43,13 @@ LL | | }; | |_____^ help: try: `!matches!(x, Some(r) if r == 0)` error: if let .. else expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:39:16 + --> $DIR/match_expr_like_matches_macro.rs:40:16 | LL | let _zzz = if let Some(5) = x { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:63:20 + --> $DIR/match_expr_like_matches_macro.rs:64:20 | LL | let _ans = match x { | ____________________^ @@ -60,7 +60,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:73:20 + --> $DIR/match_expr_like_matches_macro.rs:74:20 | LL | let _ans = match x { | ____________________^ @@ -73,7 +73,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:83:20 + --> $DIR/match_expr_like_matches_macro.rs:84:20 | LL | let _ans = match x { | ____________________^ @@ -84,7 +84,7 @@ LL | | }; | |_________^ help: try: `!matches!(x, E::B(_) | E::C)` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:143:18 + --> $DIR/match_expr_like_matches_macro.rs:144:18 | LL | let _z = match &z { | __________________^ @@ -94,7 +94,7 @@ LL | | }; | |_________^ help: try: `matches!(z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:152:18 + --> $DIR/match_expr_like_matches_macro.rs:153:18 | LL | let _z = match &z { | __________________^ @@ -104,7 +104,7 @@ LL | | }; | |_________^ help: try: `matches!(&z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:169:21 + --> $DIR/match_expr_like_matches_macro.rs:170:21 | LL | let _ = match &z { | _____________________^ @@ -114,7 +114,7 @@ LL | | }; | |_____________^ help: try: `matches!(&z, AnEnum::X)` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:183:20 + --> $DIR/match_expr_like_matches_macro.rs:184:20 | LL | let _res = match &val { | ____________________^ @@ -124,7 +124,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:195:20 + --> $DIR/match_expr_like_matches_macro.rs:196:20 | LL | let _res = match &val { | ____________________^ @@ -134,7 +134,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:253:14 + --> $DIR/match_expr_like_matches_macro.rs:254:14 | LL | let _y = match Some(5) { | ______________^ diff --git a/src/tools/clippy/tests/ui/min_ident_chars.rs b/src/tools/clippy/tests/ui/min_ident_chars.rs index 0fab224a29d3a..03784442e2c9e 100644 --- a/src/tools/clippy/tests/ui/min_ident_chars.rs +++ b/src/tools/clippy/tests/ui/min_ident_chars.rs @@ -81,3 +81,7 @@ fn b() {} fn wrong_pythagoras(a: f32, b: f32) -> f32 { a * a + a * b } + +mod issue_11163 { + struct Array([T; N]); +} diff --git a/src/tools/clippy/tests/ui/mut_key.stderr b/src/tools/clippy/tests/ui/mut_key.stderr index 02a0da86a4b83..3f756f5f0e598 100644 --- a/src/tools/clippy/tests/ui/mut_key.stderr +++ b/src/tools/clippy/tests/ui/mut_key.stderr @@ -12,14 +12,6 @@ error: mutable key type LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { | ^^^^^^^^^^^^ -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_key.rs:31:32 - | -LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&HashMap` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: mutable key type --> $DIR/mut_key.rs:32:5 | @@ -110,5 +102,13 @@ error: mutable key type LL | let _map = HashMap::>, usize>::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: this argument is a mutable reference, but not used mutably + --> $DIR/mut_key.rs:31:32 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&HashMap` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 18 previous errors diff --git a/src/tools/clippy/tests/ui/mut_reference.stderr b/src/tools/clippy/tests/ui/mut_reference.stderr index 23c812475c2af..1fdfbf9227e31 100644 --- a/src/tools/clippy/tests/ui/mut_reference.stderr +++ b/src/tools/clippy/tests/ui/mut_reference.stderr @@ -1,17 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_reference.rs:4:33 - | -LL | fn takes_a_mutable_reference(a: &mut i32) {} - | ^^^^^^^^ help: consider changing to: `&i32` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_reference.rs:11:44 - | -LL | fn takes_a_mutable_reference(&self, a: &mut i32) {} - | ^^^^^^^^ help: consider changing to: `&i32` - error: the function `takes_an_immutable_reference` doesn't need a mutable reference --> $DIR/mut_reference.rs:17:34 | @@ -32,5 +18,13 @@ error: the method `takes_an_immutable_reference` doesn't need a mutable referenc LL | my_struct.takes_an_immutable_reference(&mut 42); | ^^^^^^^ -error: aborting due to 5 previous errors +error: this argument is a mutable reference, but not used mutably + --> $DIR/mut_reference.rs:11:44 + | +LL | fn takes_a_mutable_reference(&self, a: &mut i32) {} + | ^^^^^^^^ help: consider changing to: `&i32` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.rs b/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.rs index 5e7280995c60f..ae7b018d0e256 100644 --- a/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.rs +++ b/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.rs @@ -1,9 +1,10 @@ -#![allow(unused)] +#![allow(clippy::if_same_then_else, clippy::no_effect)] +#![feature(lint_reasons)] use std::ptr::NonNull; -// Should only warn for `s`. fn foo(s: &mut Vec, b: &u32, x: &mut u32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably *x += *b + s.len() as u32; } @@ -27,8 +28,8 @@ fn foo5(s: &mut Vec) { foo2(s); } -// Should warn. fn foo6(s: &mut Vec) { + //~^ ERROR: this argument is a mutable reference, but not used mutably non_mut_ref(s); } @@ -40,13 +41,13 @@ impl Bar { // Should not warn on `&mut self`. fn bar(&mut self) {} - // Should warn about `vec` fn mushroom(&self, vec: &mut Vec) -> usize { + //~^ ERROR: this argument is a mutable reference, but not used mutably vec.len() } - // Should warn about `vec` (and not `self`). fn badger(&mut self, vec: &mut Vec) -> usize { + //~^ ERROR: this argument is a mutable reference, but not used mutably vec.len() } } @@ -91,6 +92,110 @@ impl Mut { // Should not warn. fn unused(_: &mut u32, _b: &mut u8) {} +// Should not warn. +async fn f1(x: &mut i32) { + *x += 1; +} +// Should not warn. +async fn f2(x: &mut i32, y: String) { + *x += 1; +} +// Should not warn. +async fn f3(x: &mut i32, y: String, z: String) { + *x += 1; +} +// Should not warn. +async fn f4(x: &mut i32, y: i32) { + *x += 1; +} +// Should not warn. +async fn f5(x: i32, y: &mut i32) { + *y += 1; +} +// Should not warn. +async fn f6(x: i32, y: &mut i32, z: &mut i32) { + *y += 1; + *z += 1; +} +// Should not warn. +async fn f7(x: &mut i32, y: i32, z: &mut i32, a: i32) { + *x += 1; + *z += 1; +} + +async fn a1(x: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a2(x: &mut i32, y: String) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a3(x: &mut i32, y: String, z: String) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a4(x: &mut i32, y: i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a5(x: i32, y: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a6(x: i32, y: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a7(x: i32, y: i32, z: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", z); +} +async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", z); +} + +// Should not warn (passed as closure which takes `&mut`). +fn passed_as_closure(s: &mut u32) {} + +// Should not warn. +fn passed_as_local(s: &mut u32) {} + +// Should not warn. +fn ty_unify_1(s: &mut u32) {} + +// Should not warn. +fn ty_unify_2(s: &mut u32) {} + +// Should not warn. +fn passed_as_field(s: &mut u32) {} + +fn closure_takes_mut(s: fn(&mut u32)) {} + +struct A { + s: fn(&mut u32), +} + +// Should warn. +fn used_as_path(s: &mut u32) {} + +// Make sure lint attributes work fine +#[expect(clippy::needless_pass_by_ref_mut)] +fn lint_attr(s: &mut u32) {} + +#[cfg(not(feature = "a"))] +fn cfg_warn(s: &mut u32) {} +//~^ ERROR: this argument is a mutable reference, but not used mutably +//~| NOTE: this is cfg-gated and may require further changes + +#[cfg(not(feature = "a"))] +mod foo { + fn cfg_warn(s: &mut u32) {} + //~^ ERROR: this argument is a mutable reference, but not used mutably + //~| NOTE: this is cfg-gated and may require further changes +} + fn main() { let mut u = 0; let mut v = vec![0]; @@ -102,4 +207,9 @@ fn main() { alias_check(&mut v); alias_check2(&mut v); println!("{u}"); + closure_takes_mut(passed_as_closure); + A { s: passed_as_field }; + used_as_path; + let _: fn(&mut u32) = passed_as_local; + let _ = if v[0] == 0 { ty_unify_1 } else { ty_unify_2 }; } diff --git a/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.stderr b/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.stderr index 5e9d80bb6c488..0d426ce32f9d5 100644 --- a/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.stderr +++ b/src/tools/clippy/tests/ui/needless_pass_by_ref_mut.stderr @@ -24,5 +24,75 @@ error: this argument is a mutable reference, but not used mutably LL | fn badger(&mut self, vec: &mut Vec) -> usize { | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` -error: aborting due to 4 previous errors +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:126:16 + | +LL | async fn a1(x: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:130:16 + | +LL | async fn a2(x: &mut i32, y: String) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:134:16 + | +LL | async fn a3(x: &mut i32, y: String, z: String) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:138:16 + | +LL | async fn a4(x: &mut i32, y: i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:142:24 + | +LL | async fn a5(x: i32, y: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:146:24 + | +LL | async fn a6(x: i32, y: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:150:32 + | +LL | async fn a7(x: i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:154:24 + | +LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:154:45 + | +LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:188:16 + | +LL | fn cfg_warn(s: &mut u32) {} + | ^^^^^^^^ help: consider changing to: `&u32` + | + = note: this is cfg-gated and may require further changes + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:194:20 + | +LL | fn cfg_warn(s: &mut u32) {} + | ^^^^^^^^ help: consider changing to: `&u32` + | + = note: this is cfg-gated and may require further changes + +error: aborting due to 15 previous errors diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed new file mode 100644 index 0000000000000..d6e47d07b0f80 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed @@ -0,0 +1,40 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::needless_return, + clippy::no_effect, + clippy::unit_arg, + clippy::useless_conversion, + unused +)] + +#[macro_use] +extern crate proc_macros; + +fn a() -> u32 { + return 0; +} + +fn b() -> Result { + return Err(0); +} + +// Do not lint +fn c() -> Option<()> { + return None?; +} + +fn main() -> Result<(), ()> { + Err(())?; + return Ok::<(), ()>(()); + Err(())?; + Ok::<(), ()>(()); + return Err(().into()); + external! { + return Err(())?; + } + with_span! { + return Err(())?; + } + Err(()) +} diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs new file mode 100644 index 0000000000000..4fc04d363a9bf --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs @@ -0,0 +1,40 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::needless_return, + clippy::no_effect, + clippy::unit_arg, + clippy::useless_conversion, + unused +)] + +#[macro_use] +extern crate proc_macros; + +fn a() -> u32 { + return 0; +} + +fn b() -> Result { + return Err(0); +} + +// Do not lint +fn c() -> Option<()> { + return None?; +} + +fn main() -> Result<(), ()> { + return Err(())?; + return Ok::<(), ()>(()); + Err(())?; + Ok::<(), ()>(()); + return Err(().into()); + external! { + return Err(())?; + } + with_span! { + return Err(())?; + } + Err(()) +} diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr new file mode 100644 index 0000000000000..e1d91638d2c77 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr @@ -0,0 +1,10 @@ +error: unneeded `return` statement with `?` operator + --> $DIR/needless_return_with_question_mark.rs:28:5 + | +LL | return Err(())?; + | ^^^^^^^ help: remove it + | + = note: `-D clippy::needless-return-with-question-mark` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.rs b/src/tools/clippy/tests/ui/option_env_unwrap.rs index 65a1b467f8114..61dbad939db40 100644 --- a/src/tools/clippy/tests/ui/option_env_unwrap.rs +++ b/src/tools/clippy/tests/ui/option_env_unwrap.rs @@ -9,6 +9,7 @@ use proc_macros::{external, inline_macros}; fn main() { let _ = option_env!("PATH").unwrap(); let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in your environment. let _ = inline!(option_env!($"PATH").unwrap()); let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); let _ = external!(option_env!($"PATH").unwrap()); diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.stderr b/src/tools/clippy/tests/ui/option_env_unwrap.stderr index 7bba62686eecf..cfa9dd58a3006 100644 --- a/src/tools/clippy/tests/ui/option_env_unwrap.stderr +++ b/src/tools/clippy/tests/ui/option_env_unwrap.stderr @@ -16,7 +16,15 @@ 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:12:21 + --> $DIR/option_env_unwrap.rs:12:13 + | +LL | let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in your env... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:13:21 | LL | let _ = inline!(option_env!($"PATH").unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +33,7 @@ LL | let _ = inline!(option_env!($"PATH").unwrap()); = 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:21 + --> $DIR/option_env_unwrap.rs:14:21 | LL | let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +42,7 @@ LL | let _ = inline!(option_env!($"PATH").expect($"environment variable PATH = 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:14:13 + --> $DIR/option_env_unwrap.rs:15:13 | LL | let _ = external!(option_env!($"PATH").unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -43,7 +51,7 @@ LL | let _ = external!(option_env!($"PATH").unwrap()); = 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:15:13 + --> $DIR/option_env_unwrap.rs:16:13 | LL | let _ = external!(option_env!($"PATH").expect($"environment variable PATH isn't set")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -51,5 +59,5 @@ LL | let _ = external!(option_env!($"PATH").expect($"environment variable PA = help: consider using the `env!` macro instead = 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 +error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/option_if_let_else.fixed b/src/tools/clippy/tests/ui/option_if_let_else.fixed index 8e59e4375d2e8..6fee3cce619c6 100644 --- a/src/tools/clippy/tests/ui/option_if_let_else.fixed +++ b/src/tools/clippy/tests/ui/option_if_let_else.fixed @@ -5,7 +5,8 @@ clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let, - clippy::let_unit_value + clippy::let_unit_value, + clippy::redundant_locals )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/src/tools/clippy/tests/ui/option_if_let_else.rs b/src/tools/clippy/tests/ui/option_if_let_else.rs index e72edf2a8e318..4b3cf948a1bed 100644 --- a/src/tools/clippy/tests/ui/option_if_let_else.rs +++ b/src/tools/clippy/tests/ui/option_if_let_else.rs @@ -5,7 +5,8 @@ clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let, - clippy::let_unit_value + clippy::let_unit_value, + clippy::redundant_locals )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/src/tools/clippy/tests/ui/option_if_let_else.stderr b/src/tools/clippy/tests/ui/option_if_let_else.stderr index aa2da21740032..350f0f07e136e 100644 --- a/src/tools/clippy/tests/ui/option_if_let_else.stderr +++ b/src/tools/clippy/tests/ui/option_if_let_else.stderr @@ -1,5 +1,5 @@ error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:12:5 + --> $DIR/option_if_let_else.rs:13:5 | LL | / if let Some(x) = string { LL | | (true, x) @@ -11,19 +11,19 @@ LL | | } = note: `-D clippy::option-if-let-else` implied by `-D warnings` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:30:13 + --> $DIR/option_if_let_else.rs:31:13 | LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:31:13 + --> $DIR/option_if_let_else.rs:32:13 | LL | let _ = if let Some(s) = &num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:32:13 + --> $DIR/option_if_let_else.rs:33:13 | LL | let _ = if let Some(s) = &mut num { | _____________^ @@ -43,13 +43,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:38:13 + --> $DIR/option_if_let_else.rs:39:13 | LL | let _ = if let Some(ref s) = num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:39:13 + --> $DIR/option_if_let_else.rs:40:13 | LL | let _ = if let Some(mut s) = num { | _____________^ @@ -69,7 +69,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:45:13 + --> $DIR/option_if_let_else.rs:46:13 | LL | let _ = if let Some(ref mut s) = num { | _____________^ @@ -89,7 +89,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:54:5 + --> $DIR/option_if_let_else.rs:55:5 | LL | / if let Some(x) = arg { LL | | let y = x * x; @@ -108,7 +108,7 @@ LL + }) | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:67:13 + --> $DIR/option_if_let_else.rs:68:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -120,7 +120,7 @@ LL | | }; | |_____^ help: try: `arg.map_or_else(|| side_effect(), |x| x)` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:76:13 + --> $DIR/option_if_let_else.rs:77:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -143,7 +143,7 @@ LL ~ }, |x| x * x * x * x); | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:109:13 + --> $DIR/option_if_let_else.rs:110:13 | LL | / if let Some(idx) = s.find('.') { LL | | vec![s[..idx].to_string(), s[idx..].to_string()] @@ -153,7 +153,7 @@ LL | | } | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:120:5 + --> $DIR/option_if_let_else.rs:121:5 | LL | / if let Ok(binding) = variable { LL | | println!("Ok {binding}"); @@ -172,13 +172,13 @@ LL + }) | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:142:13 + --> $DIR/option_if_let_else.rs:143:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:152:13 + --> $DIR/option_if_let_else.rs:153:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -200,13 +200,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:180:13 + --> $DIR/option_if_let_else.rs:181:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:184:13 + --> $DIR/option_if_let_else.rs:185:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -226,7 +226,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:223:13 + --> $DIR/option_if_let_else.rs:224:13 | LL | let _ = match s { | _____________^ @@ -236,7 +236,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:227:13 + --> $DIR/option_if_let_else.rs:228:13 | LL | let _ = match Some(10) { | _____________^ @@ -246,7 +246,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:233:13 + --> $DIR/option_if_let_else.rs:234:13 | LL | let _ = match res { | _____________^ @@ -256,7 +256,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:237:13 + --> $DIR/option_if_let_else.rs:238:13 | LL | let _ = match res { | _____________^ @@ -266,13 +266,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:241:13 + --> $DIR/option_if_let_else.rs:242:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:258:9 + --> $DIR/option_if_let_else.rs:259:9 | LL | / match initial { LL | | Some(value) => do_something(value), @@ -281,7 +281,7 @@ LL | | } | |_________^ help: try: `initial.as_ref().map_or({}, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:265:9 + --> $DIR/option_if_let_else.rs:266:9 | LL | / match initial { LL | | Some(value) => do_something2(value), diff --git a/src/tools/clippy/tests/ui/or_fun_call.fixed b/src/tools/clippy/tests/ui/or_fun_call.fixed index 6deff0f3240a2..581f3ad45c7d7 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.fixed +++ b/src/tools/clippy/tests/ui/or_fun_call.fixed @@ -190,7 +190,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn more_to_max_suggestion_highest_lines_1() { @@ -203,7 +203,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn equal_to_max_suggestion_highest_lines() { @@ -215,7 +215,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn less_than_max_suggestion_highest_lines() { @@ -226,7 +226,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } } @@ -257,4 +257,59 @@ mod issue8993 { } } +mod lazy { + use super::*; + + fn foo() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + struct FakeDefault; + impl FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + impl Default for FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + let with_new = Some(vec![1]); + with_new.unwrap_or_default(); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_default(); + + let with_default_type = Some(1); + with_default_type.unwrap_or_default(); + + let real_default = None::; + real_default.unwrap_or_default(); + + let mut map = HashMap::::new(); + map.entry(42).or_default(); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_default(); + + let stringy = Some(String::new()); + let _ = stringy.unwrap_or_default(); + + // negative tests + let self_default = None::; + self_default.unwrap_or_else(::default); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.rs b/src/tools/clippy/tests/ui/or_fun_call.rs index b05b33e6ee2b3..1f3987eb8917a 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.rs +++ b/src/tools/clippy/tests/ui/or_fun_call.rs @@ -257,4 +257,59 @@ mod issue8993 { } } +mod lazy { + use super::*; + + fn foo() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + struct FakeDefault; + impl FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + impl Default for FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + let with_new = Some(vec![1]); + with_new.unwrap_or_else(Vec::new); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_else(Default::default); + + let with_default_type = Some(1); + with_default_type.unwrap_or_else(u64::default); + + let real_default = None::; + real_default.unwrap_or_else(::default); + + let mut map = HashMap::::new(); + map.entry(42).or_insert_with(String::new); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert_with(String::new); + + let stringy = Some(String::new()); + let _ = stringy.unwrap_or_else(String::new); + + // negative tests + let self_default = None::; + self_default.unwrap_or_else(::default); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.stderr b/src/tools/clippy/tests/ui/or_fun_call.stderr index 7342b0c2914b0..519f09165626c 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.stderr +++ b/src/tools/clippy/tests/ui/or_fun_call.stderr @@ -6,11 +6,13 @@ LL | with_constructor.unwrap_or(make()); | = note: `-D clippy::or-fun-call` implied by `-D warnings` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:56:14 | LL | with_new.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + | + = note: `-D clippy::unwrap-or-default` implied by `-D warnings` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:59:21 @@ -30,13 +32,13 @@ error: use of `unwrap_or` followed by a function call LL | with_err_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Vec::with_capacity(12))` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:68:24 | LL | with_default_trait.unwrap_or(Default::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:71:23 | LL | with_default_type.unwrap_or(u64::default()); @@ -48,13 +50,13 @@ error: use of `unwrap_or` followed by a function call LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(::default)` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:77:18 | LL | real_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:80:14 | LL | with_vec.unwrap_or(vec![]); @@ -66,31 +68,31 @@ error: use of `unwrap_or` followed by a function call LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(Foo::new)` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:86:19 | LL | map.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:89:23 | LL | map_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:92:21 | LL | btree.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:95:25 | LL | btree_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:98:21 | LL | let _ = stringy.unwrap_or(String::new()); @@ -132,30 +134,6 @@ error: use of `unwrap_or` followed by a function call LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:193:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:206:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:218:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:229:10 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - error: use of `map_or` followed by a function call --> $DIR/or_fun_call.rs:254:25 | @@ -168,5 +146,47 @@ error: use of `map_or` followed by a function call LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` -error: aborting due to 28 previous errors +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:286:18 + | +LL | with_new.unwrap_or_else(Vec::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:289:28 + | +LL | with_default_trait.unwrap_or_else(Default::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:292:27 + | +LL | with_default_type.unwrap_or_else(u64::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:295:22 + | +LL | real_default.unwrap_or_else(::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/or_fun_call.rs:298:23 + | +LL | map.entry(42).or_insert_with(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/or_fun_call.rs:301:25 + | +LL | btree.entry(42).or_insert_with(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:304:25 + | +LL | let _ = stringy.unwrap_or_else(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: aborting due to 31 previous errors diff --git a/src/tools/clippy/tests/ui/ptr_arg.rs b/src/tools/clippy/tests/ui/ptr_arg.rs index 13e993d247b2c..08075c382a220 100644 --- a/src/tools/clippy/tests/ui/ptr_arg.rs +++ b/src/tools/clippy/tests/ui/ptr_arg.rs @@ -267,3 +267,16 @@ mod issue_9218 { todo!() } } + +mod issue_11181 { + extern "C" fn allowed(_v: &Vec) {} + + struct S; + impl S { + extern "C" fn allowed(_v: &Vec) {} + } + + trait T { + extern "C" fn allowed(_v: &Vec) {} + } +} diff --git a/src/tools/clippy/tests/ui/read_zero_byte_vec.rs b/src/tools/clippy/tests/ui/read_zero_byte_vec.rs index c6025ef1f4da1..ff2ad8644b49f 100644 --- a/src/tools/clippy/tests/ui/read_zero_byte_vec.rs +++ b/src/tools/clippy/tests/ui/read_zero_byte_vec.rs @@ -1,5 +1,9 @@ #![warn(clippy::read_zero_byte_vec)] -#![allow(clippy::unused_io_amount, clippy::needless_pass_by_ref_mut)] +#![allow( + clippy::unused_io_amount, + clippy::needless_pass_by_ref_mut, + clippy::slow_vector_initialization +)] use std::fs::File; use std::io; use std::io::prelude::*; diff --git a/src/tools/clippy/tests/ui/read_zero_byte_vec.stderr b/src/tools/clippy/tests/ui/read_zero_byte_vec.stderr index 08ba9753d7c41..4c7f605f4c2a5 100644 --- a/src/tools/clippy/tests/ui/read_zero_byte_vec.stderr +++ b/src/tools/clippy/tests/ui/read_zero_byte_vec.stderr @@ -1,5 +1,5 @@ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:17:5 + --> $DIR/read_zero_byte_vec.rs:21:5 | LL | f.read_exact(&mut data).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();` @@ -7,55 +7,55 @@ LL | f.read_exact(&mut data).unwrap(); = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:21:5 + --> $DIR/read_zero_byte_vec.rs:25:5 | LL | f.read_exact(&mut data2)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;` error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:25:5 + --> $DIR/read_zero_byte_vec.rs:29:5 | LL | f.read_exact(&mut data3)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:29:5 + --> $DIR/read_zero_byte_vec.rs:33:5 | LL | let _ = f.read(&mut data4)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:34:9 + --> $DIR/read_zero_byte_vec.rs:38:9 | LL | f.read(&mut data5) | ^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:40:9 + --> $DIR/read_zero_byte_vec.rs:44:9 | LL | f.read(&mut data6) | ^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:70:5 + --> $DIR/read_zero_byte_vec.rs:74:5 | LL | r.read(&mut data).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:74:5 + --> $DIR/read_zero_byte_vec.rs:78:5 | LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:80:5 + --> $DIR/read_zero_byte_vec.rs:84:5 | LL | r.read(&mut data).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:84:5 + --> $DIR/read_zero_byte_vec.rs:88:5 | LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/readonly_write_lock.rs b/src/tools/clippy/tests/ui/readonly_write_lock.rs new file mode 100644 index 0000000000000..656b45787e8a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/readonly_write_lock.rs @@ -0,0 +1,42 @@ +#![warn(clippy::readonly_write_lock)] + +use std::sync::RwLock; + +fn mutate_i32(x: &mut i32) { + *x += 1; +} + +fn accept_i32(_: i32) {} + +fn main() { + let lock = RwLock::new(42); + let lock2 = RwLock::new(1234); + + { + let writer = lock.write().unwrap(); + dbg!(&writer); + } + + { + let writer = lock.write().unwrap(); + accept_i32(*writer); + } + + { + let mut writer = lock.write().unwrap(); + mutate_i32(&mut writer); + dbg!(&writer); + } + + { + let mut writer = lock.write().unwrap(); + *writer += 1; + } + + { + let mut writer1 = lock.write().unwrap(); + let mut writer2 = lock2.write().unwrap(); + *writer2 += 1; + *writer1 = *writer2; + } +} diff --git a/src/tools/clippy/tests/ui/readonly_write_lock.stderr b/src/tools/clippy/tests/ui/readonly_write_lock.stderr new file mode 100644 index 0000000000000..e3d8fce7b2ce0 --- /dev/null +++ b/src/tools/clippy/tests/ui/readonly_write_lock.stderr @@ -0,0 +1,16 @@ +error: this write lock is used only for reading + --> $DIR/readonly_write_lock.rs:16:22 + | +LL | let writer = lock.write().unwrap(); + | ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()` + | + = note: `-D clippy::readonly-write-lock` implied by `-D warnings` + +error: this write lock is used only for reading + --> $DIR/readonly_write_lock.rs:21:22 + | +LL | let writer = lock.write().unwrap(); + | ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_guards.fixed b/src/tools/clippy/tests/ui/redundant_guards.fixed new file mode 100644 index 0000000000000..77ac76668649b --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_guards.fixed @@ -0,0 +1,133 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(if_let_guard)] +#![allow(clippy::no_effect, unused)] +#![warn(clippy::redundant_guards)] + +#[macro_use] +extern crate proc_macros; + +struct A(u32); + +struct B { + e: Option, +} + +struct C(u32, u32); + +fn main() { + let c = C(1, 2); + match c { + C(x, 1) => .., + _ => todo!(), + }; + + let x = Some(Some(1)); + match x { + Some(Some(1)) if true => .., + Some(Some(1)) => { + println!("a"); + .. + }, + Some(Some(1)) => .., + Some(Some(2)) => .., + // Don't lint, since x is used in the body + Some(x) if let Some(1) = x => { + x; + .. + } + _ => todo!(), + }; + let y = 1; + match x { + // Don't inline these, since y is not from the pat + Some(x) if matches!(y, 1 if true) => .., + Some(x) if let 1 = y => .., + Some(x) if y == 2 => .., + _ => todo!(), + }; + let a = A(1); + match a { + _ if a.0 == 1 => {}, + _ => todo!(), + } + let b = B { e: Some(A(0)) }; + match b { + B { e: Some(A(2)) } => .., + _ => todo!(), + }; + // Do not lint, since we cannot represent this as a pattern (at least, without a conversion) + let v = Some(vec![1u8, 2, 3]); + match v { + Some(x) if x == [1] => {}, + _ => {}, + } + + external! { + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } + with_span! { + span + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } +} + +enum E { + A(&'static str), + B(&'static str), + C(&'static str), +} + +fn i() { + match E::A("") { + // Do not lint + E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {}, + E::A("not from an or pattern") => {}, + _ => {}, + }; +} + +fn h(v: Option) { + match v { + Some(0) => .., + _ => .., + }; +} + +// Do not lint + +fn f(s: Option) { + match s { + Some(x) if x == "a" => {}, + _ => {}, + } +} + +struct S { + a: usize, +} + +impl PartialEq for S { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for S {} + +static CONST_S: S = S { a: 1 }; + +fn g(opt_s: Option) { + match opt_s { + Some(x) if x == CONST_S => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/redundant_guards.rs b/src/tools/clippy/tests/ui/redundant_guards.rs new file mode 100644 index 0000000000000..b072e4ea14d1d --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_guards.rs @@ -0,0 +1,133 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(if_let_guard)] +#![allow(clippy::no_effect, unused)] +#![warn(clippy::redundant_guards)] + +#[macro_use] +extern crate proc_macros; + +struct A(u32); + +struct B { + e: Option, +} + +struct C(u32, u32); + +fn main() { + let c = C(1, 2); + match c { + C(x, y) if let 1 = y => .., + _ => todo!(), + }; + + let x = Some(Some(1)); + match x { + Some(x) if matches!(x, Some(1) if true) => .., + Some(x) if matches!(x, Some(1)) => { + println!("a"); + .. + }, + Some(x) if let Some(1) = x => .., + Some(x) if x == Some(2) => .., + // Don't lint, since x is used in the body + Some(x) if let Some(1) = x => { + x; + .. + } + _ => todo!(), + }; + let y = 1; + match x { + // Don't inline these, since y is not from the pat + Some(x) if matches!(y, 1 if true) => .., + Some(x) if let 1 = y => .., + Some(x) if y == 2 => .., + _ => todo!(), + }; + let a = A(1); + match a { + _ if a.0 == 1 => {}, + _ => todo!(), + } + let b = B { e: Some(A(0)) }; + match b { + B { e } if matches!(e, Some(A(2))) => .., + _ => todo!(), + }; + // Do not lint, since we cannot represent this as a pattern (at least, without a conversion) + let v = Some(vec![1u8, 2, 3]); + match v { + Some(x) if x == [1] => {}, + _ => {}, + } + + external! { + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } + with_span! { + span + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } +} + +enum E { + A(&'static str), + B(&'static str), + C(&'static str), +} + +fn i() { + match E::A("") { + // Do not lint + E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {}, + E::A(y) if y == "not from an or pattern" => {}, + _ => {}, + }; +} + +fn h(v: Option) { + match v { + x if matches!(x, Some(0)) => .., + _ => .., + }; +} + +// Do not lint + +fn f(s: Option) { + match s { + Some(x) if x == "a" => {}, + _ => {}, + } +} + +struct S { + a: usize, +} + +impl PartialEq for S { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for S {} + +static CONST_S: S = S { a: 1 }; + +fn g(opt_s: Option) { + match opt_s { + Some(x) if x == CONST_S => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/redundant_guards.stderr b/src/tools/clippy/tests/ui/redundant_guards.stderr new file mode 100644 index 0000000000000..c2a92071d1dfb --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_guards.stderr @@ -0,0 +1,98 @@ +error: redundant guard + --> $DIR/redundant_guards.rs:21:20 + | +LL | C(x, y) if let 1 = y => .., + | ^^^^^^^^^ + | + = note: `-D clippy::redundant-guards` implied by `-D warnings` +help: try + | +LL - C(x, y) if let 1 = y => .., +LL + C(x, 1) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:27:20 + | +LL | Some(x) if matches!(x, Some(1) if true) => .., + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | Some(Some(1)) if true => .., + | ~~~~~~~ ~~~~~~~ + +error: redundant guard + --> $DIR/redundant_guards.rs:28:20 + | +LL | Some(x) if matches!(x, Some(1)) => { + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if matches!(x, Some(1)) => { +LL + Some(Some(1)) => { + | + +error: redundant guard + --> $DIR/redundant_guards.rs:32:20 + | +LL | Some(x) if let Some(1) = x => .., + | ^^^^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if let Some(1) = x => .., +LL + Some(Some(1)) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:33:20 + | +LL | Some(x) if x == Some(2) => .., + | ^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if x == Some(2) => .., +LL + Some(Some(2)) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:56:20 + | +LL | B { e } if matches!(e, Some(A(2))) => .., + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - B { e } if matches!(e, Some(A(2))) => .., +LL + B { e: Some(A(2)) } => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:93:20 + | +LL | E::A(y) if y == "not from an or pattern" => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - E::A(y) if y == "not from an or pattern" => {}, +LL + E::A("not from an or pattern") => {}, + | + +error: redundant guard + --> $DIR/redundant_guards.rs:100:14 + | +LL | x if matches!(x, Some(0)) => .., + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - x if matches!(x, Some(0)) => .., +LL + Some(0) => .., + | + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_locals.rs b/src/tools/clippy/tests/ui/redundant_locals.rs new file mode 100644 index 0000000000000..e74c78a50b672 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_locals.rs @@ -0,0 +1,111 @@ +//@aux-build:proc_macros.rs:proc-macro +#![allow(unused, clippy::no_effect, clippy::needless_pass_by_ref_mut)] +#![warn(clippy::redundant_locals)] + +extern crate proc_macros; +use proc_macros::{external, with_span}; + +fn main() {} + +fn immutable() { + let x = 1; + let x = x; +} + +fn mutable() { + let mut x = 1; + let mut x = x; +} + +fn upgraded_mutability() { + let x = 1; + let mut x = x; +} + +fn downgraded_mutability() { + let mut x = 1; + let x = x; +} + +fn coercion(par: &mut i32) { + let par: &i32 = par; + + let x: &mut i32 = &mut 1; + let x: &i32 = x; +} + +fn parameter(x: i32) { + let x = x; +} + +fn many() { + let x = 1; + let x = x; + let x = x; + let x = x; + let x = x; +} + +fn interleaved() { + let a = 1; + let b = 2; + let a = a; + let b = b; +} + +fn block() { + { + let x = 1; + let x = x; + } +} + +fn closure() { + || { + let x = 1; + let x = x; + }; + |x: i32| { + let x = x; + }; +} + +fn consequential_drop_order() { + use std::sync::Mutex; + + let mutex = Mutex::new(1); + let guard = mutex.lock().unwrap(); + + { + let guard = guard; + } +} + +fn inconsequential_drop_order() { + let x = 1; + + { + let x = x; + } +} + +fn macros() { + macro_rules! rebind { + ($x:ident) => { + let $x = 1; + let $x = $x; + }; + } + + rebind!(x); + + external! { + let x = 1; + let x = x; + } + with_span! { + span + let x = 1; + let x = x; + } +} diff --git a/src/tools/clippy/tests/ui/redundant_locals.stderr b/src/tools/clippy/tests/ui/redundant_locals.stderr new file mode 100644 index 0000000000000..df07dd2f453b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_locals.stderr @@ -0,0 +1,136 @@ +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:11:9 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + = note: `-D clippy::redundant-locals` implied by `-D warnings` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:16:9 + | +LL | let mut x = 1; + | ^^^^^ +LL | let mut x = x; + | ^^^^^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:37:14 + | +LL | fn parameter(x: i32) { + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:42:9 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:43:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:44:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:45:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:50:9 + | +LL | let a = 1; + | ^ +LL | let b = 2; +LL | let a = a; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `a` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:51:9 + | +LL | let b = 2; + | ^ +LL | let a = a; +LL | let b = b; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `b` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:58:13 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:65:13 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:68:6 + | +LL | |x: i32| { + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:85:9 + | +LL | let x = 1; + | ^ +... +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed index a63ba5809e4bb..d9fcd98c56e00 100644 --- a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.fixed @@ -10,6 +10,20 @@ clippy::equatable_if_let, clippy::if_same_then_else )] +#![feature(let_chains, if_let_guard)] + +fn issue_11174(boolean: bool, maybe_some: Option) -> bool { + maybe_some.is_none() && (!boolean) +} + +fn issue_11174_edge_cases(boolean: bool, boolean2: bool, maybe_some: Option) { + let _ = maybe_some.is_none() && (boolean || boolean2); // guard needs parentheses + let _ = match maybe_some { // can't use `matches!` here + // because `expr` metavars in macros don't allow let exprs + None if let Some(x) = Some(0) && x > 5 => true, + _ => false + }; +} fn main() { if None::<()>.is_none() {} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs index 631f9091672d7..cbd9494f15acf 100644 --- a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.rs @@ -10,6 +10,20 @@ clippy::equatable_if_let, clippy::if_same_then_else )] +#![feature(let_chains, if_let_guard)] + +fn issue_11174(boolean: bool, maybe_some: Option) -> bool { + matches!(maybe_some, None if !boolean) +} + +fn issue_11174_edge_cases(boolean: bool, boolean2: bool, maybe_some: Option) { + let _ = matches!(maybe_some, None if boolean || boolean2); // guard needs parentheses + let _ = match maybe_some { // can't use `matches!` here + // because `expr` metavars in macros don't allow let exprs + None if let Some(x) = Some(0) && x > 5 => true, + _ => false + }; +} fn main() { if let None = None::<()> {} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr index 097ca827a4260..b0e43924dbc9a 100644 --- a/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching_option.stderr @@ -1,49 +1,61 @@ error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:15:12 + --> $DIR/redundant_pattern_matching_option.rs:16:5 | -LL | if let None = None::<()> {} - | -------^^^^------------- help: try: `if None::<()>.is_none()` +LL | matches!(maybe_some, None if !boolean) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `maybe_some.is_none() && (!boolean)` | = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:20:13 + | +LL | let _ = matches!(maybe_some, None if boolean || boolean2); // guard needs parentheses + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `maybe_some.is_none() && (boolean || boolean2)` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:29:12 + | +LL | if let None = None::<()> {} + | -------^^^^------------- help: try: `if None::<()>.is_none()` + error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:17:12 + --> $DIR/redundant_pattern_matching_option.rs:31:12 | LL | if let Some(_) = Some(42) {} | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:19:12 + --> $DIR/redundant_pattern_matching_option.rs:33:12 | LL | if let Some(_) = Some(42) { | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:25:15 + --> $DIR/redundant_pattern_matching_option.rs:39:15 | LL | while let Some(_) = Some(42) {} | ----------^^^^^^^----------- help: try: `while Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:27:15 + --> $DIR/redundant_pattern_matching_option.rs:41:15 | LL | while let None = Some(42) {} | ----------^^^^----------- help: try: `while Some(42).is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:29:15 + --> $DIR/redundant_pattern_matching_option.rs:43:15 | LL | while let None = None::<()> {} | ----------^^^^------------- help: try: `while None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:32:15 + --> $DIR/redundant_pattern_matching_option.rs:46:15 | LL | while let Some(_) = v.pop() { | ----------^^^^^^^---------- help: try: `while v.pop().is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:40:5 + --> $DIR/redundant_pattern_matching_option.rs:54:5 | LL | / match Some(42) { LL | | Some(_) => true, @@ -52,7 +64,7 @@ LL | | }; | |_____^ help: try: `Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:45:5 + --> $DIR/redundant_pattern_matching_option.rs:59:5 | LL | / match None::<()> { LL | | Some(_) => false, @@ -61,7 +73,7 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:50:13 + --> $DIR/redundant_pattern_matching_option.rs:64:13 | LL | let _ = match None::<()> { | _____________^ @@ -71,55 +83,55 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:56:20 + --> $DIR/redundant_pattern_matching_option.rs:70:20 | LL | let _ = if let Some(_) = opt { true } else { false }; | -------^^^^^^^------ help: try: `if opt.is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:62:20 + --> $DIR/redundant_pattern_matching_option.rs:76:20 | LL | let _ = if let Some(_) = gen_opt() { | -------^^^^^^^------------ help: try: `if gen_opt().is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:64:19 + --> $DIR/redundant_pattern_matching_option.rs:78:19 | LL | } else if let None = gen_opt() { | -------^^^^------------ help: try: `if gen_opt().is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:70:12 + --> $DIR/redundant_pattern_matching_option.rs:84:12 | LL | if let Some(..) = gen_opt() {} | -------^^^^^^^^------------ help: try: `if gen_opt().is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:85:12 + --> $DIR/redundant_pattern_matching_option.rs:99:12 | LL | if let Some(_) = Some(42) {} | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:87:12 + --> $DIR/redundant_pattern_matching_option.rs:101:12 | LL | if let None = None::<()> {} | -------^^^^------------- help: try: `if None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:89:15 + --> $DIR/redundant_pattern_matching_option.rs:103:15 | LL | while let Some(_) = Some(42) {} | ----------^^^^^^^----------- help: try: `while Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:91:15 + --> $DIR/redundant_pattern_matching_option.rs:105:15 | LL | while let None = None::<()> {} | ----------^^^^------------- help: try: `while None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:93:5 + --> $DIR/redundant_pattern_matching_option.rs:107:5 | LL | / match Some(42) { LL | | Some(_) => true, @@ -128,7 +140,7 @@ LL | | }; | |_____^ help: try: `Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:98:5 + --> $DIR/redundant_pattern_matching_option.rs:112:5 | LL | / match None::<()> { LL | | Some(_) => false, @@ -137,19 +149,19 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:106:12 + --> $DIR/redundant_pattern_matching_option.rs:120:12 | LL | if let None = *(&None::<()>) {} | -------^^^^----------------- help: try: `if (&None::<()>).is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:107:12 + --> $DIR/redundant_pattern_matching_option.rs:121:12 | LL | if let None = *&None::<()> {} | -------^^^^--------------- help: try: `if (&None::<()>).is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:113:5 + --> $DIR/redundant_pattern_matching_option.rs:127:5 | LL | / match x { LL | | Some(_) => true, @@ -158,7 +170,7 @@ LL | | }; | |_____^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:118:5 + --> $DIR/redundant_pattern_matching_option.rs:132:5 | LL | / match x { LL | | None => true, @@ -167,7 +179,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:123:5 + --> $DIR/redundant_pattern_matching_option.rs:137:5 | LL | / match x { LL | | Some(_) => false, @@ -176,7 +188,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:128:5 + --> $DIR/redundant_pattern_matching_option.rs:142:5 | LL | / match x { LL | | None => false, @@ -185,16 +197,16 @@ LL | | }; | |_____^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:143:13 + --> $DIR/redundant_pattern_matching_option.rs:157:13 | LL | let _ = matches!(x, Some(_)); | ^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:145:13 + --> $DIR/redundant_pattern_matching_option.rs:159:13 | LL | let _ = matches!(x, None); | ^^^^^^^^^^^^^^^^^ help: try: `x.is_none()` -error: aborting due to 28 previous errors +error: aborting due to 30 previous errors diff --git a/src/tools/clippy/tests/ui/rename.fixed b/src/tools/clippy/tests/ui/rename.fixed index 03d9ea41a0b1b..8ec19b120d127 100644 --- a/src/tools/clippy/tests/ui/rename.fixed +++ b/src/tools/clippy/tests/ui/rename.fixed @@ -27,6 +27,7 @@ #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] +#![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] @@ -78,6 +79,7 @@ #![warn(clippy::single_char_add_str)] #![warn(clippy::module_name_repetitions)] #![warn(clippy::recursive_format_impl)] +#![warn(clippy::unwrap_or_default)] #![warn(clippy::invisible_characters)] #![warn(invalid_reference_casting)] #![warn(suspicious_double_ref_op)] diff --git a/src/tools/clippy/tests/ui/rename.rs b/src/tools/clippy/tests/ui/rename.rs index c028fcb5a3769..51a5976eb6149 100644 --- a/src/tools/clippy/tests/ui/rename.rs +++ b/src/tools/clippy/tests/ui/rename.rs @@ -27,6 +27,7 @@ #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] +#![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] @@ -78,6 +79,7 @@ #![warn(clippy::single_char_push_str)] #![warn(clippy::stutter)] #![warn(clippy::to_string_in_display)] +#![warn(clippy::unwrap_or_else_default)] #![warn(clippy::zero_width_space)] #![warn(clippy::cast_ref_to_mut)] #![warn(clippy::clone_double_ref)] diff --git a/src/tools/clippy/tests/ui/rename.stderr b/src/tools/clippy/tests/ui/rename.stderr index 4d8a7fd70f454..e420ea1d2adb5 100644 --- a/src/tools/clippy/tests/ui/rename.stderr +++ b/src/tools/clippy/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> $DIR/rename.rs:53:9 + --> $DIR/rename.rs:54:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -7,316 +7,322 @@ LL | #![warn(clippy::almost_complete_letter_range)] = note: `-D renamed-and-removed-lints` implied by `-D warnings` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> $DIR/rename.rs:54:9 + --> $DIR/rename.rs:55:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:55:9 + --> $DIR/rename.rs:56:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:56:9 + --> $DIR/rename.rs:57:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> $DIR/rename.rs:57:9 + --> $DIR/rename.rs:58:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> $DIR/rename.rs:58:9 + --> $DIR/rename.rs:59:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> $DIR/rename.rs:59:9 + --> $DIR/rename.rs:60:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> $DIR/rename.rs:60:9 + --> $DIR/rename.rs:61:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> $DIR/rename.rs:61:9 + --> $DIR/rename.rs:62:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> $DIR/rename.rs:62:9 + --> $DIR/rename.rs:63:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> $DIR/rename.rs:63:9 + --> $DIR/rename.rs:64:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> $DIR/rename.rs:64:9 + --> $DIR/rename.rs:65:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> $DIR/rename.rs:65:9 + --> $DIR/rename.rs:66:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> $DIR/rename.rs:66:9 + --> $DIR/rename.rs:67:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> $DIR/rename.rs:67:9 + --> $DIR/rename.rs:68:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> $DIR/rename.rs:68:9 + --> $DIR/rename.rs:69:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> $DIR/rename.rs:69:9 + --> $DIR/rename.rs:70:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:70:9 + --> $DIR/rename.rs:71:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:71:9 + --> $DIR/rename.rs:72:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:72:9 + --> $DIR/rename.rs:73:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:73:9 + --> $DIR/rename.rs:74:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> $DIR/rename.rs:74:9 + --> $DIR/rename.rs:75:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:75:9 + --> $DIR/rename.rs:76:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:76:9 + --> $DIR/rename.rs:77:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:77:9 + --> $DIR/rename.rs:78:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> $DIR/rename.rs:78:9 + --> $DIR/rename.rs:79:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> $DIR/rename.rs:79:9 + --> $DIR/rename.rs:80:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> $DIR/rename.rs:80:9 + --> $DIR/rename.rs:81:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` +error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` + --> $DIR/rename.rs:82:9 + | +LL | #![warn(clippy::unwrap_or_else_default)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` + error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> $DIR/rename.rs:81:9 + --> $DIR/rename.rs:83:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> $DIR/rename.rs:82:9 + --> $DIR/rename.rs:84:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> $DIR/rename.rs:83:9 + --> $DIR/rename.rs:85:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> $DIR/rename.rs:84:9 + --> $DIR/rename.rs:86:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> $DIR/rename.rs:85:9 + --> $DIR/rename.rs:87:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> $DIR/rename.rs:86:9 + --> $DIR/rename.rs:88:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> $DIR/rename.rs:87:9 + --> $DIR/rename.rs:89:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:88:9 + --> $DIR/rename.rs:90:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:89:9 + --> $DIR/rename.rs:91:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:90:9 + --> $DIR/rename.rs:92:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> $DIR/rename.rs:91:9 + --> $DIR/rename.rs:93:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> $DIR/rename.rs:92:9 + --> $DIR/rename.rs:94:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::fn_null_check` has been renamed to `incorrect_fn_null_checks` - --> $DIR/rename.rs:93:9 + --> $DIR/rename.rs:95:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `incorrect_fn_null_checks` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> $DIR/rename.rs:94:9 + --> $DIR/rename.rs:96:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> $DIR/rename.rs:95:9 + --> $DIR/rename.rs:97:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> $DIR/rename.rs:96:9 + --> $DIR/rename.rs:98:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> $DIR/rename.rs:97:9 + --> $DIR/rename.rs:99:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> $DIR/rename.rs:98:9 + --> $DIR/rename.rs:100:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> $DIR/rename.rs:99:9 + --> $DIR/rename.rs:101:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> $DIR/rename.rs:100:9 + --> $DIR/rename.rs:102:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> $DIR/rename.rs:101:9 + --> $DIR/rename.rs:103:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr` - --> $DIR/rename.rs:102:9 + --> $DIR/rename.rs:104:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> $DIR/rename.rs:103:9 + --> $DIR/rename.rs:105:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> $DIR/rename.rs:104:9 + --> $DIR/rename.rs:106:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> $DIR/rename.rs:105:9 + --> $DIR/rename.rs:107:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` -error: aborting due to 53 previous errors +error: aborting due to 54 previous errors diff --git a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.fixed b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.fixed new file mode 100644 index 0000000000000..653f4533b331f --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.fixed @@ -0,0 +1,123 @@ +//@run-rustfix +#![warn(clippy::semicolon_if_nothing_returned)] +#![allow(clippy::redundant_closure, clippy::uninlined_format_args, clippy::needless_late_init)] + +fn get_unit() {} + +// the functions below trigger the lint +fn main() { + println!("Hello"); +} + +fn hello() { + get_unit(); +} + +fn basic101(x: i32) { + let y: i32; + y = x + 1; +} + +#[rustfmt::skip] +fn closure_error() { + let _d = || { + hello(); + }; +} + +#[rustfmt::skip] +fn unsafe_checks_error() { + use std::mem::MaybeUninit; + use std::ptr; + + let mut s = MaybeUninit::::uninit(); + let _d = || unsafe { + ptr::drop_in_place(s.as_mut_ptr()); + }; +} + +// this is fine +fn print_sum(a: i32, b: i32) { + println!("{}", a + b); + assert_eq!(true, false); +} + +fn foo(x: i32) { + let y: i32; + if x < 1 { + y = 4; + } else { + y = 5; + } +} + +fn bar(x: i32) { + let y: i32; + match x { + 1 => y = 4, + _ => y = 32, + } +} + +fn foobar(x: i32) { + let y: i32; + 'label: { + y = x + 1; + } +} + +fn loop_test(x: i32) { + let y: i32; + for &ext in &["stdout", "stderr", "fixed"] { + println!("{}", ext); + } +} + +fn closure() { + let _d = || hello(); +} + +#[rustfmt::skip] +fn closure_block() { + let _d = || { hello() }; +} + +unsafe fn some_unsafe_op() {} +unsafe fn some_other_unsafe_fn() {} + +fn do_something() { + unsafe { some_unsafe_op() }; + + unsafe { some_other_unsafe_fn() }; +} + +fn unsafe_checks() { + use std::mem::MaybeUninit; + use std::ptr; + + let mut s = MaybeUninit::::uninit(); + let _d = || unsafe { ptr::drop_in_place(s.as_mut_ptr()) }; +} + +// Issue #7768 +#[rustfmt::skip] +fn macro_with_semicolon() { + macro_rules! repro { + () => { + while false { + } + }; + } + repro!(); +} + +fn function_returning_option() -> Option { + Some(1) +} + +// No warning +fn let_else_stmts() { + let Some(x) = function_returning_option() else { + return; + }; +} diff --git a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs index 8e7f1d862cfdc..9db038219b4ae 100644 --- a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs +++ b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.rs @@ -1,5 +1,6 @@ +//@run-rustfix #![warn(clippy::semicolon_if_nothing_returned)] -#![allow(clippy::redundant_closure, clippy::uninlined_format_args)] +#![allow(clippy::redundant_closure, clippy::uninlined_format_args, clippy::needless_late_init)] fn get_unit() {} diff --git a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr index 8d9a67585cf12..78813e7cc1c39 100644 --- a/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr +++ b/src/tools/clippy/tests/ui/semicolon_if_nothing_returned.stderr @@ -1,5 +1,5 @@ error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:8:5 + --> $DIR/semicolon_if_nothing_returned.rs:9:5 | LL | println!("Hello") | ^^^^^^^^^^^^^^^^^ help: add a `;` here: `println!("Hello");` @@ -7,25 +7,25 @@ LL | println!("Hello") = note: `-D clippy::semicolon-if-nothing-returned` implied by `-D warnings` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:12:5 + --> $DIR/semicolon_if_nothing_returned.rs:13:5 | LL | get_unit() | ^^^^^^^^^^ help: add a `;` here: `get_unit();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:17:5 + --> $DIR/semicolon_if_nothing_returned.rs:18:5 | LL | y = x + 1 | ^^^^^^^^^ help: add a `;` here: `y = x + 1;` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:23:9 + --> $DIR/semicolon_if_nothing_returned.rs:24:9 | LL | hello() | ^^^^^^^ help: add a `;` here: `hello();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:34:9 + --> $DIR/semicolon_if_nothing_returned.rs:35:9 | LL | ptr::drop_in_place(s.as_mut_ptr()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `ptr::drop_in_place(s.as_mut_ptr());` diff --git a/src/tools/clippy/tests/ui/shadow.rs b/src/tools/clippy/tests/ui/shadow.rs index 9be8c5e59d014..1b40a43d01962 100644 --- a/src/tools/clippy/tests/ui/shadow.rs +++ b/src/tools/clippy/tests/ui/shadow.rs @@ -1,7 +1,12 @@ //@aux-build:proc_macro_derive.rs:proc-macro #![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)] -#![allow(clippy::let_unit_value, clippy::needless_if)] +#![allow( + clippy::let_unit_value, + clippy::needless_if, + clippy::redundant_guards, + clippy::redundant_locals +)] extern crate proc_macro_derive; diff --git a/src/tools/clippy/tests/ui/shadow.stderr b/src/tools/clippy/tests/ui/shadow.stderr index 8321f6df224cf..88b02f53be12e 100644 --- a/src/tools/clippy/tests/ui/shadow.stderr +++ b/src/tools/clippy/tests/ui/shadow.stderr @@ -1,278 +1,278 @@ error: `x` is shadowed by itself in `x` - --> $DIR/shadow.rs:19:9 + --> $DIR/shadow.rs:24:9 | LL | let x = x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:18:9 + --> $DIR/shadow.rs:23:9 | LL | let x = 1; | ^ = note: `-D clippy::shadow-same` implied by `-D warnings` error: `mut x` is shadowed by itself in `&x` - --> $DIR/shadow.rs:20:13 + --> $DIR/shadow.rs:25:13 | LL | let mut x = &x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:19:9 + --> $DIR/shadow.rs:24:9 | LL | let x = x; | ^ error: `x` is shadowed by itself in `&mut x` - --> $DIR/shadow.rs:21:9 + --> $DIR/shadow.rs:26:9 | LL | let x = &mut x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:20:9 + --> $DIR/shadow.rs:25:9 | LL | let mut x = &x; | ^^^^^ error: `x` is shadowed by itself in `*x` - --> $DIR/shadow.rs:22:9 + --> $DIR/shadow.rs:27:9 | LL | let x = *x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:21:9 + --> $DIR/shadow.rs:26:9 | LL | let x = &mut x; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:27:9 + --> $DIR/shadow.rs:32:9 | LL | let x = x.0; | ^ | note: previous binding is here - --> $DIR/shadow.rs:26:9 + --> $DIR/shadow.rs:31:9 | LL | let x = ([[0]], ()); | ^ = note: `-D clippy::shadow-reuse` implied by `-D warnings` error: `x` is shadowed - --> $DIR/shadow.rs:28:9 + --> $DIR/shadow.rs:33:9 | LL | let x = x[0]; | ^ | note: previous binding is here - --> $DIR/shadow.rs:27:9 + --> $DIR/shadow.rs:32:9 | LL | let x = x.0; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:29:10 + --> $DIR/shadow.rs:34:10 | LL | let [x] = x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:28:9 + --> $DIR/shadow.rs:33:9 | LL | let x = x[0]; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:30:9 + --> $DIR/shadow.rs:35:9 | LL | let x = Some(x); | ^ | note: previous binding is here - --> $DIR/shadow.rs:29:10 + --> $DIR/shadow.rs:34:10 | LL | let [x] = x; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:31:9 + --> $DIR/shadow.rs:36:9 | LL | let x = foo(x); | ^ | note: previous binding is here - --> $DIR/shadow.rs:30:9 + --> $DIR/shadow.rs:35:9 | LL | let x = Some(x); | ^ error: `x` is shadowed - --> $DIR/shadow.rs:32:9 + --> $DIR/shadow.rs:37:9 | LL | let x = || x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:31:9 + --> $DIR/shadow.rs:36:9 | LL | let x = foo(x); | ^ error: `x` is shadowed - --> $DIR/shadow.rs:33:9 + --> $DIR/shadow.rs:38:9 | LL | let x = Some(1).map(|_| x)?; | ^ | note: previous binding is here - --> $DIR/shadow.rs:32:9 + --> $DIR/shadow.rs:37:9 | LL | let x = || x; | ^ error: `y` is shadowed - --> $DIR/shadow.rs:35:9 + --> $DIR/shadow.rs:40:9 | LL | let y = match y { | ^ | note: previous binding is here - --> $DIR/shadow.rs:34:9 + --> $DIR/shadow.rs:39:9 | LL | let y = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:50:9 + --> $DIR/shadow.rs:55:9 | LL | let x = 2; | ^ | note: previous binding is here - --> $DIR/shadow.rs:49:9 + --> $DIR/shadow.rs:54:9 | LL | let x = 1; | ^ = note: `-D clippy::shadow-unrelated` implied by `-D warnings` error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:55:13 + --> $DIR/shadow.rs:60:13 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:54:10 + --> $DIR/shadow.rs:59:10 | LL | fn f(x: u32) { | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:60:14 + --> $DIR/shadow.rs:65:14 | LL | Some(x) => { | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:61:17 + --> $DIR/shadow.rs:66:17 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:60:14 + --> $DIR/shadow.rs:65:14 | LL | Some(x) => { | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:65:17 + --> $DIR/shadow.rs:70:17 | LL | if let Some(x) = Some(1) {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:66:20 + --> $DIR/shadow.rs:71:20 | LL | while let Some(x) = Some(1) {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:67:15 + --> $DIR/shadow.rs:72:15 | LL | let _ = |[x]: [u32; 1]| { | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:68:13 + --> $DIR/shadow.rs:73:13 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:67:15 + --> $DIR/shadow.rs:72:15 | LL | let _ = |[x]: [u32; 1]| { | ^ error: `y` is shadowed - --> $DIR/shadow.rs:71:17 + --> $DIR/shadow.rs:76:17 | LL | if let Some(y) = y {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:70:9 + --> $DIR/shadow.rs:75:9 | LL | let y = Some(1); | ^ error: `_b` shadows a previous, unrelated binding - --> $DIR/shadow.rs:107:9 + --> $DIR/shadow.rs:112:9 | LL | let _b = _a; | ^^ | note: previous binding is here - --> $DIR/shadow.rs:106:28 + --> $DIR/shadow.rs:111:28 | LL | pub async fn foo2(_a: i32, _b: i64) { | ^^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:113:21 + --> $DIR/shadow.rs:118:21 | LL | if let Some(x) = Some(1) { x } else { 1 } | ^ | note: previous binding is here - --> $DIR/shadow.rs:112:13 + --> $DIR/shadow.rs:117:13 | LL | let x = 1; | ^ diff --git a/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr index 2ae9fa34d142b..a3bb05bf176ba 100644 --- a/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr +++ b/src/tools/clippy/tests/ui/should_impl_trait/method_list_2.stderr @@ -39,15 +39,6 @@ LL | | } | = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name -error: this argument is a mutable reference, but not used mutably - --> $DIR/method_list_2.rs:38:31 - | -LL | pub fn hash(&self, state: &mut T) { - | ^^^^^^ help: consider changing to: `&T` - | - = warning: changing this function will impact semver compatibility - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: method `index` can be confused for the standard trait method `std::ops::Index::index` --> $DIR/method_list_2.rs:42:5 | @@ -158,5 +149,14 @@ LL | | } | = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name +error: this argument is a mutable reference, but not used mutably + --> $DIR/method_list_2.rs:38:31 + | +LL | pub fn hash(&self, state: &mut T) { + | ^^^^^^ help: consider changing to: `&T` + | + = warning: changing this function will impact semver compatibility + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.fixed b/src/tools/clippy/tests/ui/significant_drop_tightening.fixed index eb8524167c4a3..8065e9e5fbc92 100644 --- a/src/tools/clippy/tests/ui/significant_drop_tightening.fixed +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.fixed @@ -51,6 +51,33 @@ pub fn issue_11128() { } } +pub fn issue_11160() -> bool { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + true +} + +pub fn issue_11189() { + struct Number { + pub value: u32, + } + + fn do_something() -> Result<(), ()> { + let number = Mutex::new(Number { value: 1 }); + let number2 = Mutex::new(Number { value: 2 }); + let number3 = Mutex::new(Number { value: 3 }); + let mut lock = number.lock().unwrap(); + let mut lock2 = number2.lock().unwrap(); + let mut lock3 = number3.lock().unwrap(); + lock.value += 1; + lock2.value += 1; + lock3.value += 1; + drop((lock, lock2, lock3)); + Ok(()) + } +} + pub fn path_return_can_be_ignored() -> i32 { let mutex = Mutex::new(1); let lock = mutex.lock().unwrap(); diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.rs b/src/tools/clippy/tests/ui/significant_drop_tightening.rs index f7fa65ea92270..1620b76843a0b 100644 --- a/src/tools/clippy/tests/ui/significant_drop_tightening.rs +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.rs @@ -50,6 +50,33 @@ pub fn issue_11128() { } } +pub fn issue_11160() -> bool { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + true +} + +pub fn issue_11189() { + struct Number { + pub value: u32, + } + + fn do_something() -> Result<(), ()> { + let number = Mutex::new(Number { value: 1 }); + let number2 = Mutex::new(Number { value: 2 }); + let number3 = Mutex::new(Number { value: 3 }); + let mut lock = number.lock().unwrap(); + let mut lock2 = number2.lock().unwrap(); + let mut lock3 = number3.lock().unwrap(); + lock.value += 1; + lock2.value += 1; + lock3.value += 1; + drop((lock, lock2, lock3)); + Ok(()) + } +} + pub fn path_return_can_be_ignored() -> i32 { let mutex = Mutex::new(1); let lock = mutex.lock().unwrap(); diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.stderr b/src/tools/clippy/tests/ui/significant_drop_tightening.stderr index ca4fede17c93a..b5cad88ad3ffe 100644 --- a/src/tools/clippy/tests/ui/significant_drop_tightening.stderr +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.stderr @@ -23,7 +23,7 @@ LL + drop(lock); | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:79:13 + --> $DIR/significant_drop_tightening.rs:106:13 | LL | / { LL | | let mutex = Mutex::new(1i32); @@ -43,7 +43,7 @@ LL + drop(lock); | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:100:13 + --> $DIR/significant_drop_tightening.rs:127:13 | LL | / { LL | | let mutex = Mutex::new(1i32); @@ -67,7 +67,7 @@ LL + | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:106:17 + --> $DIR/significant_drop_tightening.rs:133:17 | LL | / { LL | | let mutex = Mutex::new(vec![1i32]); diff --git a/src/tools/clippy/tests/ui/single_match.fixed b/src/tools/clippy/tests/ui/single_match.fixed index e7b1fd6a85f22..163ba94aff802 100644 --- a/src/tools/clippy/tests/ui/single_match.fixed +++ b/src/tools/clippy/tests/ui/single_match.fixed @@ -4,6 +4,7 @@ unused, clippy::uninlined_format_args, clippy::needless_if, + clippy::redundant_guards, clippy::redundant_pattern_matching )] fn dummy() {} diff --git a/src/tools/clippy/tests/ui/single_match.rs b/src/tools/clippy/tests/ui/single_match.rs index 1515a7053e5dd..0dcdb125ffd8a 100644 --- a/src/tools/clippy/tests/ui/single_match.rs +++ b/src/tools/clippy/tests/ui/single_match.rs @@ -4,6 +4,7 @@ unused, clippy::uninlined_format_args, clippy::needless_if, + clippy::redundant_guards, clippy::redundant_pattern_matching )] fn dummy() {} diff --git a/src/tools/clippy/tests/ui/single_match.stderr b/src/tools/clippy/tests/ui/single_match.stderr index 76f7e78958985..d35361599493e 100644 --- a/src/tools/clippy/tests/ui/single_match.stderr +++ b/src/tools/clippy/tests/ui/single_match.stderr @@ -1,5 +1,5 @@ error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:14:5 + --> $DIR/single_match.rs:15:5 | LL | / match x { LL | | Some(y) => { @@ -18,7 +18,7 @@ LL ~ }; | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:22:5 + --> $DIR/single_match.rs:23:5 | LL | / match x { LL | | // Note the missing block braces. @@ -30,7 +30,7 @@ LL | | } | |_____^ help: try: `if let Some(y) = x { println!("{:?}", y) }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:31:5 + --> $DIR/single_match.rs:32:5 | LL | / match z { LL | | (2..=3, 7..=9) => dummy(), @@ -39,7 +39,7 @@ LL | | }; | |_____^ help: try: `if let (2..=3, 7..=9) = z { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:60:5 + --> $DIR/single_match.rs:61:5 | LL | / match x { LL | | Some(y) => dummy(), @@ -48,7 +48,7 @@ LL | | }; | |_____^ help: try: `if let Some(y) = x { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:65:5 + --> $DIR/single_match.rs:66:5 | LL | / match y { LL | | Ok(y) => dummy(), @@ -57,7 +57,7 @@ LL | | }; | |_____^ help: try: `if let Ok(y) = y { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:72:5 + --> $DIR/single_match.rs:73:5 | LL | / match c { LL | | Cow::Borrowed(..) => dummy(), @@ -66,7 +66,7 @@ LL | | }; | |_____^ help: try: `if let Cow::Borrowed(..) = c { dummy() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:93:5 + --> $DIR/single_match.rs:94:5 | LL | / match x { LL | | "test" => println!(), @@ -75,7 +75,7 @@ LL | | } | |_____^ help: try: `if x == "test" { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:106:5 + --> $DIR/single_match.rs:107:5 | LL | / match x { LL | | Foo::A => println!(), @@ -84,7 +84,7 @@ LL | | } | |_____^ help: try: `if x == Foo::A { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:112:5 + --> $DIR/single_match.rs:113:5 | LL | / match x { LL | | FOO_C => println!(), @@ -93,7 +93,7 @@ LL | | } | |_____^ help: try: `if x == FOO_C { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:117:5 + --> $DIR/single_match.rs:118:5 | LL | / match &&x { LL | | Foo::A => println!(), @@ -102,7 +102,7 @@ LL | | } | |_____^ help: try: `if x == Foo::A { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:123:5 + --> $DIR/single_match.rs:124:5 | LL | / match &x { LL | | Foo::A => println!(), @@ -111,7 +111,7 @@ LL | | } | |_____^ help: try: `if x == &Foo::A { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:140:5 + --> $DIR/single_match.rs:141:5 | LL | / match x { LL | | Bar::A => println!(), @@ -120,7 +120,7 @@ LL | | } | |_____^ help: try: `if let Bar::A = x { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:148:5 + --> $DIR/single_match.rs:149:5 | LL | / match x { LL | | None => println!(), @@ -129,7 +129,7 @@ LL | | }; | |_____^ help: try: `if let None = x { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:170:5 + --> $DIR/single_match.rs:171:5 | LL | / match x { LL | | (Some(_), _) => {}, @@ -138,7 +138,7 @@ LL | | } | |_____^ help: try: `if let (Some(_), _) = x {}` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:176:5 + --> $DIR/single_match.rs:177:5 | LL | / match x { LL | | (Some(E::V), _) => todo!(), @@ -147,7 +147,7 @@ LL | | } | |_____^ help: try: `if let (Some(E::V), _) = x { todo!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:182:5 + --> $DIR/single_match.rs:183:5 | LL | / match (Some(42), Some(E::V), Some(42)) { LL | | (.., Some(E::V), _) => {}, @@ -156,7 +156,7 @@ LL | | } | |_____^ help: try: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:254:5 + --> $DIR/single_match.rs:255:5 | LL | / match bar { LL | | Some(v) => unsafe { @@ -176,7 +176,7 @@ LL + } } | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:262:5 + --> $DIR/single_match.rs:263:5 | LL | / match bar { LL | | #[rustfmt::skip] diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.rs b/src/tools/clippy/tests/ui/slow_vector_initialization.rs index 16be9f6d203aa..cfb856861b8c6 100644 --- a/src/tools/clippy/tests/ui/slow_vector_initialization.rs +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.rs @@ -4,6 +4,7 @@ fn main() { resize_vector(); extend_vector(); mixed_extend_resize_vector(); + from_empty_vec(); } fn extend_vector() { @@ -59,6 +60,21 @@ fn resize_vector() { vec1.resize(10, 0); } +fn from_empty_vec() { + // Resize with constant expression + let len = 300; + let mut vec1 = Vec::new(); + vec1.resize(len, 0); + + // Resize with len expression + let mut vec3 = Vec::new(); + vec3.resize(len - 10, 0); + + // Reinitialization should be warned + vec1 = Vec::new(); + vec1.resize(10, 0); +} + fn do_stuff(vec: &mut [u8]) {} fn extend_vector_with_manipulations_between() { diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr index 22376680a8e6b..532ce4ac19113 100644 --- a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr @@ -1,84 +1,108 @@ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:13:5 + --> $DIR/slow_vector_initialization.rs:14:5 | LL | let mut vec1 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec1.extend(repeat(0).take(len)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::slow-vector-initialization` implied by `-D warnings` error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:17:5 + --> $DIR/slow_vector_initialization.rs:18:5 | LL | let mut vec2 = Vec::with_capacity(len - 10); - | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` + | ---------------------------- help: consider replacing this with: `vec![0; len - 10]` LL | vec2.extend(repeat(0).take(len - 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:24:5 + --> $DIR/slow_vector_initialization.rs:25:5 | LL | let mut vec4 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec4.extend(repeat(0).take(vec4.capacity())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:34:5 + --> $DIR/slow_vector_initialization.rs:35:5 | LL | let mut resized_vec = Vec::with_capacity(30); - | ---------------------- help: consider replace allocation with: `vec![0; 30]` + | ---------------------- help: consider replacing this with: `vec![0; 30]` LL | resized_vec.resize(30, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:37:5 + --> $DIR/slow_vector_initialization.rs:38:5 | LL | let mut extend_vec = Vec::with_capacity(30); - | ---------------------- help: consider replace allocation with: `vec![0; 30]` + | ---------------------- help: consider replacing this with: `vec![0; 30]` LL | extend_vec.extend(repeat(0).take(30)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:44:5 + --> $DIR/slow_vector_initialization.rs:45:5 | LL | let mut vec1 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec1.resize(len, 0); | ^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:52:5 + --> $DIR/slow_vector_initialization.rs:53:5 | LL | let mut vec3 = Vec::with_capacity(len - 10); - | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` + | ---------------------------- help: consider replacing this with: `vec![0; len - 10]` LL | vec3.resize(len - 10, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:55:5 + --> $DIR/slow_vector_initialization.rs:56:5 | LL | let mut vec4 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec4.resize(vec4.capacity(), 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:59:5 + --> $DIR/slow_vector_initialization.rs:60:5 | LL | vec1 = Vec::with_capacity(10); - | ---------------------- help: consider replace allocation with: `vec![0; 10]` + | ---------------------- help: consider replacing this with: `vec![0; 10]` +LL | vec1.resize(10, 0); + | ^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:67:5 + | +LL | let mut vec1 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; len]` +LL | vec1.resize(len, 0); + | ^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:71:5 + | +LL | let mut vec3 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; len - 10]` +LL | vec3.resize(len - 10, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:75:5 + | +LL | vec1 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; 10]` LL | vec1.resize(10, 0); | ^^^^^^^^^^^^^^^^^^ error: this argument is a mutable reference, but not used mutably - --> $DIR/slow_vector_initialization.rs:62:18 + --> $DIR/slow_vector_initialization.rs:78:18 | LL | fn do_stuff(vec: &mut [u8]) {} | ^^^^^^^^^ help: consider changing to: `&[u8]` | = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` -error: aborting due to 10 previous errors +error: aborting due to 13 previous errors diff --git a/src/tools/clippy/tests/ui/string_lit_chars_any.fixed b/src/tools/clippy/tests/ui/string_lit_chars_any.fixed new file mode 100644 index 0000000000000..d7ab9c3397a12 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_chars_any.fixed @@ -0,0 +1,50 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::eq_op, clippy::needless_raw_string_hashes, clippy::no_effect, unused)] +#![warn(clippy::string_lit_chars_any)] + +#[macro_use] +extern crate proc_macros; + +struct NotStringLit; + +impl NotStringLit { + fn chars(&self) -> impl Iterator { + "c".chars() + } +} + +fn main() { + let c = 'c'; + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + #[rustfmt::skip] + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + // Do not lint + NotStringLit.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + let c = 'c'; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + 1; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == x); + "\\.+*?()|[]{}^$#&-~".chars().any(|_x| c == c); + matches!( + c, + '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~' + ); + external! { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } + with_span! { + span + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } +} diff --git a/src/tools/clippy/tests/ui/string_lit_chars_any.rs b/src/tools/clippy/tests/ui/string_lit_chars_any.rs new file mode 100644 index 0000000000000..9408d7bb2390c --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_chars_any.rs @@ -0,0 +1,50 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::eq_op, clippy::needless_raw_string_hashes, clippy::no_effect, unused)] +#![warn(clippy::string_lit_chars_any)] + +#[macro_use] +extern crate proc_macros; + +struct NotStringLit; + +impl NotStringLit { + fn chars(&self) -> impl Iterator { + "c".chars() + } +} + +fn main() { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + r#"\.+*?()|[]{}^$#&-~"#.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| c == x); + r#"\.+*?()|[]{}^$#&-~"#.chars().any(|x| c == x); + #[rustfmt::skip] + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { x == c }); + // Do not lint + NotStringLit.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + let c = 'c'; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + 1; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == x); + "\\.+*?()|[]{}^$#&-~".chars().any(|_x| c == c); + matches!( + c, + '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~' + ); + external! { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } + with_span! { + span + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } +} diff --git a/src/tools/clippy/tests/ui/string_lit_chars_any.stderr b/src/tools/clippy/tests/ui/string_lit_chars_any.stderr new file mode 100644 index 0000000000000..ff951b73deddf --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_chars_any.stderr @@ -0,0 +1,58 @@ +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:19:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::string-lit-chars-any` implied by `-D warnings` +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:20:5 + | +LL | r#"/.+*?()|[]{}^$#&-~"#.chars().any(|x| x == c); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:21:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| c == x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:22:5 + | +LL | r#"/.+*?()|[]{}^$#&-~"#.chars().any(|x| c == x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:24:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| { x == c }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/swap.fixed b/src/tools/clippy/tests/ui/swap.fixed index 22f904e3fd9e6..7b74a83b6df90 100644 --- a/src/tools/clippy/tests/ui/swap.fixed +++ b/src/tools/clippy/tests/ui/swap.fixed @@ -11,7 +11,8 @@ unused_assignments, unused_variables, clippy::let_and_return, - clippy::useless_vec + clippy::useless_vec, + clippy::redundant_locals )] struct Foo(u32); diff --git a/src/tools/clippy/tests/ui/swap.rs b/src/tools/clippy/tests/ui/swap.rs index ada64f89e6d27..93855cd7b5c29 100644 --- a/src/tools/clippy/tests/ui/swap.rs +++ b/src/tools/clippy/tests/ui/swap.rs @@ -11,7 +11,8 @@ unused_assignments, unused_variables, clippy::let_and_return, - clippy::useless_vec + clippy::useless_vec, + clippy::redundant_locals )] struct Foo(u32); diff --git a/src/tools/clippy/tests/ui/swap.stderr b/src/tools/clippy/tests/ui/swap.stderr index a3b9c2b744c98..1097b29bba022 100644 --- a/src/tools/clippy/tests/ui/swap.stderr +++ b/src/tools/clippy/tests/ui/swap.stderr @@ -1,5 +1,5 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:28:5 + --> $DIR/swap.rs:29: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:40:5 + --> $DIR/swap.rs:41: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:49:5 + --> $DIR/swap.rs:50: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:68:5 + --> $DIR/swap.rs:69: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:79:5 + --> $DIR/swap.rs:80: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:87:5 + --> $DIR/swap.rs:88: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:95:5 + --> $DIR/swap.rs:96: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:124:5 + --> $DIR/swap.rs:125: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:138:7 + --> $DIR/swap.rs:139: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:147:7 + --> $DIR/swap.rs:148: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:173:5 + --> $DIR/swap.rs:174: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:135:5 + --> $DIR/swap.rs:136: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:144:5 + --> $DIR/swap.rs:145: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:151:5 + --> $DIR/swap.rs:152: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:156:5 + --> $DIR/swap.rs:157: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:160:5 + --> $DIR/swap.rs:161: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:208:5 + --> $DIR/swap.rs:209:5 | LL | / let t = s.0.x; LL | | s.0.x = s.0.y; diff --git a/src/tools/clippy/tests/ui/try_err.fixed b/src/tools/clippy/tests/ui/try_err.fixed index 1816740870ad6..930489fab7646 100644 --- a/src/tools/clippy/tests/ui/try_err.fixed +++ b/src/tools/clippy/tests/ui/try_err.fixed @@ -2,7 +2,11 @@ //@aux-build:proc_macros.rs:proc-macro #![deny(clippy::try_err)] -#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] +#![allow( + clippy::unnecessary_wraps, + clippy::needless_question_mark, + clippy::needless_return_with_question_mark +)] extern crate proc_macros; use proc_macros::{external, inline_macros}; diff --git a/src/tools/clippy/tests/ui/try_err.rs b/src/tools/clippy/tests/ui/try_err.rs index 0e47c4d023ef7..f5baf3d8f744d 100644 --- a/src/tools/clippy/tests/ui/try_err.rs +++ b/src/tools/clippy/tests/ui/try_err.rs @@ -2,7 +2,11 @@ //@aux-build:proc_macros.rs:proc-macro #![deny(clippy::try_err)] -#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] +#![allow( + clippy::unnecessary_wraps, + clippy::needless_question_mark, + clippy::needless_return_with_question_mark +)] extern crate proc_macros; use proc_macros::{external, inline_macros}; diff --git a/src/tools/clippy/tests/ui/try_err.stderr b/src/tools/clippy/tests/ui/try_err.stderr index 79f7b70224a19..9968b383ebbc9 100644 --- a/src/tools/clippy/tests/ui/try_err.stderr +++ b/src/tools/clippy/tests/ui/try_err.stderr @@ -1,5 +1,5 @@ error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:19:9 + --> $DIR/try_err.rs:23:9 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err)` @@ -11,25 +11,25 @@ LL | #![deny(clippy::try_err)] | ^^^^^^^^^^^^^^^ error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:29:9 + --> $DIR/try_err.rs:33:9 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err.into())` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:49:17 + --> $DIR/try_err.rs:53:17 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err)` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:68:17 + --> $DIR/try_err.rs:72:17 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err.into())` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:88:23 + --> $DIR/try_err.rs:92:23 | LL | Err(_) => Err(1)?, | ^^^^^^^ help: try: `return Err(1)` @@ -37,7 +37,7 @@ LL | Err(_) => Err(1)?, = 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:95:23 + --> $DIR/try_err.rs:99:23 | LL | Err(_) => Err(inline!(1))?, | ^^^^^^^^^^^^^^^^ help: try: `return Err(inline!(1))` @@ -45,31 +45,31 @@ LL | Err(_) => Err(inline!(1))?, = 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:122:9 + --> $DIR/try_err.rs:126:9 | LL | Err(inline!(inline!(String::from("aasdfasdfasdfa"))))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Err(inline!(inline!(String::from("aasdfasdfasdfa"))))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:129:9 + --> $DIR/try_err.rs:133:9 | LL | Err(io::ErrorKind::WriteZero)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:131:9 + --> $DIR/try_err.rs:135:9 | LL | Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidInput, "error")))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:139:9 + --> $DIR/try_err.rs:143:9 | LL | Err(io::ErrorKind::NotFound)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Some(Err(io::ErrorKind::NotFound.into())))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:148:16 + --> $DIR/try_err.rs:152:16 | LL | return Err(42)?; | ^^^^^^^^ help: try: `Err(42)` diff --git a/src/tools/clippy/tests/ui/tuple_array_conversions.rs b/src/tools/clippy/tests/ui/tuple_array_conversions.rs index f96a7c97f1aa6..569415acbceee 100644 --- a/src/tools/clippy/tests/ui/tuple_array_conversions.rs +++ b/src/tools/clippy/tests/ui/tuple_array_conversions.rs @@ -52,6 +52,36 @@ fn main() { let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); } + // FP #11082; needs discussion + let (a, b) = (1.0f64, 2.0f64); + let _: &[f64] = &[a, b]; + // FP #11085; impossible to fix + let [src, dest]: [_; 2] = [1, 2]; + (src, dest); + // FP #11100 + fn issue_11100_array_to_tuple(this: [&mut i32; 2]) -> (&i32, &mut i32) { + let [input, output] = this; + (input, output) + } + + fn issue_11100_tuple_to_array<'a>(this: (&'a mut i32, &'a mut i32)) -> [&'a i32; 2] { + let (input, output) = this; + [input, output] + } + // FP #11124 + // tuple=>array + let (a, b) = (1, 2); + [a, b]; + let x = a; + // array=>tuple + let [a, b] = [1, 2]; + (a, b); + let x = a; + // FP #11144 + let (a, (b, c)) = (1, (2, 3)); + [a, c]; + let [[a, b], [c, d]] = [[1, 2], [3, 4]]; + (a, c); } #[clippy::msrv = "1.70.0"] diff --git a/src/tools/clippy/tests/ui/tuple_array_conversions.stderr b/src/tools/clippy/tests/ui/tuple_array_conversions.stderr index be653e8efb799..50bdcf29d1fad 100644 --- a/src/tools/clippy/tests/ui/tuple_array_conversions.stderr +++ b/src/tools/clippy/tests/ui/tuple_array_conversions.stderr @@ -15,14 +15,6 @@ LL | let x = [x.0, x.1]; | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed -error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:13:13 - | -LL | let x = (x[0], x[1]); - | ^^^^^^^^^^^^ - | - = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed - error: it looks like you're trying to convert a tuple to an array --> $DIR/tuple_array_conversions.rs:16:53 | @@ -55,8 +47,24 @@ LL | t1.iter().for_each(|&(a, b)| _ = [a, b]); | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:57:22 + | +LL | let _: &[f64] = &[a, b]; + | ^^^^^^ + | + = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed + error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:69:13 + --> $DIR/tuple_array_conversions.rs:60:5 + | +LL | (src, dest); + | ^^^^^^^^^^^ + | + = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed + +error: it looks like you're trying to convert an array to a tuple + --> $DIR/tuple_array_conversions.rs:99:13 | LL | let x = (x[0], x[1]); | ^^^^^^^^^^^^ @@ -64,20 +72,12 @@ LL | let x = (x[0], x[1]); = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed error: it looks like you're trying to convert a tuple to an array - --> $DIR/tuple_array_conversions.rs:70:13 + --> $DIR/tuple_array_conversions.rs:100:13 | LL | let x = [x.0, x.1]; | ^^^^^^^^^^ | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed -error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:72:13 - | -LL | let x = (x[0], x[1]); - | ^^^^^^^^^^^^ - | - = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed - error: aborting due to 10 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.fixed b/src/tools/clippy/tests/ui/unnecessary_cast.fixed index 8efd44baf59fe..2bf02f1341425 100644 --- a/src/tools/clippy/tests/ui/unnecessary_cast.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_cast.fixed @@ -38,6 +38,16 @@ mod fake_libc { } } +fn aaa() -> ::std::primitive::u32 { + 0 +} + +use std::primitive::u32 as UnsignedThirtyTwoBitInteger; + +fn bbb() -> UnsignedThirtyTwoBitInteger { + 0 +} + #[rustfmt::skip] fn main() { // Test cast_unnecessary @@ -105,6 +115,13 @@ fn main() { extern_fake_libc::getpid_SAFE_TRUTH() as i32; let pid = unsafe { fake_libc::getpid() }; pid as i32; + aaa(); + let x = aaa(); + aaa(); + // Will not lint currently. + bbb() as u32; + let x = bbb(); + bbb() as u32; let i8_ptr: *const i8 = &1; let u8_ptr: *const u8 = &1; diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.rs b/src/tools/clippy/tests/ui/unnecessary_cast.rs index c7723ef51f990..25b6b0f9b07ba 100644 --- a/src/tools/clippy/tests/ui/unnecessary_cast.rs +++ b/src/tools/clippy/tests/ui/unnecessary_cast.rs @@ -38,6 +38,16 @@ mod fake_libc { } } +fn aaa() -> ::std::primitive::u32 { + 0 +} + +use std::primitive::u32 as UnsignedThirtyTwoBitInteger; + +fn bbb() -> UnsignedThirtyTwoBitInteger { + 0 +} + #[rustfmt::skip] fn main() { // Test cast_unnecessary @@ -105,6 +115,13 @@ fn main() { extern_fake_libc::getpid_SAFE_TRUTH() as i32; let pid = unsafe { fake_libc::getpid() }; pid as i32; + aaa() as u32; + let x = aaa(); + aaa() as u32; + // Will not lint currently. + bbb() as u32; + let x = bbb(); + bbb() as u32; let i8_ptr: *const i8 = &1; let u8_ptr: *const u8 = &1; diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.stderr b/src/tools/clippy/tests/ui/unnecessary_cast.stderr index f0443556fb4d5..19411a01b67d3 100644 --- a/src/tools/clippy/tests/ui/unnecessary_cast.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_cast.stderr @@ -7,226 +7,238 @@ LL | ptr as *const T = note: `-D clippy::unnecessary-cast` implied by `-D warnings` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:44:5 + --> $DIR/unnecessary_cast.rs:54:5 | LL | 1i32 as i32; | ^^^^^^^^^^^ help: try: `1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:45:5 + --> $DIR/unnecessary_cast.rs:55:5 | LL | 1f32 as f32; | ^^^^^^^^^^^ help: try: `1_f32` error: casting to the same type is unnecessary (`bool` -> `bool`) - --> $DIR/unnecessary_cast.rs:46:5 + --> $DIR/unnecessary_cast.rs:56:5 | LL | false as bool; | ^^^^^^^^^^^^^ help: try: `false` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:49:5 + --> $DIR/unnecessary_cast.rs:59:5 | LL | -1_i32 as i32; | ^^^^^^^^^^^^^ help: try: `-1_i32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:50:5 + --> $DIR/unnecessary_cast.rs:60:5 | LL | - 1_i32 as i32; | ^^^^^^^^^^^^^^ help: try: `- 1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:51:5 + --> $DIR/unnecessary_cast.rs:61:5 | LL | -1f32 as f32; | ^^^^^^^^^^^^ help: try: `-1_f32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:52:5 + --> $DIR/unnecessary_cast.rs:62:5 | LL | 1_i32 as i32; | ^^^^^^^^^^^^ help: try: `1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:53:5 + --> $DIR/unnecessary_cast.rs:63:5 | LL | 1_f32 as f32; | ^^^^^^^^^^^^ help: try: `1_f32` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:55:22 + --> $DIR/unnecessary_cast.rs:65:22 | LL | let _: *mut u8 = [1u8, 2].as_ptr() as *const u8 as *mut u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:57:5 + --> $DIR/unnecessary_cast.rs:67:5 | LL | [1u8, 2].as_ptr() as *const u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*mut u8` -> `*mut u8`) - --> $DIR/unnecessary_cast.rs:59:5 + --> $DIR/unnecessary_cast.rs:69:5 | LL | [1u8, 2].as_mut_ptr() as *mut u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_mut_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*const u32` -> `*const u32`) - --> $DIR/unnecessary_cast.rs:70:5 + --> $DIR/unnecessary_cast.rs:80:5 | LL | owo::([1u32].as_ptr()) as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `owo::([1u32].as_ptr())` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:71:5 + --> $DIR/unnecessary_cast.rs:81:5 | LL | uwu::([1u32].as_ptr()) as *const u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `uwu::([1u32].as_ptr())` error: casting raw pointers to the same type and constness is unnecessary (`*const u32` -> `*const u32`) - --> $DIR/unnecessary_cast.rs:73:5 + --> $DIR/unnecessary_cast.rs:83:5 | LL | uwu::([1u32].as_ptr()) as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `uwu::([1u32].as_ptr())` +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> $DIR/unnecessary_cast.rs:118:5 + | +LL | aaa() as u32; + | ^^^^^^^^^^^^ help: try: `aaa()` + +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> $DIR/unnecessary_cast.rs:120:5 + | +LL | aaa() as u32; + | ^^^^^^^^^^^^ help: try: `aaa()` + error: casting integer literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:139:9 + --> $DIR/unnecessary_cast.rs:156:9 | LL | 100 as f32; | ^^^^^^^^^^ help: try: `100_f32` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:140:9 + --> $DIR/unnecessary_cast.rs:157:9 | LL | 100 as f64; | ^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:141:9 + --> $DIR/unnecessary_cast.rs:158:9 | LL | 100_i32 as f64; | ^^^^^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:142:17 + --> $DIR/unnecessary_cast.rs:159:17 | LL | let _ = -100 as f32; | ^^^^^^^^^^^ help: try: `-100_f32` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:143:17 + --> $DIR/unnecessary_cast.rs:160:17 | LL | let _ = -100 as f64; | ^^^^^^^^^^^ help: try: `-100_f64` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:144:17 + --> $DIR/unnecessary_cast.rs:161:17 | LL | let _ = -100_i32 as f64; | ^^^^^^^^^^^^^^^ help: try: `-100_f64` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:145:9 + --> $DIR/unnecessary_cast.rs:162:9 | LL | 100. as f32; | ^^^^^^^^^^^ help: try: `100_f32` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:146:9 + --> $DIR/unnecessary_cast.rs:163:9 | LL | 100. as f64; | ^^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `u32` is unnecessary - --> $DIR/unnecessary_cast.rs:158:9 + --> $DIR/unnecessary_cast.rs:175:9 | LL | 1 as u32; | ^^^^^^^^ help: try: `1_u32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:159:9 + --> $DIR/unnecessary_cast.rs:176:9 | LL | 0x10 as i32; | ^^^^^^^^^^^ help: try: `0x10_i32` error: casting integer literal to `usize` is unnecessary - --> $DIR/unnecessary_cast.rs:160:9 + --> $DIR/unnecessary_cast.rs:177:9 | LL | 0b10 as usize; | ^^^^^^^^^^^^^ help: try: `0b10_usize` error: casting integer literal to `u16` is unnecessary - --> $DIR/unnecessary_cast.rs:161:9 + --> $DIR/unnecessary_cast.rs:178:9 | LL | 0o73 as u16; | ^^^^^^^^^^^ help: try: `0o73_u16` error: casting integer literal to `u32` is unnecessary - --> $DIR/unnecessary_cast.rs:162:9 + --> $DIR/unnecessary_cast.rs:179:9 | LL | 1_000_000_000 as u32; | ^^^^^^^^^^^^^^^^^^^^ help: try: `1_000_000_000_u32` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:164:9 + --> $DIR/unnecessary_cast.rs:181:9 | LL | 1.0 as f64; | ^^^^^^^^^^ help: try: `1.0_f64` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:165:9 + --> $DIR/unnecessary_cast.rs:182:9 | LL | 0.5 as f32; | ^^^^^^^^^^ help: try: `0.5_f32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:169:17 + --> $DIR/unnecessary_cast.rs:186:17 | LL | let _ = -1 as i32; | ^^^^^^^^^ help: try: `-1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:170:17 + --> $DIR/unnecessary_cast.rs:187:17 | LL | let _ = -1.0 as f32; | ^^^^^^^^^^^ help: try: `-1.0_f32` error: casting to the same type is unnecessary (`i32` -> `i32`) - --> $DIR/unnecessary_cast.rs:176:18 + --> $DIR/unnecessary_cast.rs:193:18 | LL | let _ = &(x as i32); | ^^^^^^^^^^ help: try: `{ x }` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:182:22 + --> $DIR/unnecessary_cast.rs:199:22 | LL | let _: i32 = -(1) as i32; | ^^^^^^^^^^^ help: try: `-1_i32` error: casting integer literal to `i64` is unnecessary - --> $DIR/unnecessary_cast.rs:184:22 + --> $DIR/unnecessary_cast.rs:201:22 | LL | let _: i64 = -(1) as i64; | ^^^^^^^^^^^ help: try: `-1_i64` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:191:22 + --> $DIR/unnecessary_cast.rs:208:22 | LL | let _: f64 = (-8.0 as f64).exp(); | ^^^^^^^^^^^^^ help: try: `(-8.0_f64)` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:193:23 + --> $DIR/unnecessary_cast.rs:210:23 | LL | let _: f64 = -(8.0 as f64).exp(); // should suggest `-8.0_f64.exp()` here not to change code behavior | ^^^^^^^^^^^^ help: try: `8.0_f64` error: casting to the same type is unnecessary (`f32` -> `f32`) - --> $DIR/unnecessary_cast.rs:201:20 + --> $DIR/unnecessary_cast.rs:218:20 | LL | let _num = foo() as f32; | ^^^^^^^^^^^^ help: try: `foo()` -error: aborting due to 38 previous errors +error: aborting due to 40 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs index 8e01c2674f167..3c8c6ec94c1b6 100644 --- a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs @@ -148,3 +148,9 @@ mod comment_1052978898 { }) } } + +fn issue11260() { + // #11260 is about unnecessary_find_map, but the fix also kind of applies to + // unnecessary_filter_map + let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr index 5585b10ab903d..2d5403ce39444 100644 --- a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr @@ -34,5 +34,11 @@ error: this `.filter_map` can be written more simply using `.map` LL | let _ = (0..4).filter_map(|x| Some(x + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:155:14 + | +LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_find_map.rs b/src/tools/clippy/tests/ui/unnecessary_find_map.rs index a52390861b403..2c228fbbc9597 100644 --- a/src/tools/clippy/tests/ui/unnecessary_find_map.rs +++ b/src/tools/clippy/tests/ui/unnecessary_find_map.rs @@ -21,3 +21,9 @@ fn main() { fn find_map_none_changes_item_type() -> Option { "".chars().find_map(|_| None) } + +fn issue11260() { + let y = Some(1); + let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); + let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(y)); // different option, so can't be just `.find()` +} diff --git a/src/tools/clippy/tests/ui/unnecessary_find_map.stderr b/src/tools/clippy/tests/ui/unnecessary_find_map.stderr index fb33c122fe337..3a995b41b179e 100644 --- a/src/tools/clippy/tests/ui/unnecessary_find_map.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_find_map.stderr @@ -34,5 +34,11 @@ error: this `.find_map` can be written more simply using `.map(..).next()` LL | let _ = (0..4).find_map(|x| Some(x + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: this `.find_map` can be written more simply using `.find` + --> $DIR/unnecessary_find_map.rs:27:14 + | +LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.fixed b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.fixed index 276cd800b8907..72d52c62355c8 100644 --- a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.fixed @@ -16,12 +16,23 @@ fn unwrap_option_some() { 1; } +#[rustfmt::skip] // force rustfmt not to remove braces in `|| { 234 }` fn unwrap_option_none() { let _val = panic!(); let _val = panic!("this always happens"); + let _val: String = String::default(); + let _val: u16 = 234; + let _val: u16 = 234; + let _val: u16 = { 234 }; + let _val: u16 = { 234 }; panic!(); panic!("this always happens"); + String::default(); + 234; + 234; + { 234 }; + { 234 }; } fn unwrap_result_ok() { diff --git a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.rs b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.rs index 3065778d77901..7d713ea205f3c 100644 --- a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.rs +++ b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.rs @@ -16,12 +16,23 @@ fn unwrap_option_some() { Some(1).expect("this never happens"); } +#[rustfmt::skip] // force rustfmt not to remove braces in `|| { 234 }` fn unwrap_option_none() { let _val = None::<()>.unwrap(); let _val = None::<()>.expect("this always happens"); + let _val: String = None.unwrap_or_default(); + let _val: u16 = None.unwrap_or(234); + let _val: u16 = None.unwrap_or_else(|| 234); + let _val: u16 = None.unwrap_or_else(|| { 234 }); + let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); None::<()>.unwrap(); None::<()>.expect("this always happens"); + None::.unwrap_or_default(); + None::.unwrap_or(234); + None::.unwrap_or_else(|| 234); + None::.unwrap_or_else(|| { 234 }); + None::.unwrap_or_else(|| -> u16 { 234 }); } fn unwrap_result_ok() { diff --git a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.stderr b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.stderr index 5823313b736c9..7f603d6ef5826 100644 --- a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap.stderr @@ -48,13 +48,13 @@ LL + 1; | error: used `unwrap()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:20:16 + --> $DIR/unnecessary_literal_unwrap.rs:21:16 | LL | let _val = None::<()>.unwrap(); | ^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap()`: `panic!()` error: used `expect()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:21:16 + --> $DIR/unnecessary_literal_unwrap.rs:22:16 | LL | let _val = None::<()>.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -64,14 +64,68 @@ help: remove the `None` and `expect()` LL | let _val = panic!("this always happens"); | ~~~~~~~ ~ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:23:24 + | +LL | let _val: String = None.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `String::default()` + +error: used `unwrap_or()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:24:21 + | +LL | let _val: u16 = None.unwrap_or(234); + | ^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or()` + | +LL - let _val: u16 = None.unwrap_or(234); +LL + let _val: u16 = 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:25:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| 234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| 234); +LL + let _val: u16 = 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:26:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| { 234 }); +LL + let _val: u16 = { 234 }; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:27:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); +LL + let _val: u16 = { 234 }; + | + error: used `unwrap()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:23:5 + --> $DIR/unnecessary_literal_unwrap.rs:29:5 | LL | None::<()>.unwrap(); | ^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap()`: `panic!()` error: used `expect()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:24:5 + --> $DIR/unnecessary_literal_unwrap.rs:30:5 | LL | None::<()>.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,8 +135,62 @@ help: remove the `None` and `expect()` LL | panic!("this always happens"); | ~~~~~~~ ~ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:31:5 + | +LL | None::.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `String::default()` + +error: used `unwrap_or()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:32:5 + | +LL | None::.unwrap_or(234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or()` + | +LL - None::.unwrap_or(234); +LL + 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:33:5 + | +LL | None::.unwrap_or_else(|| 234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| 234); +LL + 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:34:5 + | +LL | None::.unwrap_or_else(|| { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| { 234 }); +LL + { 234 }; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:35:5 + | +LL | None::.unwrap_or_else(|| -> u16 { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| -> u16 { 234 }); +LL + { 234 }; + | + error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:28:16 + --> $DIR/unnecessary_literal_unwrap.rs:39:16 | LL | let _val = Ok::<_, ()>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,7 +202,7 @@ LL + let _val = 1; | error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:29:16 + --> $DIR/unnecessary_literal_unwrap.rs:40:16 | LL | let _val = Ok::<_, ()>(1).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -106,7 +214,7 @@ LL + let _val = 1; | error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:30:16 + --> $DIR/unnecessary_literal_unwrap.rs:41:16 | LL | let _val = Ok::<_, ()>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,7 +225,7 @@ LL | let _val = panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:31:16 + --> $DIR/unnecessary_literal_unwrap.rs:42:16 | LL | let _val = Ok::<_, ()>(1).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -128,7 +236,7 @@ LL | let _val = panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:33:5 + --> $DIR/unnecessary_literal_unwrap.rs:44:5 | LL | Ok::<_, ()>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -140,7 +248,7 @@ LL + 1; | error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:34:5 + --> $DIR/unnecessary_literal_unwrap.rs:45:5 | LL | Ok::<_, ()>(1).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -152,7 +260,7 @@ LL + 1; | error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:35:5 + --> $DIR/unnecessary_literal_unwrap.rs:46:5 | LL | Ok::<_, ()>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -163,7 +271,7 @@ LL | panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:36:5 + --> $DIR/unnecessary_literal_unwrap.rs:47:5 | LL | Ok::<_, ()>(1).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +282,7 @@ LL | panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:40:16 + --> $DIR/unnecessary_literal_unwrap.rs:51:16 | LL | let _val = Err::<(), _>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -186,7 +294,7 @@ LL + let _val = 1; | error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:41:16 + --> $DIR/unnecessary_literal_unwrap.rs:52:16 | LL | let _val = Err::<(), _>(1).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -198,7 +306,7 @@ LL + let _val = 1; | error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:42:16 + --> $DIR/unnecessary_literal_unwrap.rs:53:16 | LL | let _val = Err::<(), _>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -209,7 +317,7 @@ LL | let _val = panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:43:16 + --> $DIR/unnecessary_literal_unwrap.rs:54:16 | LL | let _val = Err::<(), _>(1).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -220,7 +328,7 @@ LL | let _val = panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:45:5 + --> $DIR/unnecessary_literal_unwrap.rs:56:5 | LL | Err::<(), _>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -232,7 +340,7 @@ LL + 1; | error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:46:5 + --> $DIR/unnecessary_literal_unwrap.rs:57:5 | LL | Err::<(), _>(1).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -244,7 +352,7 @@ LL + 1; | error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:47:5 + --> $DIR/unnecessary_literal_unwrap.rs:58:5 | LL | Err::<(), _>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -255,7 +363,7 @@ LL | panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:48:5 + --> $DIR/unnecessary_literal_unwrap.rs:59:5 | LL | Err::<(), _>(1).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -266,7 +374,7 @@ LL | panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:52:16 + --> $DIR/unnecessary_literal_unwrap.rs:63:16 | LL | let _val = Some(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^ @@ -278,7 +386,7 @@ LL + let _val = 1; | error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:53:16 + --> $DIR/unnecessary_literal_unwrap.rs:64:16 | LL | let _val = Some(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -290,7 +398,7 @@ LL + let _val = 1; | error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:54:16 + --> $DIR/unnecessary_literal_unwrap.rs:65:16 | LL | let _val = Some(1).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -302,7 +410,7 @@ LL + let _val = 1; | error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:56:5 + --> $DIR/unnecessary_literal_unwrap.rs:67:5 | LL | Some(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^ @@ -314,7 +422,7 @@ LL + 1; | error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:57:5 + --> $DIR/unnecessary_literal_unwrap.rs:68:5 | LL | Some(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -326,7 +434,7 @@ LL + 1; | error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:58:5 + --> $DIR/unnecessary_literal_unwrap.rs:69:5 | LL | Some(1).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -338,7 +446,7 @@ LL + 1; | error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:62:16 + --> $DIR/unnecessary_literal_unwrap.rs:73:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -350,7 +458,7 @@ LL + let _val = 1; | error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:63:16 + --> $DIR/unnecessary_literal_unwrap.rs:74:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -362,7 +470,7 @@ LL + let _val = 1; | error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:64:16 + --> $DIR/unnecessary_literal_unwrap.rs:75:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -374,7 +482,7 @@ LL + let _val = 1; | error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:66:5 + --> $DIR/unnecessary_literal_unwrap.rs:77:5 | LL | Ok::<_, ()>(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -386,7 +494,7 @@ LL + 1; | error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:67:5 + --> $DIR/unnecessary_literal_unwrap.rs:78:5 | LL | Ok::<_, ()>(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -398,7 +506,7 @@ LL + 1; | error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:68:5 + --> $DIR/unnecessary_literal_unwrap.rs:79:5 | LL | Ok::<_, ()>(1).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -410,7 +518,7 @@ LL + 1; | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:82:22 + --> $DIR/unnecessary_literal_unwrap.rs:93:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -422,7 +530,7 @@ LL + let _ = 1; | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:83:22 + --> $DIR/unnecessary_literal_unwrap.rs:94:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() + *(&1 as *const i32) }; // needs to keep the unsafe block | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -434,7 +542,7 @@ LL + let _ = unsafe { 1 + *(&1 as *const i32) }; // needs to keep the unsafe | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:84:22 + --> $DIR/unnecessary_literal_unwrap.rs:95:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() } + 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -446,7 +554,7 @@ LL + let _ = 1 + 1; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:85:22 + --> $DIR/unnecessary_literal_unwrap.rs:96:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -458,7 +566,7 @@ LL + let _ = 1; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:86:22 + --> $DIR/unnecessary_literal_unwrap.rs:97:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() + *(&1 as *const i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -470,7 +578,7 @@ LL + let _ = unsafe { 1 + *(&1 as *const i32) }; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:87:22 + --> $DIR/unnecessary_literal_unwrap.rs:98:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() } + 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -482,7 +590,7 @@ LL + let _ = 1 + 1; | error: used `unwrap_err_unchecked()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:88:22 + --> $DIR/unnecessary_literal_unwrap.rs:99:22 | LL | let _ = unsafe { Err::<(), i32>(123).unwrap_err_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -493,5 +601,5 @@ LL - let _ = unsafe { Err::<(), i32>(123).unwrap_err_unchecked() }; LL + let _ = 123; | -error: aborting due to 43 previous errors +error: aborting due to 53 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.rs b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.rs index 711fdce39624e..41300aceb4153 100644 --- a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.rs +++ b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.rs @@ -21,6 +21,8 @@ fn unwrap_option_none() { let val = None::<()>; let _val2 = val.unwrap(); let _val2 = val.expect("this always happens"); + let _val3: u8 = None.unwrap_or_default(); + None::<()>.unwrap_or_default(); } fn unwrap_result_ok() { diff --git a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.stderr b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.stderr index feb9325b77a67..2d1270d47174f 100644 --- a/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_literal_unwrap_unfixable.stderr @@ -95,509 +95,521 @@ help: remove the `None` and `expect()` LL | let val = None::<()>; | ^^^^^^^^^^ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:24:21 + | +LL | let _val3: u8 = None.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `Default::default()` + +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:25:5 + | +LL | None::<()>.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `Default::default()` + error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:28:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:30:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:31:17 | LL | let _val2 = val.expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:30:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:32:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:31:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:33:17 | LL | let _val2 = val.expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:35:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:35:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:36:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:36:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:39:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:39:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:41:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:43:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:44:17 | LL | let _val2 = val.expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:43:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:45:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:44:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:46:17 | LL | let _val2 = val.expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:49:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:51:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:52:17 | LL | let _val2 = val.expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:51:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:53:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:52:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:54:17 | LL | let _val2 = val.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:56:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:56:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:57:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:57:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:60:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:60:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:62:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:64:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:65:17 | LL | let _val2 = val.expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:64:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:66:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:65:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:67:17 | LL | let _val2 = val.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:70:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:72:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:73:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:72:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:74:17 | LL | let _val2 = val.unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:76:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:76:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:77:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:79:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:77:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:79:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:81:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:83:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:84:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:83:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:85:17 | LL | let _val2 = val.unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:88:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:90:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:91:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:90:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:92:17 | LL | let _val2 = val.unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:94:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:94:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:95:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:97:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:95:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:97:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:99:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:101:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:102:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:101:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:103:17 | LL | let _val2 = val.unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 50 previous errors +error: aborting due to 52 previous errors diff --git a/src/tools/clippy/tests/ui/unused_async.rs b/src/tools/clippy/tests/ui/unused_async.rs index 69e46ab473622..1d188025e418d 100644 --- a/src/tools/clippy/tests/ui/unused_async.rs +++ b/src/tools/clippy/tests/ui/unused_async.rs @@ -37,6 +37,23 @@ mod issue10459 { } } +mod issue9695 { + use std::future::Future; + + async fn f() {} + async fn f2() {} + async fn f3() {} + + fn needs_async_fn>(_: fn() -> F) {} + + fn test() { + let x = f; + needs_async_fn(x); // async needed in f + needs_async_fn(f2); // async needed in f2 + f3(); // async not needed in f3 + } +} + async fn foo() -> i32 { 4 } diff --git a/src/tools/clippy/tests/ui/unused_async.stderr b/src/tools/clippy/tests/ui/unused_async.stderr index ffae8366b88c3..8d9b72c4886c2 100644 --- a/src/tools/clippy/tests/ui/unused_async.stderr +++ b/src/tools/clippy/tests/ui/unused_async.stderr @@ -17,7 +17,15 @@ LL | ready(()).await; = note: `-D clippy::unused-async` implied by `-D warnings` error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:40:1 + --> $DIR/unused_async.rs:45:5 + | +LL | async fn f3() {} + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the `async` from this function + +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:57:1 | LL | / async fn foo() -> i32 { LL | | 4 @@ -27,7 +35,7 @@ LL | | } = help: consider removing the `async` from this function error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:51:5 + --> $DIR/unused_async.rs:68:5 | LL | / async fn unused(&self) -> i32 { LL | | 1 @@ -36,5 +44,5 @@ LL | | } | = help: consider removing the `async` from this function -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/unwrap_or_else_default.fixed b/src/tools/clippy/tests/ui/unwrap_or_else_default.fixed index 08b89a18bbbd4..acdb96942ba1a 100644 --- a/src/tools/clippy/tests/ui/unwrap_or_else_default.fixed +++ b/src/tools/clippy/tests/ui/unwrap_or_else_default.fixed @@ -1,10 +1,10 @@ //@run-rustfix -#![warn(clippy::unwrap_or_else_default)] +#![warn(clippy::unwrap_or_default)] #![allow(dead_code)] #![allow(clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap)] -/// Checks implementation of the `UNWRAP_OR_ELSE_DEFAULT` lint. +/// Checks implementation of the `UNWRAP_OR_DEFAULT` lint. fn unwrap_or_else_default() { struct Foo; @@ -74,4 +74,62 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_default(); } +fn type_certainty(option: Option>) { + option.unwrap_or_default().push(1); + + let option: std::option::Option> = None; + option.unwrap_or_default().push(1); + + let option: Option> = None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_default().push(1); + + let option = Option::>::None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_default().push(1); + + let option = Option::None::>; + option.unwrap_or_default().push(1); + + let option = None::>; + option.unwrap_or_default().push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); + + type Alias = Option>; + let option: Alias = Option::>::Some(Vec::new()); + option.unwrap_or_default().push(1); +} + +fn method_call_with_deref() { + use std::cell::RefCell; + use std::collections::HashMap; + + let cell = RefCell::new(HashMap::>::new()); + + let mut outer_map = cell.borrow_mut(); + + #[allow(unused_assignments)] + let mut option = None; + option = Some(0); + + let inner_map = outer_map.get_mut(&option.unwrap()).unwrap(); + + let _ = inner_map.entry(0).or_default(); +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/unwrap_or_else_default.rs b/src/tools/clippy/tests/ui/unwrap_or_else_default.rs index ad2a744908fcb..55ccd00e1a2aa 100644 --- a/src/tools/clippy/tests/ui/unwrap_or_else_default.rs +++ b/src/tools/clippy/tests/ui/unwrap_or_else_default.rs @@ -1,10 +1,10 @@ //@run-rustfix -#![warn(clippy::unwrap_or_else_default)] +#![warn(clippy::unwrap_or_default)] #![allow(dead_code)] #![allow(clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap)] -/// Checks implementation of the `UNWRAP_OR_ELSE_DEFAULT` lint. +/// Checks implementation of the `UNWRAP_OR_DEFAULT` lint. fn unwrap_or_else_default() { struct Foo; @@ -74,4 +74,62 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_else(|| "".to_string()); } +fn type_certainty(option: Option>) { + option.unwrap_or_else(Vec::new).push(1); + + let option: std::option::Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = None::>; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); + + type Alias = Option>; + let option: Alias = Option::>::Some(Vec::new()); + option.unwrap_or_else(Vec::new).push(1); +} + +fn method_call_with_deref() { + use std::cell::RefCell; + use std::collections::HashMap; + + let cell = RefCell::new(HashMap::>::new()); + + let mut outer_map = cell.borrow_mut(); + + #[allow(unused_assignments)] + let mut option = None; + option = Some(0); + + let inner_map = outer_map.get_mut(&option.unwrap()).unwrap(); + + let _ = inner_map.entry(0).or_insert_with(Default::default); +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/unwrap_or_else_default.stderr b/src/tools/clippy/tests/ui/unwrap_or_else_default.stderr index d2b9212223f77..af662c6def7e3 100644 --- a/src/tools/clippy/tests/ui/unwrap_or_else_default.stderr +++ b/src/tools/clippy/tests/ui/unwrap_or_else_default.stderr @@ -1,40 +1,100 @@ -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:48:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:48:14 | LL | with_new.unwrap_or_else(Vec::new); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_new.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` | - = note: `-D clippy::unwrap-or-else-default` implied by `-D warnings` + = note: `-D clippy::unwrap-or-default` implied by `-D warnings` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:62:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:62:23 | LL | with_real_default.unwrap_or_else(::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_real_default.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:65:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:65:24 | LL | with_default_trait.unwrap_or_else(Default::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_trait.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:68:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:68:23 | LL | with_default_type.unwrap_or_else(u64::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_type.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:71:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:71:23 | LL | with_default_type.unwrap_or_else(Vec::new); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_type.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:74:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:74:18 | LL | empty_string.unwrap_or_else(|| "".to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `empty_string.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: aborting due to 6 previous errors +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:78:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:81:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:84:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:87:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:90:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:93:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:96:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:99:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:115:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/unwrap_or_else_default.rs:132:32 + | +LL | let _ = inner_map.entry(0).or_insert_with(Default::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml index c40b71f6ca7d4..6856bb0ab3759 100644 --- a/src/tools/clippy/triagebot.toml +++ b/src/tools/clippy/triagebot.toml @@ -9,6 +9,9 @@ allow-unauthenticated = [ # See https://github.com/rust-lang/triagebot/wiki/Shortcuts [shortcut] +# Have rustbot inform users about the *No Merge Policy* +[no-merges] + [autolabel."S-waiting-on-review"] new_pr = true @@ -27,4 +30,6 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB "@Alexendoo", "@dswij", "@Jarcho", + "@blyxyas", + "@Centri3", ]