diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 0e27cc927..fac2c9971 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -24,6 +24,7 @@ env: RUST_BACKTRACE: 1 CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 + CARGO_INCREMENTAL: 0 jobs: base: diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 97453303c..30607af49 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -10,6 +10,7 @@ env: RUST_BACKTRACE: 1 CARGO_TARGET_DIR: '${{ github.workspace }}/target' NO_FMT_TEST: 1 + CARGO_INCREMENTAL: 0 defaults: run: diff --git a/CHANGELOG.md b/CHANGELOG.md index 380cd4519..c488c142e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,157 @@ document. ## Unreleased / In Rust Nightly -[7c21f91b...master](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...master) +[d7b5cbf0...master](https://github.com/rust-lang/rust-clippy/compare/d7b5cbf0...master) + +## Rust 1.63 + +Current stable, released 2022-08-11 + +[7c21f91b...d7b5cbf0](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...d7b5cbf0) + +### New Lints + +* [`borrow_deref_ref`] + [#7930](https://github.com/rust-lang/rust-clippy/pull/7930) +* [`doc_link_with_quotes`] + [#8385](https://github.com/rust-lang/rust-clippy/pull/8385) +* [`no_effect_replace`] + [#8754](https://github.com/rust-lang/rust-clippy/pull/8754) +* [`rc_clone_in_vec_init`] + [#8769](https://github.com/rust-lang/rust-clippy/pull/8769) +* [`derive_partial_eq_without_eq`] + [#8796](https://github.com/rust-lang/rust-clippy/pull/8796) +* [`mismatching_type_param_order`] + [#8831](https://github.com/rust-lang/rust-clippy/pull/8831) +* [`duplicate_mod`] [#8832](https://github.com/rust-lang/rust-clippy/pull/8832) +* [`unused_rounding`] + [#8866](https://github.com/rust-lang/rust-clippy/pull/8866) +* [`get_first`] [#8882](https://github.com/rust-lang/rust-clippy/pull/8882) +* [`swap_ptr_to_ref`] + [#8916](https://github.com/rust-lang/rust-clippy/pull/8916) +* [`almost_complete_letter_range`] + [#8918](https://github.com/rust-lang/rust-clippy/pull/8918) +* [`needless_parens_on_range_literals`] + [#8933](https://github.com/rust-lang/rust-clippy/pull/8933) +* [`as_underscore`] [#8934](https://github.com/rust-lang/rust-clippy/pull/8934) + +### Moves and Deprecations + +* Rename `eval_order_dependence` to [`mixed_read_write_in_expression`], move to + `nursery` [#8621](https://github.com/rust-lang/rust-clippy/pull/8621) + +### Enhancements + +* [`undocumented_unsafe_blocks`]: Now also lints on unsafe trait implementations + [#8761](https://github.com/rust-lang/rust-clippy/pull/8761) +* [`empty_line_after_outer_attr`]: Now also lints on argumentless macros + [#8790](https://github.com/rust-lang/rust-clippy/pull/8790) +* [`expect_used`]: Now can be disabled in tests with the `allow-expect-in-tests` + option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802) +* [`unwrap_used`]: Now can be disabled in tests with the `allow-unwrap-in-tests` + option [#8802](https://github.com/rust-lang/rust-clippy/pull/8802) +* [`disallowed_methods`]: Now also lints indirect usages + [#8852](https://github.com/rust-lang/rust-clippy/pull/8852) +* [`get_last_with_len`]: Now also lints `VecDeque` and any deref to slice + [#8862](https://github.com/rust-lang/rust-clippy/pull/8862) +* [`manual_range_contains`]: Now also lints on chains of `&&` and `||` + [#8884](https://github.com/rust-lang/rust-clippy/pull/8884) +* [`rc_clone_in_vec_init`]: Now also lints on `Weak` + [#8885](https://github.com/rust-lang/rust-clippy/pull/8885) +* [`dbg_macro`]: Introduce `allow-dbg-in-tests` config option + [#8897](https://github.com/rust-lang/rust-clippy/pull/8897) +* [`use_self`]: Now also lints on `TupleStruct` and `Struct` patterns + [#8899](https://github.com/rust-lang/rust-clippy/pull/8899) +* [`manual_find_map`] and [`manual_filter_map`]: Now also lints on more complex + method chains inside `map` + [#8930](https://github.com/rust-lang/rust-clippy/pull/8930) +* [`needless_return`]: Now also lints on macro expressions in return statements + [#8932](https://github.com/rust-lang/rust-clippy/pull/8932) +* [`doc_markdown`]: Users can now indicate, that the `doc-valid-idents` config + should extend the default and not replace it + [#8944](https://github.com/rust-lang/rust-clippy/pull/8944) +* [`disallowed_names`]: Users can now indicate, that the `disallowed-names` + config should extend the default and not replace it + [#8944](https://github.com/rust-lang/rust-clippy/pull/8944) +* [`never_loop`]: Now checks for `continue` in struct expression + [#9002](https://github.com/rust-lang/rust-clippy/pull/9002) + +### False Positive Fixes + +* [`useless_transmute`]: No longer lints on types with erased regions + [#8564](https://github.com/rust-lang/rust-clippy/pull/8564) +* [`vec_init_then_push`]: No longer lints when further extended + [#8699](https://github.com/rust-lang/rust-clippy/pull/8699) +* [`cmp_owned`]: No longer lints on `From::from` for `Copy` types + [#8807](https://github.com/rust-lang/rust-clippy/pull/8807) +* [`redundant_allocation`]: No longer lints on fat pointers that would become + thin pointers [#8813](https://github.com/rust-lang/rust-clippy/pull/8813) +* [`derive_partial_eq_without_eq`]: + * Handle differing predicates applied by `#[derive(PartialEq)]` and + `#[derive(Eq)]` + [#8869](https://github.com/rust-lang/rust-clippy/pull/8869) + * No longer lints on non-public types and better handles generics + [#8950](https://github.com/rust-lang/rust-clippy/pull/8950) +* [`empty_line_after_outer_attr`]: No longer lints empty lines in inner + string values [#8892](https://github.com/rust-lang/rust-clippy/pull/8892) +* [`branches_sharing_code`]: No longer lints when using different binding names + [#8901](https://github.com/rust-lang/rust-clippy/pull/8901) +* [`significant_drop_in_scrutinee`]: No longer lints on Try `?` and `await` + desugared expressions [#8902](https://github.com/rust-lang/rust-clippy/pull/8902) +* [`checked_conversions`]: No longer lints in `const` contexts + [#8907](https://github.com/rust-lang/rust-clippy/pull/8907) +* [`iter_overeager_cloned`]: No longer lints on `.cloned().flatten()` when + `T::Item` doesn't implement `IntoIterator` + [#8960](https://github.com/rust-lang/rust-clippy/pull/8960) + +### Suggestion Fixes/Improvements + +* [`vec_init_then_push`]: Suggest to remove `mut` binding when possible + [#8699](https://github.com/rust-lang/rust-clippy/pull/8699) +* [`manual_range_contains`]: Fix suggestion for integers with different signs + [#8763](https://github.com/rust-lang/rust-clippy/pull/8763) +* [`identity_op`]: Add parenthesis to suggestions where required + [#8786](https://github.com/rust-lang/rust-clippy/pull/8786) +* [`cast_lossless`]: No longer gives wrong suggestion on `usize`/`isize`->`f64` + [#8778](https://github.com/rust-lang/rust-clippy/pull/8778) +* [`rc_clone_in_vec_init`]: Add suggestion + [#8814](https://github.com/rust-lang/rust-clippy/pull/8814) +* The "unknown field" error messages for config files now wraps the field names + [#8823](https://github.com/rust-lang/rust-clippy/pull/8823) +* [`cast_abs_to_unsigned`]: Do not remove cast if it's required + [#8876](https://github.com/rust-lang/rust-clippy/pull/8876) +* [`significant_drop_in_scrutinee`]: Improve lint message for types that are not + references and not trivially clone-able + [#8902](https://github.com/rust-lang/rust-clippy/pull/8902) +* [`for_loops_over_fallibles`]: Now suggests the correct variant of `iter()`, + `iter_mut()` or `into_iter()` + [#8941](https://github.com/rust-lang/rust-clippy/pull/8941) + +### ICE Fixes + +* Fix ICE in [`let_unit_value`] when calling a `static`/`const` callable type + [#8835](https://github.com/rust-lang/rust-clippy/pull/8835) +* Fix ICEs on callable `static`/`const`s + [#8896](https://github.com/rust-lang/rust-clippy/pull/8896) +* [`needless_late_init`] + [#8912](https://github.com/rust-lang/rust-clippy/pull/8912) +* Fix ICE in shadow lints + [#8913](https://github.com/rust-lang/rust-clippy/pull/8913) + +### Documentation Improvements + +* Clippy has a [Book](https://doc.rust-lang.org/nightly/clippy/) now! + [#7359](https://github.com/rust-lang/rust-clippy/pull/7359) +* Add a *copy lint name*-button to Clippy's lint list + [#8839](https://github.com/rust-lang/rust-clippy/pull/8839) +* Display past names of renamed lints on Clippy's lint list + [#8843](https://github.com/rust-lang/rust-clippy/pull/8843) +* Add the ability to show the lint output in the lint list + [#8947](https://github.com/rust-lang/rust-clippy/pull/8947) ## Rust 1.62 -Current stable, released 2022-06-30 +Released 2022-06-30 [d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b) @@ -3481,6 +3627,7 @@ Released 2018-09-13 [`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss [`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes +[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp @@ -3496,6 +3643,7 @@ Released 2018-09-13 [`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if [`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if [`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match +[`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace [`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain [`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty [`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime @@ -3656,6 +3804,8 @@ Released 2018-09-13 [`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator [`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero +[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections +[`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_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain @@ -3697,6 +3847,7 @@ Released 2018-09-13 [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat +[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap [`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or @@ -3747,6 +3898,7 @@ Released 2018-09-13 [`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic [`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one +[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl [`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate @@ -3827,6 +3979,7 @@ Released 2018-09-13 [`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none [`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite [`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch +[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence [`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl @@ -3872,6 +4025,7 @@ Released 2018-09-13 [`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts [`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs [`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used +[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err [`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option [`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else @@ -3930,6 +4084,7 @@ Released 2018-09-13 [`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl [`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings [`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn +[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned [`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting [`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref [`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments @@ -4002,6 +4157,7 @@ Released 2018-09-13 [`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label +[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable [`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit diff --git a/clippy_dev/src/bless.rs b/clippy_dev/src/bless.rs index f5c51b947..92b2771f3 100644 --- a/clippy_dev/src/bless.rs +++ b/clippy_dev/src/bless.rs @@ -37,7 +37,7 @@ fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) { return; } - let test_output_file = fs::read(&test_output_path).expect("Unable to read test output file"); + let test_output_file = fs::read(test_output_path).expect("Unable to read test output file"); let reference_file = fs::read(&reference_file_path).unwrap_or_default(); if test_output_file != reference_file { diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 3b27f061e..357cf6fc4 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -46,7 +46,7 @@ pub fn run(check: bool, verbose: bool) { // dependency if fs::read_to_string(project_root.join("Cargo.toml")) .expect("Failed to read clippy Cargo.toml") - .contains(&"[target.'cfg(NOT_A_PLATFORM)'.dependencies]") + .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]") { return Err(CliError::IntellijSetupActive); } @@ -193,10 +193,10 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { let args = &["--version"]; if context.verbose { - println!("{}", format_command(&program, &dir, args)); + println!("{}", format_command(program, &dir, args)); } - let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?; + let output = Command::new(program).current_dir(&dir).args(args.iter()).output()?; if output.status.success() { Ok(()) @@ -207,7 +207,7 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { Err(CliError::RustfmtNotInstalled) } else { Err(CliError::CommandFailed( - format_command(&program, &dir, args), + format_command(program, &dir, args), std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), )) } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 10a8f31f4..be05e67d7 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -155,7 +155,7 @@ fn to_camel_case(name: &str) -> String { name.split('_') .map(|s| { if s.is_empty() { - String::from("") + String::new() } else { [&s[0..1].to_uppercase(), &s[1..]].concat() } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 05e79a241..c503142e5 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -418,7 +418,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io .expect("failed to find `impl_lint_pass` terminator"); impl_lint_pass_end += impl_lint_pass_start; - if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) { + if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) { let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); for c in content[lint_name_end..impl_lint_pass_end].chars() { // Remove trailing whitespace @@ -451,7 +451,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io } let mut content = - fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); + fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); eprintln!( "warn: you will have to manually remove any code related to `{}` from `{}`", diff --git a/clippy_lints/src/as_underscore.rs b/clippy_lints/src/as_underscore.rs deleted file mode 100644 index 0bdef9d0a..000000000 --- a/clippy_lints/src/as_underscore.rs +++ /dev/null @@ -1,74 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, TyKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Check for the usage of `as _` conversion using inferred type. - /// - /// ### Why is this bad? - /// The conversion might include lossy conversion and dangerous cast that might go - /// undetected du to the type being inferred. - /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. - /// - /// ### Example - /// ```rust - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as _); - /// ``` - /// Use instead: - /// ```rust - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as usize); - /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, - restriction, - "detects `as _` conversion" -} -declare_lint_pass!(AsUnderscore => [AS_UNDERSCORE]); - -impl<'tcx> LateLintPass<'tcx> for AsUnderscore { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if let ExprKind::Cast(_, ty) = expr.kind && let TyKind::Infer = ty.kind { - - let ty_resolved = cx.typeck_results().expr_ty(expr); - if let ty::Error(_) = ty_resolved.kind() { - span_lint_and_help( - cx, - AS_UNDERSCORE, - expr.span, - "using `as _` conversion", - None, - "consider giving the type explicitly", - ); - } else { - span_lint_and_then( - cx, - AS_UNDERSCORE, - expr.span, - "using `as _` conversion", - |diag| { - diag.span_suggestion( - ty.span, - "consider giving the type explicitly", - ty_resolved, - Applicability::MachineApplicable, - ); - } - ); - } - } - } -} diff --git a/clippy_lints/src/borrow_as_ptr.rs b/clippy_lints/src/borrow_as_ptr.rs deleted file mode 100644 index 0993adbae..000000000 --- a/clippy_lints/src/borrow_as_ptr.rs +++ /dev/null @@ -1,99 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_no_std_crate; -use clippy_utils::source::snippet_opt; -use clippy_utils::{meets_msrv, msrvs}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `&expr as *const T` or - /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or - /// `ptr::addr_of_mut` instead. - /// - /// ### Why is this bad? - /// This would improve readability and avoid creating a reference - /// that points to an uninitialized value or unaligned place. - /// Read the `ptr::addr_of` docs for more information. - /// - /// ### Example - /// ```rust - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; - /// ``` - /// Use instead: - /// ```rust - /// let val = 1; - /// let p = std::ptr::addr_of!(val); - /// - /// let mut val_mut = 1; - /// let p_mut = std::ptr::addr_of_mut!(val_mut); - /// ``` - #[clippy::version = "1.60.0"] - pub BORROW_AS_PTR, - pedantic, - "borrowing just to cast to a raw pointer" -} - -impl_lint_pass!(BorrowAsPtr => [BORROW_AS_PTR]); - -pub struct BorrowAsPtr { - msrv: Option, -} - -impl BorrowAsPtr { - #[must_use] - pub fn new(msrv: Option) -> Self { - Self { msrv } - } -} - -impl<'tcx> LateLintPass<'tcx> for BorrowAsPtr { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) { - return; - } - - if expr.span.from_expansion() { - return; - } - - if_chain! { - if let ExprKind::Cast(left_expr, ty) = &expr.kind; - if let TyKind::Ptr(_) = ty.kind; - if let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = &left_expr.kind; - - then { - let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; - let macro_name = match mutability { - Mutability::Not => "addr_of", - Mutability::Mut => "addr_of_mut", - }; - - span_lint_and_sugg( - cx, - BORROW_AS_PTR, - expr.span, - "borrow as raw pointer", - "try", - format!( - "{}::ptr::{}!({})", - core_or_std, - macro_name, - snippet_opt(cx, e.span).unwrap() - ), - Applicability::MachineApplicable, - ); - } - } - } - - extract_msrv_attr!(LateContext); -} diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 937765b66..c4520d003 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -29,22 +29,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// fn foo(_x: &str) {} - /// /// let s = &String::new(); /// /// let a: &String = &* s; - /// foo(&*s); /// ``` /// /// Use instead: /// ```rust - /// # fn foo(_x: &str) {} /// # let s = &String::new(); /// let a: &String = s; - /// foo(&**s); /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.63.0"] pub BORROW_DEREF_REF, complexity, "deref on an immutable reference returns the same type as itself" diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs deleted file mode 100644 index 326ce3408..000000000 --- a/clippy_lints/src/bytecount.rs +++ /dev/null @@ -1,103 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::match_type; -use clippy_utils::visitors::is_local_used; -use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, UintTy}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for naive byte counts - /// - /// ### Why is this bad? - /// The [`bytecount`](https://crates.io/crates/bytecount) - /// crate has methods to count your bytes faster, especially for large slices. - /// - /// ### Known problems - /// If you have predominantly small slices, the - /// `bytecount::count(..)` method may actually be slower. However, if you can - /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be - /// faster in those cases. - /// - /// ### Example - /// ```rust - /// # let vec = vec![1_u8]; - /// let count = vec.iter().filter(|x| **x == 0u8).count(); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let vec = vec![1_u8]; - /// let count = bytecount::count(&vec, 0u8); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NAIVE_BYTECOUNT, - pedantic, - "use of naive `.filter(|&x| x == y).count()` to count byte values" -} - -declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]); - -impl<'tcx> LateLintPass<'tcx> for ByteCount { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let ExprKind::MethodCall(count, [count_recv], _) = expr.kind; - if count.ident.name == sym::count; - if let ExprKind::MethodCall(filter, [filter_recv, filter_arg], _) = count_recv.kind; - if filter.ident.name == sym!(filter); - if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind; - let body = cx.tcx.hir().body(body); - if let [param] = body.params; - if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; - if let ExprKind::Binary(ref op, l, r) = body.value.kind; - if op.node == BinOpKind::Eq; - if match_type(cx, - cx.typeck_results().expr_ty(filter_recv).peel_refs(), - &paths::SLICE_ITER); - let operand_is_arg = |expr| { - let expr = peel_ref_operators(cx, peel_blocks(expr)); - path_to_local_id(expr, arg_id) - }; - let needle = if operand_is_arg(l) { - r - } else if operand_is_arg(r) { - l - } else { - return; - }; - if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); - if !is_local_used(cx, needle, arg_id); - then { - let haystack = if let ExprKind::MethodCall(path, args, _) = - filter_recv.kind { - let p = path.ident.name; - if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { - &args[0] - } else { - filter_recv - } - } else { - filter_recv - }; - let mut applicability = Applicability::MaybeIncorrect; - span_lint_and_sugg( - cx, - NAIVE_BYTECOUNT, - expr.span, - "you appear to be counting bytes the naive way", - "consider using the bytecount crate", - format!("bytecount::count({}, {})", - snippet_with_applicability(cx, haystack.span, "..", &mut applicability), - snippet_with_applicability(cx, needle.span, "..", &mut applicability)), - applicability, - ); - } - }; - } -} diff --git a/clippy_lints/src/bytes_count_to_len.rs b/clippy_lints/src/bytes_count_to_len.rs deleted file mode 100644 index d70dbf5b2..000000000 --- a/clippy_lints/src/bytes_count_to_len.rs +++ /dev/null @@ -1,70 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{match_def_path, paths}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// It checks for `str::bytes().count()` and suggests replacing it with - /// `str::len()`. - /// - /// ### Why is this bad? - /// `str::bytes().count()` is longer and may not be as performant as using - /// `str::len()`. - /// - /// ### Example - /// ```rust - /// "hello".bytes().count(); - /// String::from("hello").bytes().count(); - /// ``` - /// Use instead: - /// ```rust - /// "hello".len(); - /// String::from("hello").len(); - /// ``` - #[clippy::version = "1.62.0"] - pub BYTES_COUNT_TO_LEN, - complexity, - "Using `bytes().count()` when `len()` performs the same functionality" -} - -declare_lint_pass!(BytesCountToLen => [BYTES_COUNT_TO_LEN]); - -impl<'tcx> LateLintPass<'tcx> for BytesCountToLen { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if_chain! { - if let hir::ExprKind::MethodCall(_, expr_args, _) = &expr.kind; - if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match_def_path(cx, expr_def_id, &paths::ITER_COUNT); - - if let [bytes_expr] = &**expr_args; - if let hir::ExprKind::MethodCall(_, bytes_args, _) = &bytes_expr.kind; - if let Some(bytes_def_id) = cx.typeck_results().type_dependent_def_id(bytes_expr.hir_id); - if match_def_path(cx, bytes_def_id, &paths::STR_BYTES); - - if let [str_expr] = &**bytes_args; - let ty = cx.typeck_results().expr_ty(str_expr).peel_refs(); - - if is_type_diagnostic_item(cx, ty, sym::String) || ty.kind() == &ty::Str; - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BYTES_COUNT_TO_LEN, - expr.span, - "using long and hard to read `.bytes().count()`", - "consider calling `.len()` instead", - format!("{}.len()", snippet_with_applicability(cx, str_expr.span, "..", &mut applicability)), - applicability - ); - } - }; - } -} diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs deleted file mode 100644 index 7eff71d50..000000000 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ /dev/null @@ -1,86 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use if_chain::if_chain; -use rustc_ast::ast::LitKind; -use rustc_hir::{Expr, ExprKind, PathSegment}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{source_map::Spanned, symbol::sym, Span}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `ends_with` with possible file extensions - /// and suggests to use a case-insensitive approach instead. - /// - /// ### Why is this bad? - /// `ends_with` is case-sensitive and may not detect files with a valid extension. - /// - /// ### Example - /// ```rust - /// fn is_rust_file(filename: &str) -> bool { - /// filename.ends_with(".rs") - /// } - /// ``` - /// Use instead: - /// ```rust - /// fn is_rust_file(filename: &str) -> bool { - /// let filename = std::path::Path::new(filename); - /// filename.extension() - /// .map(|ext| ext.eq_ignore_ascii_case("rs")) - /// .unwrap_or(false) - /// } - /// ``` - #[clippy::version = "1.51.0"] - pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - pedantic, - "Checks for calls to ends_with with case-sensitive file extensions" -} - -declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]); - -fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if_chain! { - if let ExprKind::MethodCall(PathSegment { ident, .. }, [obj, extension, ..], span) = expr.kind; - if ident.as_str() == "ends_with"; - if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind; - if (2..=6).contains(&ext_literal.as_str().len()); - if ext_literal.as_str().starts_with('.'); - if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit()) - || ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()); - then { - let mut ty = ctx.typeck_results().expr_ty(obj); - ty = match ty.kind() { - ty::Ref(_, ty, ..) => *ty, - _ => ty - }; - - match ty.kind() { - ty::Str => { - return Some(span); - }, - ty::Adt(def, _) => { - if ctx.tcx.is_diagnostic_item(sym::String, def.did()) { - return Some(span); - } - }, - _ => { return None; } - } - } - } - None -} - -impl<'tcx> LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons { - fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) { - span_lint_and_help( - ctx, - CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - span, - "case-sensitive file extension comparison", - None, - "consider using a case-insensitive comparison instead", - ); - } - } -} diff --git a/clippy_lints/src/casts/as_underscore.rs b/clippy_lints/src/casts/as_underscore.rs new file mode 100644 index 000000000..56e894c62 --- /dev/null +++ b/clippy_lints/src/casts/as_underscore.rs @@ -0,0 +1,25 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::AS_UNDERSCORE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ty: &'tcx Ty<'_>) { + if matches!(ty.kind, TyKind::Infer) { + span_lint_and_then(cx, AS_UNDERSCORE, expr.span, "using `as _` conversion", |diag| { + let ty_resolved = cx.typeck_results().expr_ty(expr); + if let ty::Error(_) = ty_resolved.kind() { + diag.help("consider giving the type explicitly"); + } else { + diag.span_suggestion( + ty.span, + "consider giving the type explicitly", + ty_resolved, + Applicability::MachineApplicable, + ); + } + }); + } +} diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs new file mode 100644 index 000000000..6e1f8cd64 --- /dev/null +++ b/clippy_lints/src/casts/borrow_as_ptr.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_no_std_crate; +use clippy_utils::source::snippet_with_context; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind}; +use rustc_lint::LateContext; + +use super::BORROW_AS_PTR; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + cast_expr: &'tcx Expr<'_>, + cast_to: &'tcx Ty<'_>, +) { + if matches!(cast_to.kind, TyKind::Ptr(_)) + && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind + { + let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; + let macro_name = match mutability { + Mutability::Not => "addr_of", + Mutability::Mut => "addr_of_mut", + }; + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0; + + span_lint_and_sugg( + cx, + BORROW_AS_PTR, + expr.span, + "borrow as raw pointer", + "try", + format!("{}::ptr::{}!({})", core_or_std, macro_name, snip), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs new file mode 100644 index 000000000..284ef1659 --- /dev/null +++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -0,0 +1,63 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{match_def_path, meets_msrv, msrvs, paths}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def_id::DefId, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_semver::RustcVersion; + +use super::CAST_SLICE_FROM_RAW_PARTS; + +enum RawPartsKind { + Immutable, + Mutable, +} + +fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option { + if match_def_path(cx, did, &paths::SLICE_FROM_RAW_PARTS) { + Some(RawPartsKind::Immutable) + } else if match_def_path(cx, did, &paths::SLICE_FROM_RAW_PARTS_MUT) { + Some(RawPartsKind::Mutable) + } else { + None + } +} + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_to: Ty<'_>, + msrv: Option, +) { + if_chain! { + if meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS); + if let ty::RawPtr(ptrty) = cast_to.kind(); + if let ty::Slice(_) = ptrty.ty.kind(); + if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); + if let Some(rpk) = raw_parts_kind(cx, fun_def_id); + then { + let func = match rpk { + RawPartsKind::Immutable => "from_raw_parts", + RawPartsKind::Mutable => "from_raw_parts_mut" + }; + let span = expr.span; + let mut applicability = Applicability::MachineApplicable; + let ptr = snippet_with_applicability(cx, ptr_arg.span, "ptr", &mut applicability); + let len = snippet_with_applicability(cx, len_arg.span, "len", &mut applicability); + span_lint_and_sugg( + cx, + CAST_SLICE_FROM_RAW_PARTS, + span, + &format!("casting the result of `{func}` to {cast_to}"), + "replace with", + format!("core::ptr::slice_{func}({ptr}, {len})"), + applicability + ); + } + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index af3798a0c..cc5d346b9 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -1,3 +1,5 @@ +mod as_underscore; +mod borrow_as_ptr; mod cast_abs_to_unsigned; mod cast_enum_constructor; mod cast_lossless; @@ -8,6 +10,7 @@ mod cast_ptr_alignment; mod cast_ref_to_mut; mod cast_sign_loss; mod cast_slice_different_sizes; +mod cast_slice_from_raw_parts; mod char_lit_as_u8; mod fn_to_numeric_cast; mod fn_to_numeric_cast_any; @@ -16,7 +19,7 @@ mod ptr_as_ptr; mod unnecessary_cast; mod utils; -use clippy_utils::is_hir_ty_cfg_dependant; +use clippy_utils::{is_hir_ty_cfg_dependant, meets_msrv, msrvs}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -506,6 +509,93 @@ declare_clippy_lint! { "casting the result of `abs()` to an unsigned integer can panic" } +declare_clippy_lint! { + /// ### What it does + /// Check for the usage of `as _` conversion using inferred type. + /// + /// ### Why is this bad? + /// The conversion might include lossy conversion and dangerous cast that might go + /// undetected due to the type being inferred. + /// + /// The lint is allowed by default as using `_` is less wordy than always specifying the type. + /// + /// ### Example + /// ```rust + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as _); + /// ``` + /// Use instead: + /// ```rust + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as usize); + /// ``` + #[clippy::version = "1.63.0"] + pub AS_UNDERSCORE, + restriction, + "detects `as _` conversion" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `&expr as *const T` or + /// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or + /// `ptr::addr_of_mut` instead. + /// + /// ### Why is this bad? + /// This would improve readability and avoid creating a reference + /// that points to an uninitialized value or unaligned place. + /// Read the `ptr::addr_of` docs for more information. + /// + /// ### Example + /// ```rust + /// let val = 1; + /// let p = &val as *const i32; + /// + /// let mut val_mut = 1; + /// let p_mut = &mut val_mut as *mut i32; + /// ``` + /// Use instead: + /// ```rust + /// let val = 1; + /// let p = std::ptr::addr_of!(val); + /// + /// let mut val_mut = 1; + /// let p_mut = std::ptr::addr_of_mut!(val_mut); + /// ``` + #[clippy::version = "1.60.0"] + pub BORROW_AS_PTR, + pedantic, + "borrowing just to cast to a raw pointer" +} +declare_clippy_lint! { + /// ### What it does + /// Checks for a raw slice being cast to a slice pointer + /// + /// ### Why is this bad? + /// This can result in multiple `&mut` references to the same location when only a pointer is + /// required. + /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require + /// the same [safety requirements] to be upheld. + /// + /// ### Example + /// ```rust,ignore + /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; + /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); + /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); + /// ``` + /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + #[clippy::version = "1.64.0"] + pub CAST_SLICE_FROM_RAW_PARTS, + suspicious, + "casting a slice created from a pointer and length to a slice pointer" +} + pub struct Casts { msrv: Option, } @@ -534,7 +624,10 @@ impl_lint_pass!(Casts => [ PTR_AS_PTR, CAST_ENUM_TRUNCATION, CAST_ENUM_CONSTRUCTOR, - CAST_ABS_TO_UNSIGNED + CAST_ABS_TO_UNSIGNED, + AS_UNDERSCORE, + BORROW_AS_PTR, + CAST_SLICE_FROM_RAW_PARTS ]); impl<'tcx> LateLintPass<'tcx> for Casts { @@ -547,8 +640,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts { return; } - if let ExprKind::Cast(cast_expr, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { + if let ExprKind::Cast(cast_expr, cast_to_hir) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to_hir) { return; } let (cast_from, cast_to) = ( @@ -559,7 +652,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) { return; } - + cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, self.msrv); fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); @@ -575,6 +668,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv); cast_enum_constructor::check(cx, expr, cast_expr, cast_from); } + + as_underscore::check(cx, expr, cast_to_hir); + + if meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) { + borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir); + } } cast_ref_to_mut::check(cx, expr); diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index fff7da8e3..19d2e6e1d 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -90,13 +90,20 @@ pub(super) fn check<'tcx>( fn lint_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; + let replaced_literal; + let matchless = if literal_str.contains(['(', ')']) { + replaced_literal = literal_str.replace(['(', ')'], ""); + &replaced_literal + } else { + literal_str + }; span_lint_and_sugg( cx, UNNECESSARY_CAST, expr.span, &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), "try", - format!("{}_{}", literal_str.trim_end_matches('.'), cast_to), + format!("{}_{}", matchless.trim_end_matches('.'), cast_to), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59f10247a..1506ea604 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,24 +1,34 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, ty_sig, variant_of_res}; -use clippy_utils::{get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage}; +use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res}; +use clippy_utils::{ + fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, meets_msrv, msrvs, path_to_local, + walk_to_expr_usage, +}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; 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, def_id::DefId, 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, }; +use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Binder, BoundVariableKind, List, Ty, TyCtxt, TypeVisitable, TypeckResults}; +use rustc_middle::ty::{ + self, subst::Subst, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, + ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults, +}; +use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP}; -use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause}; +use std::collections::VecDeque; declare_clippy_lint! { /// ### What it does @@ -151,6 +161,7 @@ pub struct Dereferencing { /// been finished. Note we can't lint at the end of every body as they can be nested within each /// other. current_body: Option, + /// The list of locals currently being checked by the lint. /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted. /// This is needed for or patterns where one of the branches can be linted, but another can not @@ -158,6 +169,19 @@ pub struct Dereferencing { /// /// e.g. `m!(x) | Foo::Bar(ref x)` ref_locals: FxIndexMap>, + + // `IntoIterator` for arrays requires Rust 1.53. + msrv: Option, +} + +impl Dereferencing { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + ..Dereferencing::default() + } + } } struct StateData { @@ -170,6 +194,7 @@ struct StateData { struct DerefedBorrow { count: usize, msg: &'static str, + snip_expr: Option, } enum State { @@ -250,7 +275,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let (position, adjustments) = walk_parents(cx, expr); + let (position, adjustments) = walk_parents(cx, expr, self.msrv); match kind { RefOp::Deref => { @@ -331,20 +356,23 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { 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) = if position.can_auto_borrow() { - (1, if deref_count == 1 { borrow_msg } else { deref_msg }) + 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) { if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable() { - (3, deref_msg) + (3, deref_msg, None) } else { - (2, deref_msg) + (2, deref_msg, None) } } else { - (2, deref_msg) + (2, deref_msg, None) }; if deref_count >= required_refs { @@ -354,6 +382,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { // can't be removed without breaking the code. See earlier comment. count: deref_count - required_refs, msg, + snip_expr, }), StateData { span: expr.span, hir_id: expr.hir_id, position }, )); @@ -510,7 +539,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { spans: vec![pat.span], app, replacements: vec![(pat.span, snip.into())], - hir_id: pat.hir_id + hir_id: pat.hir_id, }), ); } @@ -542,6 +571,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { self.current_body = None; } } + + extract_msrv_attr!(LateContext); } fn try_parse_ref_op<'tcx>( @@ -594,6 +625,7 @@ enum Position { /// The method is defined on a reference type. e.g. `impl Foo for &T` MethodReceiverRefImpl, Callee, + ImplArg(HirId), FieldAccess(Symbol), Postfix, Deref, @@ -630,7 +662,7 @@ impl Position { | Self::Callee | Self::FieldAccess(_) | Self::Postfix => PREC_POSTFIX, - Self::Deref => PREC_PREFIX, + Self::ImplArg(_) | Self::Deref => PREC_PREFIX, Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p, } } @@ -639,8 +671,12 @@ impl Position { /// 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. -#[allow(clippy::too_many_lines)] -fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) { +#[expect(clippy::too_many_lines)] +fn walk_parents<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + msrv: Option, +) -> (Position, &'tcx [Adjustment<'tcx>]) { let mut adjustments = [].as_slice(); let mut precedence = 0i8; let ctxt = e.span.ctxt(); @@ -745,13 +781,20 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & .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 => ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) - .position_for_arg(), + .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 => { + if let ty::Param(param_ty) = ty.skip_binder().kind() { + needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv) + } else { + ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) + .position_for_arg() + } + }, + }) }), ExprKind::MethodCall(_, args, _) => { let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); @@ -773,7 +816,7 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & .and_then(|subs| subs.get(1..)) { Some(subs) => cx.tcx.mk_substs(subs.iter().copied()), - None => cx.tcx.mk_substs([].iter()), + None => cx.tcx.mk_substs(std::iter::empty::>()), } && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() { // Trait methods taking `&self` sub_ty @@ -792,12 +835,17 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & Position::MethodReceiver } } else { - ty_auto_deref_stability( - cx, - cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i)), - precedence, - ) - .position_for_arg() + let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i]; + if let ty::Param(param_ty) = ty.kind() { + needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv) + } else { + ty_auto_deref_stability( + cx, + cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i)), + precedence, + ) + .position_for_arg() + } } }) }, @@ -948,6 +996,205 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { v.0 } +// 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. +fn needless_borrow_impl_arg_position<'tcx>( + cx: &LateContext<'tcx>, + parent: &Expr<'tcx>, + arg_index: usize, + param_ty: ParamTy, + mut expr: &Expr<'tcx>, + precedence: i8, + msrv: Option, +) -> Position { + 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).skip_binder(); + let substs_with_expr_ty = cx + .typeck_results() + .node_substs(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 projection_predicates = predicates + .iter() + .filter_map(|predicate| { + if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() { + Some(projection_predicate) + } else { + None + } + }) + .collect::>(); + + let mut trait_with_ref_mut_self_method = false; + + // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return. + if predicates + .iter() + .filter_map(|predicate| { + if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() + && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx) + { + Some(trait_predicate.trait_ref.def_id) + } else { + None + } + }) + .inspect(|trait_def_id| { + trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id); + }) + .all(|trait_def_id| { + Some(trait_def_id) == destruct_trait_def_id + || Some(trait_def_id) == sized_trait_def_id + || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id) + }) + { + return Position::Other(precedence); + } + + // `substs_with_referent_ty` can be constructed outside of `check_referent` because the same + // elements are modified each time `check_referent` is called. + let mut substs_with_referent_ty = substs_with_expr_ty.to_vec(); + + let mut check_referent = |referent| { + let referent_ty = cx.typeck_results().expr_ty(referent); + + if !is_copy(cx, referent_ty) { + return false; + } + + // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321 + if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) { + return false; + } + + if !replace_types( + cx, + param_ty, + referent_ty, + fn_sig, + arg_index, + &projection_predicates, + &mut substs_with_referent_ty, + ) { + return false; + } + + predicates.iter().all(|predicate| { + if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() + && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id) + && let ty::Param(param_ty) = trait_predicate.self_ty().kind() + && let GenericArgKind::Type(ty) = substs_with_referent_ty[param_ty.index as usize].unpack() + && ty.is_array() + && !meets_msrv(msrv, msrvs::ARRAY_INTO_ITERATOR) + { + return false; + } + + let predicate = EarlyBinder(predicate).subst(cx.tcx, &substs_with_referent_ty); + let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate); + cx.tcx + .infer_ctxt() + .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation)) + }) + }; + + let mut needless_borrow = false; + while let ExprKind::AddrOf(_, _, referent) = expr.kind { + if !check_referent(referent) { + break; + } + expr = referent; + needless_borrow = true; + } + + if needless_borrow { + Position::ImplArg(expr.hir_id) + } else { + Position::Other(precedence) + } +} + +fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { + cx.tcx + .associated_items(trait_def_id) + .in_definition_order() + .any(|assoc_item| { + if assoc_item.fn_has_self_parameter { + let self_ty = cx.tcx.fn_sig(assoc_item.def_id).skip_binder().inputs()[0]; + matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut)) + } else { + false + } + }) +} + +// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting +// projected type that is a type parameter. Returns `false` if replacing the types would have an +// effect on the function signature beyond substituting `new_ty` for `param_ty`. +// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757 +fn replace_types<'tcx>( + cx: &LateContext<'tcx>, + param_ty: ParamTy, + new_ty: Ty<'tcx>, + fn_sig: FnSig<'tcx>, + arg_index: usize, + projection_predicates: &[ProjectionPredicate<'tcx>], + substs: &mut [ty::GenericArg<'tcx>], +) -> bool { + let mut replaced = BitSet::new_empty(substs.len()); + + let mut deque = VecDeque::with_capacity(substs.len()); + deque.push_back((param_ty, new_ty)); + + while let Some((param_ty, new_ty)) = deque.pop_front() { + // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in. + if !fn_sig + .inputs_and_output + .iter() + .enumerate() + .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx))) + { + return false; + } + + substs[param_ty.index as usize] = ty::GenericArg::from(new_ty); + + // The `replaced.insert(...)` check provides some protection against infinite loops. + if replaced.insert(param_ty.index) { + for projection_predicate in projection_predicates { + if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx) + && let ty::Term::Ty(term_ty) = projection_predicate.term + && let ty::Param(term_param_ty) = term_ty.kind() + { + let item_def_id = projection_predicate.projection_ty.item_def_id; + let assoc_item = cx.tcx.associated_item(item_def_id); + let projection = cx.tcx + .mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, &[])); + + if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection) + && substs[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty) + { + deque.push_back((*term_param_ty, projected_ty)); + } + } + } + } + } + + true +} + struct TyPosition<'tcx> { position: Position, ty: Option>, @@ -1086,7 +1333,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }, State::DerefedBorrow(state) => { let mut app = Applicability::MachineApplicable; - let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); + 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); 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 sugg = if !snip_is_macro diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index a982990e4..9ca443b7d 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -516,7 +516,10 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain( params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate { - trait_ref: TraitRef::new(eq_trait_id, tcx.mk_substs([tcx.mk_param_from_def(param)].into_iter())), + trait_ref: TraitRef::new( + eq_trait_id, + tcx.mk_substs(std::iter::once(tcx.mk_param_from_def(param))), + ), constness: BoundConstness::NotConst, polarity: ImplPolarity::Positive, }))) diff --git a/clippy_lints/src/doc_link_with_quotes.rs b/clippy_lints/src/doc_link_with_quotes.rs index cb07f57e8..0ff1d2755 100644 --- a/clippy_lints/src/doc_link_with_quotes.rs +++ b/clippy_lints/src/doc_link_with_quotes.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// /// See also: [`foo`] /// fn bar() {} /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.63.0"] pub DOC_LINK_WITH_QUOTES, pedantic, "possible typo for an intra-doc link" diff --git a/clippy_lints/src/duplicate_mod.rs b/clippy_lints/src/duplicate_mod.rs index e1eb3b632..7ff7068f0 100644 --- a/clippy_lints/src/duplicate_mod.rs +++ b/clippy_lints/src/duplicate_mod.rs @@ -39,7 +39,7 @@ declare_clippy_lint! { /// // a.rs /// use crate::b; /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub DUPLICATE_MOD, suspicious, "file loaded as module multiple times" diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 1ac7bfba0..327865e4c 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_hir; -use clippy_utils::ty::contains_ty; use rustc_hir::intravisit; use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node, Pat, PatKind}; use rustc_infer::infer::TyCtxtInferExt; @@ -30,18 +29,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// # fn foo(bar: usize) {} - /// let x = Box::new(1); - /// foo(*x); - /// println!("{}", *x); + /// fn foo(x: Box) {} /// ``` /// /// Use instead: /// ```rust - /// # fn foo(bar: usize) {} - /// let x = 1; - /// foo(x); - /// println!("{}", x); + /// fn foo(x: u32) {} /// ``` #[clippy::version = "pre 1.29.0"] pub BOXED_LOCAL, @@ -172,7 +165,7 @@ impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { // skip if there is a `self` parameter binding to a type // that contains `Self` (i.e.: `self: Box`), see #4804 if let Some(trait_self_ty) = self.trait_self_ty { - if map.name(cmt.hir_id) == kw::SelfLower && contains_ty(cmt.place.ty(), trait_self_ty) { + if map.name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) { return; } } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index df9b41d2c..bb50e8fca 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -172,7 +172,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { expr.span, "logarithm for bases 2, 10 and e can be computed more accurately", "consider using", - format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method), + format!("{}.{}()", Sugg::hir(cx, &args[0], "..").maybe_par(), method), Applicability::MachineApplicable, ); } @@ -263,13 +263,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { ( SUBOPTIMAL_FLOPS, "square-root of a number can be computed more efficiently and accurately", - format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")), + format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..").maybe_par()), ) } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { ( IMPRECISE_FLOPS, "cube-root of a number can be computed more accurately", - format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")), + format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..").maybe_par()), ) } else if let Some(exponent) = get_integer_from_float_constant(&value) { ( @@ -277,7 +277,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { "exponentiation with integer powers can be computed more efficiently", format!( "{}.powi({})", - Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &args[0], "..").maybe_par(), numeric_literal::format(&exponent.to_string(), None, false) ), ) @@ -327,7 +327,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { "consider using", format!( "{}.mul_add({}, {})", - Sugg::hir(cx, &args[0], ".."), + Sugg::hir(cx, &args[0], "..").maybe_par(), Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, other_addend, ".."), ), @@ -418,7 +418,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { "consider using", format!( "{}.exp_m1()", - Sugg::hir(cx, self_arg, "..") + Sugg::hir(cx, self_arg, "..").maybe_par() ), Applicability::MachineApplicable, ); @@ -550,11 +550,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { then { let positive_abs_sugg = ( "manual implementation of `abs` method", - format!("{}.abs()", Sugg::hir(cx, body, "..")), + format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), ); let negative_abs_sugg = ( "manual implementation of negation of `abs` method", - format!("-{}.abs()", Sugg::hir(cx, body, "..")), + format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), ); let sugg = if is_testing_positive(cx, cond, body) { if if_expr_positive { @@ -621,7 +621,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "log base can be expressed more clearly", "consider using", - format!("{}.log({})", Sugg::hir(cx, largs_self, ".."), Sugg::hir(cx, rargs_self, ".."),), + format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),), Applicability::MachineApplicable, ); } @@ -651,7 +651,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) { - let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")); + let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); if_chain! { if let ExprKind::Lit(ref literal) = mul_lhs.kind; if let ast::LitKind::Float(ref value, float_type) = literal.node; @@ -677,7 +677,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) { - let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")); + let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); if_chain! { if let ExprKind::Lit(ref literal) = mul_lhs.kind; if let ast::LitKind::Float(ref value, float_type) = literal.node; diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 925a8cb8d..0c5851cdb 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn}; -use clippy_utils::source::{snippet_opt, snippet_with_applicability}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; @@ -56,29 +56,27 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { }; let mut applicability = Applicability::MachineApplicable; - if format_args.value_args.is_empty() { - match *format_args.format_string_parts { + if format_args.args.is_empty() { + match *format_args.format_string.parts { [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability), [_] => { - if let Some(s_src) = snippet_opt(cx, format_args.format_string_span) { - // Simulate macro expansion, converting {{ and }} to { and }. - let s_expand = s_src.replace("{{", "{").replace("}}", "}"); - let sugg = format!("{}.to_string()", s_expand); - span_useless_format(cx, call_site, sugg, applicability); - } + // Simulate macro expansion, converting {{ and }} to { and }. + let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}"); + let sugg = format!("{}.to_string()", s_expand); + span_useless_format(cx, call_site, sugg, applicability); }, [..] => {}, } - } else if let [value] = *format_args.value_args { + } else if let [arg] = &*format_args.args { + let value = arg.param.value; if_chain! { - if format_args.format_string_parts == [kw::Empty]; + if format_args.format_string.parts == [kw::Empty]; if match cx.typeck_results().expr_ty(value).peel_refs().kind() { ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()), ty::Str => true, _ => false, }; - if let Some(args) = format_args.args(); - if args.iter().all(|arg| arg.format_trait == sym::Display && !arg.has_string_formatting()); + if !arg.format.has_string_formatting(); then { let is_new_string = match value.kind { ExprKind::Binary(..) => true, diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 1e6feaac2..9fb9fd997 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; -use clippy_utils::macros::{is_format_macro, FormatArgsArg, FormatArgsExpn}; +use clippy_utils::macros::{is_format_macro, FormatArgsExpn}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::implements_trait; use if_chain::if_chain; +use itertools::Itertools; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; @@ -74,20 +75,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { if let Some(macro_def_id) = outermost_expn_data.macro_def_id; if is_format_macro(cx, macro_def_id); if let ExpnKind::Macro(_, name) = outermost_expn_data.kind; - if let Some(args) = format_args.args(); then { - for (i, arg) in args.iter().enumerate() { - if arg.format_trait != sym::Display { + for arg in &format_args.args { + if arg.format.has_string_formatting() { continue; } - if arg.has_string_formatting() { + if is_aliased(&format_args, arg.param.value.hir_id) { continue; } - if is_aliased(&args, i) { - continue; - } - check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.value); - check_to_string_in_format_args(cx, name, arg.value); + check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value); + check_to_string_in_format_args(cx, name, arg.param.value); } } } @@ -134,45 +131,56 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex if is_diag_trait_item(cx, method_def_id, sym::ToString); let receiver_ty = cx.typeck_results().expr_ty(receiver); if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display); + let (n_needed_derefs, target) = + count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter()); + if implements_trait(cx, target, display_trait_id, &[]); + if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait(); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - let (n_needed_derefs, target) = count_needed_derefs( - receiver_ty, - cx.typeck_results().expr_adjustments(receiver).iter(), - ); - if implements_trait(cx, target, display_trait_id, &[]) { - if n_needed_derefs == 0 { - span_lint_and_sugg( - cx, - TO_STRING_IN_FORMAT_ARGS, - value.span.with_lo(receiver.span.hi()), - &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name), - "remove this", - String::new(), - Applicability::MachineApplicable, - ); - } else { - span_lint_and_sugg( - cx, - TO_STRING_IN_FORMAT_ARGS, - value.span, - &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name), - "use this", - format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs), - Applicability::MachineApplicable, - ); - } + let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]); + if n_needed_derefs == 0 && !needs_ref { + span_lint_and_sugg( + cx, + TO_STRING_IN_FORMAT_ARGS, + value.span.with_lo(receiver.span.hi()), + &format!( + "`to_string` applied to a type that implements `Display` in `{}!` args", + name + ), + "remove this", + String::new(), + Applicability::MachineApplicable, + ); + } else { + span_lint_and_sugg( + cx, + TO_STRING_IN_FORMAT_ARGS, + value.span, + &format!( + "`to_string` applied to a type that implements `Display` in `{}!` args", + name + ), + "use this", + format!( + "{}{:*>width$}{}", + if needs_ref { "&" } else { "" }, + "", + receiver_snippet, + width = n_needed_derefs + ), + Applicability::MachineApplicable, + ); } } } } -// Returns true if `args[i]` "refers to" or "is referred to by" another argument. -fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool { - let value = args[i].value; - args.iter() - .enumerate() - .any(|(j, arg)| i != j && std::ptr::eq(value, arg.value)) +// Returns true if `hir_id` is referred to by multiple format params +fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool { + args.params() + .filter(|param| param.value.hir_id == hir_id) + .at_most_one() + .is_err() } fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 04b5be6c8..d8bc0bf08 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn}; +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn}; use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -168,10 +168,9 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, if let macro_def_id = outer_macro.def_id; if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn); if is_format_macro(cx, macro_def_id); - if let Some(args) = format_args.args(); then { - for arg in args { - if arg.format_trait != impl_trait.name { + for arg in format_args.args { + if arg.format.r#trait != impl_trait.name { continue; } check_format_arg_self(cx, expr, &arg, impl_trait); @@ -180,11 +179,11 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, } } -fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) { +fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) { // Handle multiple dereferencing of references e.g. &&self // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) // Since the argument to fmt is itself a reference: &self - let reference = peel_ref_operators(cx, arg.value); + let reference = peel_ref_operators(cx, arg.param.value); let map = cx.tcx.hir(); // Is the reference self? if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 73261fb8a..90911e0bf 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -1,6 +1,6 @@ mod must_use; mod not_unsafe_ptr_arg_deref; -mod result_unit_err; +mod result; mod too_many_arguments; mod too_many_lines; @@ -217,17 +217,62 @@ declare_clippy_lint! { "public function returning `Result` with an `Err` type of `()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for functions that return `Result` with an unusually large + /// `Err`-variant. + /// + /// ### Why is this bad? + /// A `Result` is at least as large as the `Err`-variant. While we + /// expect that variant to be seldomly used, the compiler needs to reserve + /// and move that much memory every single time. + /// + /// ### Known problems + /// The size determined by Clippy is platform-dependent. + /// + /// ### Examples + /// ```rust + /// pub enum ParseError { + /// UnparsedBytes([u8; 512]), + /// UnexpectedEof, + /// } + /// + /// // The `Result` has at least 512 bytes, even in the `Ok`-case + /// pub fn parse() -> Result<(), ParseError> { + /// Ok(()) + /// } + /// ``` + /// should be + /// ``` + /// pub enum ParseError { + /// UnparsedBytes(Box<[u8; 512]>), + /// UnexpectedEof, + /// } + /// + /// // The `Result` is slightly larger than a pointer + /// pub fn parse() -> Result<(), ParseError> { + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.64.0"] + pub RESULT_LARGE_ERR, + perf, + "function returning `Result` with large `Err` type" +} + #[derive(Copy, Clone)] pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, + large_error_threshold: u64, } impl Functions { - pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self { + pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self { Self { too_many_arguments_threshold, too_many_lines_threshold, + large_error_threshold, } } } @@ -240,6 +285,7 @@ impl_lint_pass!(Functions => [ DOUBLE_MUST_USE, MUST_USE_CANDIDATE, RESULT_UNIT_ERR, + RESULT_LARGE_ERR, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -259,18 +305,18 @@ impl<'tcx> LateLintPass<'tcx> for Functions { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { must_use::check_item(cx, item); - result_unit_err::check_item(cx, item); + result::check_item(cx, item, self.large_error_threshold); } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { must_use::check_impl_item(cx, item); - result_unit_err::check_impl_item(cx, item); + result::check_impl_item(cx, item, self.large_error_threshold); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold); not_unsafe_ptr_arg_deref::check_trait_item(cx, item); must_use::check_trait_item(cx, item); - result_unit_err::check_trait_item(cx, item); + result::check_trait_item(cx, item, self.large_error_threshold); } } diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs new file mode 100644 index 000000000..af520a493 --- /dev/null +++ b/clippy_lints/src/functions/result.rs @@ -0,0 +1,100 @@ +use rustc_errors::Diagnostic; +use rustc_hir as hir; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{sym, Span}; +use rustc_typeck::hir_ty_to_ty; + +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; +use clippy_utils::trait_ref_of_method; +use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item}; + +use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR}; + +/// The type of the `Err`-variant in a `std::result::Result` returned by the +/// given `FnDecl` +fn result_err_ty<'tcx>( + cx: &LateContext<'tcx>, + decl: &hir::FnDecl<'tcx>, + item_span: Span, +) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> { + if !in_external_macro(cx.sess(), item_span) + && let hir::FnRetTy::Return(hir_ty) = decl.output + && let ty = hir_ty_to_ty(cx.tcx, hir_ty) + && is_type_diagnostic_item(cx, ty, sym::Result) + && let ty::Adt(_, substs) = ty.kind() + { + let err_ty = substs.type_at(1); + Some((hir_ty, err_ty)) + } else { + None + } +} + +pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) { + if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) + { + if cx.access_levels.is_exported(item.def_id) { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } +} + +pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) { + // Don't lint if method is a trait's implementation, we can't do anything about those + if let hir::ImplItemKind::Fn(ref sig, _) = item.kind + && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) + && trait_ref_of_method(cx, item.def_id).is_none() + { + if cx.access_levels.is_exported(item.def_id) { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) { + if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.span) { + if cx.access_levels.is_exported(item.def_id) { + check_result_unit_err(cx, err_ty, fn_header_span); + } + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + } + } +} + +fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) { + if err_ty.is_unit() { + span_lint_and_help( + cx, + RESULT_UNIT_ERR, + fn_header_span, + "this returns a `Result<_, ()>`", + None, + "use a custom `Error` type instead", + ); + } +} + +fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) { + let ty_size = approx_ty_size(cx, err_ty); + if ty_size >= large_err_threshold { + span_lint_and_then( + cx, + RESULT_LARGE_ERR, + hir_ty_span, + "the `Err`-variant returned from this function is very large", + |diag: &mut Diagnostic| { + diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes")); + diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`")); + }, + ); + } +} diff --git a/clippy_lints/src/functions/result_unit_err.rs b/clippy_lints/src/functions/result_unit_err.rs deleted file mode 100644 index 2e63a1f92..000000000 --- a/clippy_lints/src/functions/result_unit_err.rs +++ /dev/null @@ -1,66 +0,0 @@ -use rustc_hir as hir; -use rustc_lint::{LateContext, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; -use rustc_span::{sym, Span}; -use rustc_typeck::hir_ty_to_ty; - -use if_chain::if_chain; - -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::trait_ref_of_method; -use clippy_utils::ty::is_type_diagnostic_item; - -use super::RESULT_UNIT_ERR; - -pub(super) fn check_item(cx: &LateContext<'_>, item: &hir::Item<'_>) { - if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &hir::ImplItem<'_>) { - if let hir::ImplItemKind::Fn(ref sig, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public && trait_ref_of_method(cx, item.def_id).is_none() { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>) { - if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { - let is_public = cx.access_levels.is_exported(item.def_id); - let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); - if is_public { - check_result_unit_err(cx, sig.decl, item.span, fn_header_span); - } - } -} - -fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) { - if_chain! { - if !in_external_macro(cx.sess(), item_span); - if let hir::FnRetTy::Return(ty) = decl.output; - let ty = hir_ty_to_ty(cx.tcx, ty); - if is_type_diagnostic_item(cx, ty, sym::Result); - if let ty::Adt(_, substs) = ty.kind(); - let err_ty = substs.type_at(1); - if err_ty.is_unit(); - then { - span_lint_and_help( - cx, - RESULT_UNIT_ERR, - fn_header_span, - "this returns a `Result<_, ()>`", - None, - "use a custom `Error` type instead", - ); - } - } -} diff --git a/clippy_lints/src/get_first.rs b/clippy_lints/src/get_first.rs deleted file mode 100644 index 529f7baba..000000000 --- a/clippy_lints/src/get_first.rs +++ /dev/null @@ -1,68 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_slice_of_primitives, match_def_path, paths}; -use if_chain::if_chain; -use rustc_ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Spanned; - -declare_clippy_lint! { - /// ### What it does - /// Checks for using `x.get(0)` instead of - /// `x.first()`. - /// - /// ### Why is this bad? - /// Using `x.first()` is easier to read and has the same - /// result. - /// - /// ### Example - /// ```rust - /// let x = vec![2, 3, 5]; - /// let first_element = x.get(0); - /// ``` - /// - /// Use instead: - /// ```rust - /// let x = vec![2, 3, 5]; - /// let first_element = x.first(); - /// ``` - #[clippy::version = "1.63.0"] - pub GET_FIRST, - style, - "Using `x.get(0)` when `x.first()` is simpler" -} -declare_lint_pass!(GetFirst => [GET_FIRST]); - -impl<'tcx> LateLintPass<'tcx> for GetFirst { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if_chain! { - if let hir::ExprKind::MethodCall(_, [struct_calling_on, method_arg], _) = &expr.kind; - if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match_def_path(cx, expr_def_id, &paths::SLICE_GET); - - if let Some(_) = is_slice_of_primitives(cx, struct_calling_on); - if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = method_arg.kind; - - then { - let mut applicability = Applicability::MachineApplicable; - let slice_name = snippet_with_applicability( - cx, - struct_calling_on.span, "..", - &mut applicability, - ); - span_lint_and_sugg( - cx, - GET_FIRST, - expr.span, - &format!("accessing first element with `{0}.get(0)`", slice_name), - "try", - format!("{}.first()", slice_name), - applicability, - ); - } - } - } -} diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index e95017007..4d703d691 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,8 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::SpanlessEq; use if_chain::if_chain; +use rustc_errors::Diagnostic; use rustc_hir::intravisit::{self as visit, Visitor}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -45,16 +46,8 @@ declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]); impl<'tcx> LateLintPass<'tcx> for IfLetMutex { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - let mut arm_visit = ArmVisitor { - mutex_lock_called: false, - found_mutex: None, - cx, - }; - let mut op_visit = OppVisitor { - mutex_lock_called: false, - found_mutex: None, - cx, - }; + let mut arm_visit = ArmVisitor { found_mutex: None, cx }; + let mut op_visit = OppVisitor { found_mutex: None, cx }; if let Some(higher::IfLet { let_expr, if_then, @@ -63,18 +56,28 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { }) = higher::IfLet::hir(cx, expr) { op_visit.visit_expr(let_expr); - if op_visit.mutex_lock_called { + if let Some(op_mutex) = op_visit.found_mutex { arm_visit.visit_expr(if_then); arm_visit.visit_expr(if_else); - if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) { - span_lint_and_help( + if let Some(arm_mutex) = arm_visit.found_mutex_if_same_as(op_mutex) { + let diag = |diag: &mut Diagnostic| { + diag.span_label( + op_mutex.span, + "this Mutex will remain locked for the entire `if let`-block...", + ); + diag.span_label( + arm_mutex.span, + "... and is tried to lock again here, which will always deadlock.", + ); + diag.help("move the lock call outside of the `if let ...` expression"); + }; + span_lint_and_then( cx, IF_LET_MUTEX, expr.span, "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", - None, - "move the lock call outside of the `if let ...` expression", + diag, ); } } @@ -84,7 +87,6 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex { /// Checks if `Mutex::lock` is called in the `if let` expr. pub struct OppVisitor<'a, 'tcx> { - mutex_lock_called: bool, found_mutex: Option<&'tcx Expr<'tcx>>, cx: &'a LateContext<'tcx>, } @@ -93,7 +95,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) { self.found_mutex = Some(mutex); - self.mutex_lock_called = true; return; } visit::walk_expr(self, expr); @@ -102,7 +103,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> { /// Checks if `Mutex::lock` is called in any of the branches. pub struct ArmVisitor<'a, 'tcx> { - mutex_lock_called: bool, found_mutex: Option<&'tcx Expr<'tcx>>, cx: &'a LateContext<'tcx>, } @@ -111,7 +111,6 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) { self.found_mutex = Some(mutex); - self.mutex_lock_called = true; return; } visit::walk_expr(self, expr); @@ -119,9 +118,12 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> { } impl<'tcx, 'l> ArmVisitor<'tcx, 'l> { - fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool { - self.found_mutex - .map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)) + fn found_mutex_if_same_as(&self, op_mutex: &Expr<'_>) -> Option<&Expr<'_>> { + self.found_mutex.and_then(|arm_mutex| { + SpanlessEq::new(self.cx) + .eq_expr(op_mutex, arm_mutex) + .then_some(arm_mutex) + }) } } @@ -129,7 +131,7 @@ fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Opt if_chain! { if let ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind; if path.ident.as_str() == "lock"; - let ty = cx.typeck_results().expr_ty(self_arg); + let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); if is_type_diagnostic_item(cx, ty, sym::Mutex); then { Some(self_arg) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index b8d227855..11c432478 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks}; -use if_chain::if_chain; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -11,10 +11,12 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { /// ### What it does - /// Checks for if-else that could be written to `bool::then`. + /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. /// /// ### Why is this bad? - /// Looks a little redundant. Using `bool::then` helps it have less lines of code. + /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. + /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated + /// in comparison to `bool::then`. /// /// ### Example /// ```rust @@ -39,7 +41,7 @@ declare_clippy_lint! { #[clippy::version = "1.53.0"] pub IF_THEN_SOME_ELSE_NONE, restriction, - "Finds if-else that could be written using `bool::then`" + "Finds if-else that could be written using either `bool::then` or `bool::then_some`" } pub struct IfThenSomeElseNone { @@ -56,7 +58,7 @@ impl IfThenSomeElseNone { impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !meets_msrv(self.msrv, msrvs::BOOL_THEN) { return; } @@ -70,43 +72,47 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { return; } - if_chain! { - if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr); - if let ExprKind::Block(then_block, _) = then.kind; - if let Some(then_expr) = then_block.expr; - if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind; - if let ExprKind::Path(ref then_call_qpath) = then_call.kind; - if is_lang_ctor(cx, then_call_qpath, OptionSome); - if let ExprKind::Path(ref qpath) = peel_blocks(els).kind; - if is_lang_ctor(cx, qpath, OptionNone); - if !stmts_contains_early_return(then_block.stmts); - then { - let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]"); - let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { - format!("({})", cond_snip) - } else { - cond_snip.into_owned() - }; - let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, ""); - let closure_body = if then_block.stmts.is_empty() { - arg_snip.into_owned() - } else { - format!("{{ /* snippet */ {} }}", arg_snip) - }; - let help = format!( - "consider using `bool::then` like: `{}.then(|| {})`", - cond_snip, - closure_body, - ); - span_lint_and_help( - cx, - IF_THEN_SOME_ELSE_NONE, - expr.span, - "this could be simplified with `bool::then`", - None, - &help, - ); - } + if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr) + && let ExprKind::Block(then_block, _) = then.kind + && let Some(then_expr) = then_block.expr + && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let ExprKind::Path(ref then_call_qpath) = then_call.kind + && is_lang_ctor(cx, then_call_qpath, OptionSome) + && let ExprKind::Path(ref qpath) = peel_blocks(els).kind + && is_lang_ctor(cx, qpath, OptionNone) + && !stmts_contains_early_return(then_block.stmts) + { + let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]"); + let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { + format!("({})", cond_snip) + } else { + cond_snip.into_owned() + }; + let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, ""); + let mut method_body = if then_block.stmts.is_empty() { + arg_snip.into_owned() + } else { + format!("{{ /* snippet */ {} }}", arg_snip) + }; + let method_name = if switch_to_eager_eval(cx, expr) && meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) { + "then_some" + } else { + method_body.insert_str(0, "|| "); + "then" + }; + + let help = format!( + "consider using `bool::{}` like: `{}.{}({})`", + method_name, cond_snip, method_name, method_body, + ); + span_lint_and_help( + cx, + IF_THEN_SOME_ELSE_NONE, + expr.span, + &format!("this could be simplified with `bool::{}`", method_name), + None, + &help, + ); } } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 01082cc8e..134cbbf7b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -20,12 +20,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(booleans::OVERLY_COMPLEX_BOOL_EXPR), LintId::of(borrow_deref_ref::BORROW_DEREF_REF), - LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CAST_ABS_TO_UNSIGNED), LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), + LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::FN_TO_NUMERIC_CAST), LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), @@ -80,9 +80,9 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(functions::DOUBLE_MUST_USE), LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(functions::RESULT_LARGE_ERR), LintId::of(functions::RESULT_UNIT_ERR), LintId::of(functions::TOO_MANY_ARGUMENTS), - LintId::of(get_first::GET_FIRST), LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(infinite_iter::INFINITE_ITER), @@ -128,7 +128,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID), LintId::of(manual_retain::MANUAL_RETAIN), LintId::of(manual_strip::MANUAL_STRIP), - LintId::of(map_clone::MAP_CLONE), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(match_result_ok::MATCH_RESULT_OK), @@ -150,17 +149,20 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT), LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT), LintId::of(methods::BIND_INSTEAD_OF_MAP), + LintId::of(methods::BYTES_COUNT_TO_LEN), LintId::of(methods::BYTES_NTH), LintId::of(methods::CHARS_LAST_CMP), LintId::of(methods::CHARS_NEXT_CMP), LintId::of(methods::CLONE_DOUBLE_REF), LintId::of(methods::CLONE_ON_COPY), + LintId::of(methods::COLLAPSIBLE_STR_REPLACE), LintId::of(methods::ERR_EXPECT), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), LintId::of(methods::FLAT_MAP_IDENTITY), + LintId::of(methods::GET_FIRST), LintId::of(methods::GET_LAST_WITH_LEN), LintId::of(methods::INSPECT_FOR_EACH), LintId::of(methods::INTO_ITER_ON_REF), @@ -178,13 +180,16 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(methods::MANUAL_SPLIT_ONCE), LintId::of(methods::MANUAL_STR_REPEAT), + LintId::of(methods::MAP_CLONE), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::MAP_FLATTEN), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::MUT_MUTEX_LOCK), LintId::of(methods::NEEDLESS_OPTION_AS_DEREF), LintId::of(methods::NEEDLESS_OPTION_TAKE), LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::NEW_RET_NO_SELF), + LintId::of(methods::NONSENSICAL_OPEN_OPTIONS), LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), @@ -193,6 +198,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::OR_THEN_UNWRAP), + LintId::of(methods::RANGE_ZIP_WITH_LEN), + LintId::of(methods::REPEAT_ONCE), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SHOULD_IMPLEMENT_TRAIT), @@ -202,14 +209,18 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::STRING_EXTEND_CHARS), LintId::of(methods::SUSPICIOUS_MAP), LintId::of(methods::SUSPICIOUS_SPLITN), + LintId::of(methods::SUSPICIOUS_TO_OWNED), LintId::of(methods::UNINIT_ASSUMED_INIT), + LintId::of(methods::UNIT_HASH), LintId::of(methods::UNNECESSARY_FILTER_MAP), LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::UNNECESSARY_FOLD), LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS), + LintId::of(methods::UNNECESSARY_SORT_BY), LintId::of(methods::UNNECESSARY_TO_OWNED), LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT), LintId::of(methods::USELESS_ASREF), + LintId::of(methods::VEC_RESIZE_TO_ZERO), LintId::of(methods::WRONG_SELF_CONVENTION), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), @@ -224,8 +235,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), LintId::of(misc_early::ZERO_PREFIXED_LITERAL), LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION), + LintId::of(multi_assignments::MULTI_ASSIGNMENTS), LintId::of(mut_key::MUTABLE_KEY_TYPE), - LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), LintId::of(needless_bool::BOOL_COMPARISON), @@ -245,7 +256,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(octal_escapes::OCTAL_ESCAPES), - LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::ASSIGN_OP_PATTERN), LintId::of(operators::BAD_BIT_MASK), @@ -275,7 +286,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(question_mark::QUESTION_MARK), LintId::of(ranges::MANUAL_RANGE_CONTAINS), - LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), @@ -286,7 +296,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(reference::DEREF_ADDROF), LintId::of(regex::INVALID_REGEX), - LintId::of(repeat_once::REPEAT_ONCE), LintId::of(returns::LET_AND_RETURN), LintId::of(returns::NEEDLESS_RETURN), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), @@ -314,10 +323,10 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT), LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES), LintId::of(transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(transmute::TRANSMUTING_NULL), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::USELESS_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), - LintId::of(transmuting_null::TRANSMUTING_NULL), LintId::of(types::BORROWED_BOX), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), @@ -325,7 +334,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(types::VEC_BOX), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), - LintId::of(unit_hash::UNIT_HASH), LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(unit_types::LET_UNIT_VALUE), LintId::of(unit_types::UNIT_ARG), @@ -333,9 +341,9 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS), - LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(unused_peekable::UNUSED_PEEKABLE), LintId::of(unused_unit::UNUSED_UNIT), LintId::of(unwrap::PANICKING_UNWRAP), LintId::of(unwrap::UNNECESSARY_UNWRAP), @@ -343,7 +351,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(vec::USELESS_VEC), LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH), - LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO), + LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS), LintId::of(write::PRINTLN_EMPTY_STRING), LintId::of(write::PRINT_LITERAL), LintId::of(write::PRINT_WITH_NEWLINE), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 3784d3c68..aa247352f 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -6,7 +6,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(attrs::DEPRECATED_CFG_ATTR), LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(borrow_deref_ref::BORROW_DEREF_REF), - LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::UNNECESSARY_CAST), LintId::of(dereference::EXPLICIT_AUTO_DEREF), @@ -33,6 +32,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), LintId::of(methods::BIND_INSTEAD_OF_MAP), + LintId::of(methods::BYTES_COUNT_TO_LEN), LintId::of(methods::CLONE_ON_COPY), LintId::of(methods::FILTER_MAP_IDENTITY), LintId::of(methods::FILTER_NEXT), @@ -51,10 +51,13 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OR_THEN_UNWRAP), + LintId::of(methods::RANGE_ZIP_WITH_LEN), + LintId::of(methods::REPEAT_ONCE), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), LintId::of(methods::UNNECESSARY_FIND_MAP), + LintId::of(methods::UNNECESSARY_SORT_BY), LintId::of(methods::USELESS_ASREF), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), @@ -69,6 +72,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(operators::DOUBLE_COMPARISONS), LintId::of(operators::DURATION_SUBSEC), LintId::of(operators::IDENTITY_OP), @@ -76,11 +80,9 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), - LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_slicing::REDUNDANT_SLICING), LintId::of(reference::DEREF_ADDROF), - LintId::of(repeat_once::REPEAT_ONCE), LintId::of(strings::STRING_FROM_UTF8_AS_BYTES), LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS), LintId::of(swap::MANUAL_SWAP), @@ -99,7 +101,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(types::TYPE_COMPLEXITY), LintId::of(types::VEC_BOX), LintId::of(unit_types::UNIT_ARG), - LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unwrap::UNNECESSARY_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 006275d13..bb94037ec 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -39,12 +39,14 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT), LintId::of(methods::CLONE_DOUBLE_REF), LintId::of(methods::ITERATOR_STEP_BY_ZERO), + LintId::of(methods::NONSENSICAL_OPEN_OPTIONS), LintId::of(methods::SUSPICIOUS_SPLITN), LintId::of(methods::UNINIT_ASSUMED_INIT), + LintId::of(methods::UNIT_HASH), + LintId::of(methods::VEC_RESIZE_TO_ZERO), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), - LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(operators::ABSURD_EXTREME_COMPARISONS), LintId::of(operators::BAD_BIT_MASK), LintId::of(operators::CMP_NAN), @@ -62,17 +64,15 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(swap::ALMOST_SWAPPED), + LintId::of(transmute::TRANSMUTING_NULL), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), - LintId::of(transmuting_null::TRANSMUTING_NULL), LintId::of(unicode::INVISIBLE_CHARACTERS), LintId::of(uninit_vec::UNINIT_VEC), - LintId::of(unit_hash::UNIT_HASH), LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(unit_types::UNIT_CMP), LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(unwrap::PANICKING_UNWRAP), - LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index c540573b8..fd20e0165 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -38,7 +38,6 @@ store.register_lints(&[ almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE, approx_const::APPROX_CONSTANT, as_conversions::AS_CONVERSIONS, - as_underscore::AS_UNDERSCORE, asm_syntax::INLINE_ASM_X86_ATT_SYNTAX, asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX, assertions_on_constants::ASSERTIONS_ON_CONSTANTS, @@ -59,16 +58,14 @@ store.register_lints(&[ bool_assert_comparison::BOOL_ASSERT_COMPARISON, booleans::NONMINIMAL_BOOL, booleans::OVERLY_COMPLEX_BOOL_EXPR, - borrow_as_ptr::BORROW_AS_PTR, borrow_deref_ref::BORROW_DEREF_REF, - bytecount::NAIVE_BYTECOUNT, - bytes_count_to_len::BYTES_COUNT_TO_LEN, cargo::CARGO_COMMON_METADATA, cargo::MULTIPLE_CRATE_VERSIONS, cargo::NEGATIVE_FEATURE_NAMES, cargo::REDUNDANT_FEATURE_NAMES, cargo::WILDCARD_DEPENDENCIES, - case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::AS_UNDERSCORE, + casts::BORROW_AS_PTR, casts::CAST_ABS_TO_UNSIGNED, casts::CAST_ENUM_CONSTRUCTOR, casts::CAST_ENUM_TRUNCATION, @@ -80,6 +77,7 @@ store.register_lints(&[ casts::CAST_REF_TO_MUT, casts::CAST_SIGN_LOSS, casts::CAST_SLICE_DIFFERENT_SIZES, + casts::CAST_SLICE_FROM_RAW_PARTS, casts::CHAR_LIT_AS_U8, casts::FN_TO_NUMERIC_CAST, casts::FN_TO_NUMERIC_CAST_ANY, @@ -173,11 +171,11 @@ store.register_lints(&[ functions::MUST_USE_CANDIDATE, functions::MUST_USE_UNIT, functions::NOT_UNSAFE_PTR_ARG_DEREF, + functions::RESULT_LARGE_ERR, functions::RESULT_UNIT_ERR, functions::TOO_MANY_ARGUMENTS, functions::TOO_MANY_LINES, future_not_send::FUTURE_NOT_SEND, - get_first::GET_FIRST, if_let_mutex::IF_LET_MUTEX, if_not_else::IF_NOT_ELSE, if_then_some_else_none::IF_THEN_SOME_ELSE_NONE, @@ -246,12 +244,10 @@ store.register_lints(&[ manual_bits::MANUAL_BITS, manual_instant_elapsed::MANUAL_INSTANT_ELAPSED, manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, - manual_ok_or::MANUAL_OK_OR, manual_rem_euclid::MANUAL_REM_EUCLID, manual_retain::MANUAL_RETAIN, + manual_string_new::MANUAL_STRING_NEW, manual_strip::MANUAL_STRIP, - map_clone::MAP_CLONE, - map_err_ignore::MAP_ERR_IGNORE, map_unit_fn::OPTION_MAP_UNIT_FN, map_unit_fn::RESULT_MAP_UNIT_FN, match_result_ok::MATCH_RESULT_OK, @@ -284,13 +280,16 @@ store.register_lints(&[ mem_replace::MEM_REPLACE_WITH_DEFAULT, mem_replace::MEM_REPLACE_WITH_UNINIT, methods::BIND_INSTEAD_OF_MAP, + methods::BYTES_COUNT_TO_LEN, methods::BYTES_NTH, + methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, methods::CHARS_LAST_CMP, methods::CHARS_NEXT_CMP, methods::CLONED_INSTEAD_OF_COPIED, methods::CLONE_DOUBLE_REF, methods::CLONE_ON_COPY, methods::CLONE_ON_REF_PTR, + methods::COLLAPSIBLE_STR_REPLACE, methods::ERR_EXPECT, methods::EXPECT_FUN_CALL, methods::EXPECT_USED, @@ -302,6 +301,7 @@ store.register_lints(&[ methods::FLAT_MAP_IDENTITY, methods::FLAT_MAP_OPTION, methods::FROM_ITER_INSTEAD_OF_COLLECT, + methods::GET_FIRST, methods::GET_LAST_WITH_LEN, methods::GET_UNWRAP, methods::IMPLICIT_CLONE, @@ -315,22 +315,30 @@ store.register_lints(&[ methods::ITER_NEXT_SLICE, methods::ITER_NTH, methods::ITER_NTH_ZERO, + methods::ITER_ON_EMPTY_COLLECTIONS, + methods::ITER_ON_SINGLE_ITEMS, methods::ITER_OVEREAGER_CLONED, methods::ITER_SKIP_NEXT, methods::ITER_WITH_DRAIN, methods::MANUAL_FILTER_MAP, methods::MANUAL_FIND_MAP, + methods::MANUAL_OK_OR, methods::MANUAL_SATURATING_ARITHMETIC, methods::MANUAL_SPLIT_ONCE, methods::MANUAL_STR_REPEAT, + methods::MAP_CLONE, methods::MAP_COLLECT_RESULT_UNIT, + methods::MAP_ERR_IGNORE, methods::MAP_FLATTEN, methods::MAP_IDENTITY, methods::MAP_UNWRAP_OR, + methods::MUT_MUTEX_LOCK, + methods::NAIVE_BYTECOUNT, methods::NEEDLESS_OPTION_AS_DEREF, methods::NEEDLESS_OPTION_TAKE, methods::NEEDLESS_SPLITN, methods::NEW_RET_NO_SELF, + methods::NONSENSICAL_OPEN_OPTIONS, methods::NO_EFFECT_REPLACE, methods::OBFUSCATED_IF_ELSE, methods::OK_EXPECT, @@ -339,25 +347,34 @@ store.register_lints(&[ methods::OPTION_MAP_OR_NONE, methods::OR_FUN_CALL, methods::OR_THEN_UNWRAP, + methods::PATH_BUF_PUSH_OVERWRITE, + methods::RANGE_ZIP_WITH_LEN, + methods::REPEAT_ONCE, methods::RESULT_MAP_OR_INTO_OPTION, methods::SEARCH_IS_SOME, methods::SHOULD_IMPLEMENT_TRAIT, methods::SINGLE_CHAR_ADD_STR, methods::SINGLE_CHAR_PATTERN, methods::SKIP_WHILE_NEXT, + methods::STABLE_SORT_PRIMITIVE, methods::STRING_EXTEND_CHARS, methods::SUSPICIOUS_MAP, methods::SUSPICIOUS_SPLITN, + methods::SUSPICIOUS_TO_OWNED, methods::UNINIT_ASSUMED_INIT, + methods::UNIT_HASH, methods::UNNECESSARY_FILTER_MAP, methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, methods::UNNECESSARY_JOIN, methods::UNNECESSARY_LAZY_EVALUATIONS, + methods::UNNECESSARY_SORT_BY, methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, methods::UNWRAP_USED, methods::USELESS_ASREF, + methods::VEC_RESIZE_TO_ZERO, + methods::VERBOSE_FILE_READS, methods::WRONG_SELF_CONVENTION, methods::ZST_OFFSET, minmax::MIN_MAX, @@ -384,9 +401,9 @@ store.register_lints(&[ mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION, module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, + multi_assignments::MULTI_ASSIGNMENTS, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, - mut_mutex_lock::MUT_MUTEX_LOCK, mut_reference::UNNECESSARY_MUT_PASSED, mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL, mutex_atomic::MUTEX_ATOMIC, @@ -418,7 +435,6 @@ store.register_lints(&[ nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES, octal_escapes::OCTAL_ESCAPES, only_used_in_recursion::ONLY_USED_IN_RECURSION, - open_options::NONSENSICAL_OPEN_OPTIONS, operators::ABSURD_EXTREME_COMPARISONS, operators::ARITHMETIC, operators::ASSIGN_OP_PATTERN, @@ -457,7 +473,6 @@ store.register_lints(&[ partialeq_to_none::PARTIALEQ_TO_NONE, pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE, pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF, - path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, pattern_type_mismatch::PATTERN_TYPE_MISMATCH, precedence::PRECEDENCE, ptr::CMP_NULL, @@ -470,7 +485,6 @@ store.register_lints(&[ ranges::MANUAL_RANGE_CONTAINS, ranges::RANGE_MINUS_ONE, ranges::RANGE_PLUS_ONE, - ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT, read_zero_byte_vec::READ_ZERO_BYTE_VEC, @@ -486,7 +500,6 @@ store.register_lints(&[ reference::DEREF_ADDROF, regex::INVALID_REGEX, regex::TRIVIAL_REGEX, - repeat_once::REPEAT_ONCE, return_self_not_must_use::RETURN_SELF_NOT_MUST_USE, returns::LET_AND_RETURN, returns::NEEDLESS_RETURN, @@ -501,7 +514,6 @@ store.register_lints(&[ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - stable_sort_primitive::STABLE_SORT_PRIMITIVE, std_instead_of_core::ALLOC_INSTEAD_OF_CORE, std_instead_of_core::STD_INSTEAD_OF_ALLOC, std_instead_of_core::STD_INSTEAD_OF_CORE, @@ -537,10 +549,10 @@ store.register_lints(&[ transmute::TRANSMUTE_PTR_TO_PTR, transmute::TRANSMUTE_PTR_TO_REF, transmute::TRANSMUTE_UNDEFINED_REPR, + transmute::TRANSMUTING_NULL, transmute::UNSOUND_COLLECTION_TRANSMUTE, transmute::USELESS_TRANSMUTE, transmute::WRONG_TRANSMUTE, - transmuting_null::TRANSMUTING_NULL, types::BORROWED_BOX, types::BOX_COLLECTION, types::LINKEDLIST, @@ -555,7 +567,6 @@ store.register_lints(&[ unicode::NON_ASCII_LITERAL, unicode::UNICODE_NOT_NFC, uninit_vec::UNINIT_VEC, - unit_hash::UNIT_HASH, unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD, unit_types::LET_UNIT_VALUE, unit_types::UNIT_ARG, @@ -564,12 +575,12 @@ store.register_lints(&[ unnamed_address::VTABLE_ADDRESS_COMPARISONS, unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, - unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, unnested_or_patterns::UNNESTED_OR_PATTERNS, unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, unused_async::UNUSED_ASYNC, unused_io_amount::UNUSED_IO_AMOUNT, + unused_peekable::UNUSED_PEEKABLE, unused_rounding::UNUSED_ROUNDING, unused_self::UNUSED_SELF, unused_unit::UNUSED_UNIT, @@ -581,10 +592,9 @@ store.register_lints(&[ useless_conversion::USELESS_CONVERSION, vec::USELESS_VEC, vec_init_then_push::VEC_INIT_THEN_PUSH, - vec_resize_to_zero::VEC_RESIZE_TO_ZERO, - verbose_file_reads::VERBOSE_FILE_READS, wildcard_imports::ENUM_GLOB_USE, wildcard_imports::WILDCARD_IMPORTS, + write::POSITIONAL_NAMED_FORMAT_PARAMETERS, write::PRINTLN_EMPTY_STRING, write::PRINT_LITERAL, write::PRINT_STDERR, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 91210b23a..e319e7ee7 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -14,16 +14,17 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE), + LintId::of(methods::ITER_ON_EMPTY_COLLECTIONS), + LintId::of(methods::ITER_ON_SINGLE_ITEMS), LintId::of(methods::ITER_WITH_DRAIN), + LintId::of(methods::PATH_BUF_PUSH_OVERWRITE), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutex_atomic::MUTEX_ATOMIC), LintId::of(mutex_atomic::MUTEX_INTEGER), LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES), - LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(option_if_let_else::OPTION_IF_LET_ELSE), - LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE), LintId::of(regex::TRIVIAL_REGEX), LintId::of(strings::STRING_LIT_AS_BYTES), diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index bd7d1a15a..584ccf55e 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,9 +4,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(borrow_as_ptr::BORROW_AS_PTR), - LintId::of(bytecount::NAIVE_BYTECOUNT), - LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), + LintId::of(casts::BORROW_AS_PTR), LintId::of(casts::CAST_LOSSLESS), LintId::of(casts::CAST_POSSIBLE_TRUNCATION), LintId::of(casts::CAST_POSSIBLE_WRAP), @@ -50,20 +48,24 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(macro_use::MACRO_USE_IMPORTS), LintId::of(manual_assert::MANUAL_ASSERT), LintId::of(manual_instant_elapsed::MANUAL_INSTANT_ELAPSED), - LintId::of(manual_ok_or::MANUAL_OK_OR), + LintId::of(manual_string_new::MANUAL_STRING_NEW), LintId::of(matches::MATCH_BOOL), LintId::of(matches::MATCH_ON_VEC_ITEMS), LintId::of(matches::MATCH_SAME_ARMS), LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), LintId::of(matches::MATCH_WILD_ERR_ARM), LintId::of(matches::SINGLE_MATCH_ELSE), + LintId::of(methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), LintId::of(methods::CLONED_INSTEAD_OF_COPIED), LintId::of(methods::FILTER_MAP_NEXT), LintId::of(methods::FLAT_MAP_OPTION), LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT), LintId::of(methods::IMPLICIT_CLONE), LintId::of(methods::INEFFICIENT_TO_STRING), + LintId::of(methods::MANUAL_OK_OR), LintId::of(methods::MAP_UNWRAP_OR), + LintId::of(methods::NAIVE_BYTECOUNT), + LintId::of(methods::STABLE_SORT_PRIMITIVE), LintId::of(methods::UNNECESSARY_JOIN), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER), @@ -85,7 +87,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(ref_option_ref::REF_OPTION_REF), LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE), LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED), - LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE), LintId::of(strings::STRING_ADD_ASSIGN), LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index e1b90acb9..195ce41e3 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -7,12 +7,14 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(escape::BOXED_LOCAL), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(functions::RESULT_LARGE_ERR), LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(manual_retain::MANUAL_RETAIN), + LintId::of(methods::COLLAPSIBLE_STR_REPLACE), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index a7339ef27..dd1e1e1a8 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -4,11 +4,11 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(as_conversions::AS_CONVERSIONS), - LintId::of(as_underscore::AS_UNDERSCORE), LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX), LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), LintId::of(assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES), LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON), + LintId::of(casts::AS_UNDERSCORE), LintId::of(casts::FN_TO_NUMERIC_CAST_ANY), LintId::of(create_dir::CREATE_DIR), LintId::of(dbg_macro::DBG_MACRO), @@ -30,7 +30,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(large_include_file::LARGE_INCLUDE_FILE), LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE), LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION), - LintId::of(map_err_ignore::MAP_ERR_IGNORE), LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS), LintId::of(matches::TRY_ERR), LintId::of(matches::WILDCARD_ENUM_MATCH_ARM), @@ -39,7 +38,9 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(methods::EXPECT_USED), LintId::of(methods::FILETYPE_IS_FILE), LintId::of(methods::GET_UNWRAP), + LintId::of(methods::MAP_ERR_IGNORE), LintId::of(methods::UNWRAP_USED), + LintId::of(methods::VERBOSE_FILE_READS), LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX), LintId::of(misc_early::UNNEEDED_FIELD_PATTERN), LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), @@ -81,7 +82,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(unicode::NON_ASCII_LITERAL), LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS), LintId::of(unwrap_in_result::UNWRAP_IN_RESULT), - LintId::of(verbose_file_reads::VERBOSE_FILE_READS), LintId::of(write::PRINT_STDERR), LintId::of(write::PRINT_STDOUT), LintId::of(write::USE_DEBUG), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index bfa654238..b5cb078e7 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -29,7 +29,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(functions::DOUBLE_MUST_USE), LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::RESULT_UNIT_ERR), - LintId::of(get_first::GET_FIRST), LintId::of(inherent_to_string::INHERENT_TO_STRING), LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS), LintId::of(len_zero::COMPARISON_TO_EMPTY), @@ -45,7 +44,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(manual_async_fn::MANUAL_ASYNC_FN), LintId::of(manual_bits::MANUAL_BITS), LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), - LintId::of(map_clone::MAP_CLONE), LintId::of(match_result_ok::MATCH_RESULT_OK), LintId::of(matches::COLLAPSIBLE_MATCH), LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH), @@ -61,6 +59,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::CHARS_LAST_CMP), LintId::of(methods::CHARS_NEXT_CMP), LintId::of(methods::ERR_EXPECT), + LintId::of(methods::GET_FIRST), LintId::of(methods::INTO_ITER_ON_REF), LintId::of(methods::IS_DIGIT_ASCII_RADIX), LintId::of(methods::ITER_CLONED_COLLECT), @@ -68,7 +67,9 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::ITER_NTH_ZERO), LintId::of(methods::ITER_SKIP_NEXT), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(methods::MAP_CLONE), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), + LintId::of(methods::MUT_MUTEX_LOCK), LintId::of(methods::NEW_RET_NO_SELF), LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), @@ -88,7 +89,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), LintId::of(misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(misc_early::REDUNDANT_PATTERN), - LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 964992bd9..8f131bbf9 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -11,6 +11,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(casts::CAST_ABS_TO_UNSIGNED), LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), + LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS), LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), LintId::of(drop_forget_ref::DROP_NON_DROP), LintId::of(drop_forget_ref::FORGET_NON_DROP), @@ -24,6 +25,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(loops::MUT_RANGE_BOUND), LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::SUSPICIOUS_MAP), + LintId::of(methods::SUSPICIOUS_TO_OWNED), + LintId::of(multi_assignments::MULTI_ASSIGNMENTS), LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS), @@ -32,4 +35,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF), + LintId::of(unused_peekable::UNUSED_PEEKABLE), + LintId::of(write::POSITIONAL_NAMED_FORMAT_PARAMETERS), ]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ec5c73c13..dfdaf90f0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -171,7 +171,6 @@ mod renamed_lints; mod almost_complete_letter_range; mod approx_const; mod as_conversions; -mod as_underscore; mod asm_syntax; mod assertions_on_constants; mod assertions_on_result_states; @@ -181,12 +180,8 @@ mod await_holding_invalid; mod blocks_in_if_conditions; mod bool_assert_comparison; mod booleans; -mod borrow_as_ptr; mod borrow_deref_ref; -mod bytecount; -mod bytes_count_to_len; mod cargo; -mod case_sensitive_file_extension_comparisons; mod casts; mod checked_conversions; mod cognitive_complexity; @@ -239,7 +234,6 @@ mod from_over_into; mod from_str_radix_10; mod functions; mod future_not_send; -mod get_first; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; @@ -276,12 +270,10 @@ mod manual_async_fn; mod manual_bits; mod manual_instant_elapsed; mod manual_non_exhaustive; -mod manual_ok_or; mod manual_rem_euclid; mod manual_retain; +mod manual_string_new; mod manual_strip; -mod map_clone; -mod map_err_ignore; mod map_unit_fn; mod match_result_ok; mod matches; @@ -298,9 +290,9 @@ mod missing_enforced_import_rename; mod missing_inline; mod mixed_read_write_in_expression; mod module_style; +mod multi_assignments; mod mut_key; mod mut_mut; -mod mut_mutex_lock; mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; @@ -325,7 +317,6 @@ mod non_send_fields_in_send_ty; mod nonstandard_macro_braces; mod octal_escapes; mod only_used_in_recursion; -mod open_options; mod operators; mod option_env_unwrap; mod option_if_let_else; @@ -335,7 +326,6 @@ mod panic_unimplemented; mod partialeq_ne_impl; mod partialeq_to_none; mod pass_by_ref_or_value; -mod path_buf_push_overwrite; mod pattern_type_mismatch; mod precedence; mod ptr; @@ -355,7 +345,6 @@ mod redundant_static_lifetimes; mod ref_option_ref; mod reference; mod regex; -mod repeat_once; mod return_self_not_must_use; mod returns; mod same_name_method; @@ -367,7 +356,6 @@ mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; mod slow_vector_initialization; -mod stable_sort_primitive; mod std_instead_of_core; mod strings; mod strlen_on_c_strings; @@ -381,23 +369,21 @@ mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; mod transmute; -mod transmuting_null; mod types; mod undocumented_unsafe_blocks; mod unicode; mod uninit_vec; -mod unit_hash; mod unit_return_expecting_ord; mod unit_types; mod unnamed_address; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; -mod unnecessary_sort_by; mod unnecessary_wraps; mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; +mod unused_peekable; mod unused_rounding; mod unused_self; mod unused_unit; @@ -408,8 +394,6 @@ mod use_self; mod useless_conversion; mod vec; mod vec_init_then_push; -mod vec_resize_to_zero; -mod verbose_file_reads; mod wildcard_imports; mod write; mod zero_div_zero; @@ -597,7 +581,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions)); store.register_late_pass(|| Box::new(unicode::Unicode)); store.register_late_pass(|| Box::new(uninit_vec::UninitVec)); - store.register_late_pass(|| Box::new(unit_hash::UnitHash)); store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)); store.register_late_pass(|| Box::new(strings::StringAdd)); store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn)); @@ -635,8 +618,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(needless_question_mark::NeedlessQuestionMark)); store.register_late_pass(move || Box::new(casts::Casts::new(msrv))); store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv))); - store.register_late_pass(move || Box::new(map_clone::MapClone::new(msrv))); - store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount)); store.register_late_pass(|| Box::new(same_name_method::SameNameMethod)); let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length; @@ -646,7 +627,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: msrv, )) }); - store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore)); store.register_late_pass(|| Box::new(shadow::Shadow::default())); store.register_late_pass(|| Box::new(unit_types::UnitTypes)); store.register_late_pass(|| Box::new(loops::Loops)); @@ -654,7 +634,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(lifetimes::Lifetimes)); store.register_late_pass(|| Box::new(entry::HashMapPass)); store.register_late_pass(|| Box::new(minmax::MinMaxPass)); - store.register_late_pass(|| Box::new(open_options::OpenOptions)); store.register_late_pass(|| Box::new(zero_div_zero::ZeroDiv)); store.register_late_pass(|| Box::new(mutex_atomic::Mutex)); store.register_late_pass(|| Box::new(needless_update::NeedlessUpdate)); @@ -690,10 +669,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone()))); let too_many_arguments_threshold = conf.too_many_arguments_threshold; let too_many_lines_threshold = conf.too_many_lines_threshold; + let large_error_threshold = conf.large_error_threshold; store.register_late_pass(move || { Box::new(functions::Functions::new( too_many_arguments_threshold, too_many_lines_threshold, + large_error_threshold, )) }); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::>(); @@ -720,7 +701,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ); store.register_late_pass(move || Box::new(pass_by_ref_or_value)); store.register_late_pass(|| Box::new(ref_option_ref::RefOptionRef)); - store.register_late_pass(|| Box::new(bytecount::ByteCount)); store.register_late_pass(|| Box::new(infinite_iter::InfiniteIter)); store.register_late_pass(|| Box::new(inline_fn_without_body::InlineFnWithoutBody)); store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default())); @@ -738,12 +718,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); store.register_late_pass(|| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|| Box::new(slow_vector_initialization::SlowVectorInit)); - store.register_late_pass(|| Box::new(unnecessary_sort_by::UnnecessarySortBy)); store.register_late_pass(move || Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api))); store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants)); store.register_late_pass(|| Box::new(assertions_on_result_states::AssertionsOnResultStates)); - store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull)); - store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite)); store.register_late_pass(|| Box::new(inherent_to_string::InherentToString)); let max_trait_bounds = conf.max_trait_bounds; store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds))); @@ -819,18 +796,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports; store.register_late_pass(move || Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports))); - store.register_late_pass(|| Box::new(verbose_file_reads::VerboseFileReads)); store.register_late_pass(|| Box::new(redundant_pub_crate::RedundantPubCrate::default())); store.register_late_pass(|| Box::new(unnamed_address::UnnamedAddress)); - store.register_late_pass(|| Box::new(dereference::Dereferencing::default())); + store.register_late_pass(move || Box::new(dereference::Dereferencing::new(msrv))); store.register_late_pass(|| Box::new(option_if_let_else::OptionIfLetElse)); store.register_late_pass(|| Box::new(future_not_send::FutureNotSend)); store.register_late_pass(|| Box::new(if_let_mutex::IfLetMutex)); store.register_late_pass(|| Box::new(if_not_else::IfNotElse)); store.register_late_pass(|| Box::new(equatable_if_let::PatternEquality)); - store.register_late_pass(|| Box::new(mut_mutex_lock::MutMutexLock)); store.register_late_pass(|| Box::new(manual_async_fn::ManualAsyncFn)); - store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero)); store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn)); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; store.register_early_pass(move || { @@ -842,10 +816,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher))); store.register_late_pass(|| Box::new(macro_use::MacroUseImports::default())); store.register_late_pass(|| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive)); - store.register_late_pass(|| Box::new(repeat_once::RepeatOnce)); store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult)); - store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr)); store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync)); let disallowed_methods = conf.disallowed_methods.clone(); @@ -857,9 +828,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(strings::StringToString)); store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues)); store.register_late_pass(|| Box::new(vec_init_then_push::VecInitThenPush::default())); - store.register_late_pass(|| { - Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons) - }); store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing)); store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv))); @@ -894,11 +862,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); store.register_late_pass(|| Box::new(init_numbered_fields::NumberedFields)); store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); - store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv))); store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes)); - store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); + store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion::default())); let allow_dbg_in_tests = conf.allow_dbg_in_tests; store.register_late_pass(move || Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests))); let cargo_ignore_publish = conf.cargo_ignore_publish; @@ -912,18 +879,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_late_pass(|| Box::new(format_push_string::FormatPushString)); - store.register_late_pass(|| Box::new(bytes_count_to_len::BytesCountToLen)); let max_include_file_size = conf.max_include_file_size; store.register_late_pass(move || Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size))); store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace)); store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default())); - store.register_late_pass(|| Box::new(get_first::GetFirst)); store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv))); store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef)); store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch)); - store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec)); store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv))); @@ -934,6 +898,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default())); store.register_late_pass(|| Box::new(manual_instant_elapsed::ManualInstantElapsed)); store.register_late_pass(|| Box::new(partialeq_to_none::PartialeqToNone)); + store.register_late_pass(|| Box::new(manual_string_new::ManualStringNew)); + store.register_late_pass(|| Box::new(unused_peekable::UnusedPeekable)); + store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index ddaffc751..6d987f393 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -1,5 +1,6 @@ use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; +use clippy_utils::higher; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; @@ -184,10 +185,19 @@ struct IterFunctionVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { fn visit_block(&mut self, block: &'tcx Block<'tcx>) { for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) { + if check_loop_kind(expr).is_some() { + continue; + } self.visit_block_expr(expr, hir_id); } if let Some(expr) = block.expr { - self.visit_block_expr(expr, None); + if let Some(loop_kind) = check_loop_kind(expr) { + if let LoopKind::Conditional(block_expr) = loop_kind { + self.visit_block_expr(block_expr, None); + } + } else { + self.visit_block_expr(expr, None); + } } } @@ -264,6 +274,28 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { } } +enum LoopKind<'tcx> { + Conditional(&'tcx Expr<'tcx>), + Loop, +} + +fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option> { + if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) { + return Some(LoopKind::Conditional(let_expr)); + } + if let Some(higher::While { condition, .. }) = higher::While::hir(expr) { + return Some(LoopKind::Conditional(condition)); + } + if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) { + return Some(LoopKind::Conditional(arg)); + } + if let ExprKind::Loop { .. } = expr.kind { + return Some(LoopKind::Loop); + } + + None +} + impl<'tcx> IterFunctionVisitor<'_, 'tcx> { fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option) { self.current_statement_hir_id = hir_id; diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index a0ca7e6ff..2502c8f88 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -192,7 +192,7 @@ fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, match output.kind { TyKind::Tup(tys) if tys.is_empty() => { let sugg = "remove the return type"; - Some((sugg, "".into())) + Some((sugg, String::new())) }, _ => { let sugg = "return the output of the future directly"; diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs deleted file mode 100644 index cf5004399..000000000 --- a/clippy_lints/src/manual_ok_or.rs +++ /dev/null @@ -1,95 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_lang_ctor, path_to_local_id}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::LangItem::{ResultErr, ResultOk}; -use rustc_hir::{Closure, Expr, ExprKind, PatKind}; -use rustc_lint::LintContext; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// - /// Finds patterns that reimplement `Option::ok_or`. - /// - /// ### Why is this bad? - /// - /// Concise code helps focusing on behavior instead of boilerplate. - /// - /// ### Examples - /// ```rust - /// let foo: Option = None; - /// foo.map_or(Err("error"), |v| Ok(v)); - /// ``` - /// - /// Use instead: - /// ```rust - /// let foo: Option = None; - /// foo.ok_or("error"); - /// ``` - #[clippy::version = "1.49.0"] - pub MANUAL_OK_OR, - pedantic, - "finds patterns that can be encoded more concisely with `Option::ok_or`" -} - -declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]); - -impl<'tcx> LateLintPass<'tcx> for ManualOkOr { - fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) { - if in_external_macro(cx.sess(), scrutinee.span) { - return; - } - - if_chain! { - if let ExprKind::MethodCall(method_segment, [receiver, or_expr, map_expr], _) = scrutinee.kind; - if method_segment.ident.name == sym!(map_or); - let ty = cx.typeck_results().expr_ty(receiver); - if is_type_diagnostic_item(cx, ty, sym::Option); - if is_ok_wrapping(cx, map_expr); - if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; - if is_lang_ctor(cx, err_path, ResultErr); - if let Some(method_receiver_snippet) = snippet_opt(cx, receiver.span); - if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); - if let Some(indent) = indent_of(cx, scrutinee.span); - then { - let reindented_err_arg_snippet = - reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); - span_lint_and_sugg( - cx, - MANUAL_OK_OR, - scrutinee.span, - "this pattern reimplements `Option::ok_or`", - "replace with", - format!( - "{}.ok_or({})", - method_receiver_snippet, - reindented_err_arg_snippet - ), - Applicability::MachineApplicable, - ); - } - } - } -} - -fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { - if let ExprKind::Path(ref qpath) = map_expr.kind { - if is_lang_ctor(cx, qpath, ResultOk) { - return true; - } - } - if_chain! { - if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind; - let body = cx.tcx.hir().body(body); - if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; - if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; - if is_lang_ctor(cx, ok_path, ResultOk); - then { path_to_local_id(ok_arg, param_id) } else { false } - } -} diff --git a/clippy_lints/src/manual_string_new.rs b/clippy_lints/src/manual_string_new.rs new file mode 100644 index 000000000..a90eaa8fd --- /dev/null +++ b/clippy_lints/src/manual_string_new.rs @@ -0,0 +1,140 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::LitKind; +use rustc_errors::Applicability::MachineApplicable; +use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, symbol, Span}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`, + /// `String::from("")` and others. + /// + /// ### Why is this bad? + /// + /// Different ways of creating an empty string makes your code less standardized, which can + /// be confusing. + /// + /// ### Example + /// ```rust + /// let a = "".to_string(); + /// let b: String = "".into(); + /// ``` + /// Use instead: + /// ```rust + /// let a = String::new(); + /// let b = String::new(); + /// ``` + #[clippy::version = "1.65.0"] + pub MANUAL_STRING_NEW, + pedantic, + "empty String is being created manually" +} +declare_lint_pass!(ManualStringNew => [MANUAL_STRING_NEW]); + +impl LateLintPass<'_> for ManualStringNew { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + match ty.kind() { + ty::Adt(adt_def, _) if adt_def.is_struct() => { + if !cx.tcx.is_diagnostic_item(sym::String, adt_def.did()) { + return; + } + }, + _ => return, + } + + match expr.kind { + ExprKind::Call(func, args) => { + parse_call(cx, expr.span, func, args); + }, + ExprKind::MethodCall(path_segment, args, _) => { + parse_method_call(cx, expr.span, path_segment, args); + }, + _ => (), + } + } +} + +/// Checks if an expression's kind corresponds to an empty &str. +fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool { + if let ExprKind::Lit(lit) = expr_kind && + let LitKind::Str(value, _) = lit.node && + value == symbol::kw::Empty + { + return true; + } + + false +} + +fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { + span_lint_and_sugg( + cx, + MANUAL_STRING_NEW, + span, + "empty String is being created manually", + "consider using", + "String::new()".into(), + MachineApplicable, + ); +} + +/// Tries to parse an expression as a method call, emitting the warning if necessary. +fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, args: &[Expr<'_>]) { + if args.is_empty() { + // When parsing TryFrom::try_from(...).expect(...), we will have more than 1 arg. + return; + } + + let ident = path_segment.ident.as_str(); + let method_arg_kind = &args[0].kind; + if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { + warn_then_suggest(cx, span); + } else if let ExprKind::Call(func, args) = method_arg_kind { + // If our first argument is a function call itself, it could be an `unwrap`-like function. + // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc. + parse_call(cx, span, func, args); + } +} + +/// Tries to parse an expression as a function call, emitting the warning if necessary. +fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) { + if args.len() != 1 { + return; + } + + let arg_kind = &args[0].kind; + if let ExprKind::Path(qpath) = &func.kind { + if let QPath::TypeRelative(_, _) = qpath { + // String::from(...) or String::try_from(...) + if let QPath::TypeRelative(ty, path_seg) = qpath && + [sym::from, sym::try_from].contains(&path_seg.ident.name) && + let TyKind::Path(qpath) = &ty.kind && + let QPath::Resolved(_, path) = qpath && + let [path_seg] = path.segments && + path_seg.ident.name == sym::String && + is_expr_kind_empty_str(arg_kind) + { + warn_then_suggest(cx, span); + } + } else if let QPath::Resolved(_, path) = qpath { + // From::from(...) or TryFrom::try_from(...) + if let [path_seg1, path_seg2] = path.segments && + is_expr_kind_empty_str(arg_kind) && ( + (path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) || + (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from) + ) + { + warn_then_suggest(cx, span); + } + } + } +} diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs deleted file mode 100644 index 95c312f1f..000000000 --- a/clippy_lints/src/map_clone.rs +++ /dev/null @@ -1,167 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; -use clippy_utils::{is_trait_method, meets_msrv, msrvs, peel_blocks}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::mir::Mutability; -use rustc_middle::ty; -use rustc_middle::ty::adjustment::Adjust; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::Ident; -use rustc_span::{sym, Span}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `map(|x| x.clone())` or - /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, - /// and suggests `cloned()` or `copied()` instead - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely - /// - /// ### Example - /// ```rust - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.map(|i| *i); - /// ``` - /// - /// The correct use would be: - /// - /// ```rust - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.cloned(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MAP_CLONE, - style, - "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" -} - -pub struct MapClone { - msrv: Option, -} - -impl_lint_pass!(MapClone => [MAP_CLONE]); - -impl MapClone { - pub fn new(msrv: Option) -> Self { - Self { msrv } - } -} - -impl<'tcx> LateLintPass<'tcx> for MapClone { - fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if_chain! { - if let hir::ExprKind::MethodCall(method, args, _) = e.kind; - if args.len() == 2; - if method.ident.name == sym::map; - let ty = cx.typeck_results().expr_ty(&args[0]); - if is_type_diagnostic_item(cx, ty, sym::Option) || is_trait_method(cx, e, sym::Iterator); - if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = args[1].kind; - then { - let closure_body = cx.tcx.hir().body(body); - let closure_expr = peel_blocks(&closure_body.value); - match closure_body.params[0].pat.kind { - hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding( - hir::BindingAnnotation::Unannotated, .., name, None - ) = inner.kind { - if ident_eq(name, closure_expr) { - self.lint_explicit_closure(cx, e.span, args[0].span, true); - } - }, - hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { - match closure_expr.kind { - hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { - if ident_eq(name, inner) { - if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { - self.lint_explicit_closure(cx, e.span, args[0].span, true); - } - } - }, - hir::ExprKind::MethodCall(method, [obj], _) => if_chain! { - if ident_eq(name, obj) && method.ident.name == sym::clone; - if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); - if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); - if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); - // no autoderefs - if !cx.typeck_results().expr_adjustments(obj).iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); - then { - let obj_ty = cx.typeck_results().expr_ty(obj); - if let ty::Ref(_, ty, mutability) = obj_ty.kind() { - if matches!(mutability, Mutability::Not) { - let copy = is_copy(cx, *ty); - self.lint_explicit_closure(cx, e.span, args[0].span, copy); - } - } else { - lint_needless_cloning(cx, e.span, args[0].span); - } - } - }, - _ => {}, - } - }, - _ => {}, - } - } - } - } - - extract_msrv_attr!(LateContext); -} - -fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { - if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind { - path.segments.len() == 1 && path.segments[0].ident == name - } else { - false - } -} - -fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { - span_lint_and_sugg( - cx, - MAP_CLONE, - root.trim_start(receiver).unwrap(), - "you are needlessly cloning iterator elements", - "remove the `map` call", - String::new(), - Applicability::MachineApplicable, - ); -} - -impl MapClone { - fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) { - let mut applicability = Applicability::MachineApplicable; - - let (message, sugg_method) = if is_copy && meets_msrv(self.msrv, msrvs::ITERATOR_COPIED) { - ("you are using an explicit closure for copying elements", "copied") - } else { - ("you are using an explicit closure for cloning elements", "cloned") - }; - - span_lint_and_sugg( - cx, - MAP_CLONE, - replace, - message, - &format!("consider calling the dedicated `{}` method", sugg_method), - format!( - "{}.{}()", - snippet_with_applicability(cx, root, "..", &mut applicability), - sugg_method, - ), - applicability, - ); - } -} diff --git a/clippy_lints/src/map_err_ignore.rs b/clippy_lints/src/map_err_ignore.rs deleted file mode 100644 index 1e542447c..000000000 --- a/clippy_lints/src/map_err_ignore.rs +++ /dev/null @@ -1,154 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map_err(|_| Some::Enum)` - /// - /// ### Why is this bad? - /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error - /// - /// ### Example - /// Before: - /// ```rust - /// use std::fmt; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible, - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error {} - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(|_| Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - /// - /// After: - /// ```rust - /// use std::{fmt, num::ParseIntError}; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible(ParseIntError), - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible(_) => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error { - /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - /// match self { - /// Error::Indivisible(source) => Some(source), - /// _ => None, - /// } - /// } - /// } - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub MAP_ERR_IGNORE, - restriction, - "`map_err` should not ignore the original error" -} - -declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]); - -impl<'tcx> LateLintPass<'tcx> for MapErrIgnore { - // do not try to lint if this is from a macro or desugaring - fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { - if e.span.from_expansion() { - return; - } - - // check if this is a method call (e.g. x.foo()) - if let ExprKind::MethodCall(method, [_, arg], _) = e.kind { - // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1] - // Enum::Variant[2])) - if method.ident.name == sym!(map_err) { - // make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span - // fields - if let ExprKind::Closure(&Closure { - capture_clause, - body, - fn_decl_span, - .. - }) = arg.kind - { - // check if this is by Reference (meaning there's no move statement) - if capture_clause == CaptureBy::Ref { - // Get the closure body to check the parameters and values - let closure_body = cx.tcx.hir().body(body); - // make sure there's only one parameter (`|_|`) - if closure_body.params.len() == 1 { - // make sure that parameter is the wild token (`_`) - if let PatKind::Wild = closure_body.params[0].pat.kind { - // span the area of the closure capture and warn that the - // original error will be thrown away - span_lint_and_help( - cx, - MAP_ERR_IGNORE, - fn_decl_span, - "`map_err(|_|...` wildcard pattern discards the original error", - None, - "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", - ); - } - } - } - } - } - } - } -} diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 0da4833f1..34cc08268 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_wild; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::span_contains_comment; use rustc_ast::{Attribute, LitKind}; use rustc_errors::Applicability; use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat}; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; use rustc_span::source_map::Spanned; @@ -76,6 +77,7 @@ where >, { if_chain! { + if !span_contains_comment(cx.sess().source_map(), expr.span); if iter.len() >= 2; if cx.typeck_results().expr_ty(expr).is_bool(); if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index fa19cddd3..6f037339e 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -8,7 +8,7 @@ use clippy_utils::{ }; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath}; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::sym; use rustc_typeck::hir_ty_to_ty; @@ -65,8 +65,26 @@ pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool { for arm in arms { let arm_expr = peel_blocks_with_stmt(arm.body); + + if let Some(guard_expr) = &arm.guard { + match guard_expr { + // gives up if `pat if expr` can have side effects + Guard::If(if_cond) => { + if if_cond.can_have_side_effects() { + return false; + } + }, + // gives up `pat if let ...` arm + Guard::IfLet(_) => { + return false; + }, + }; + } + if let PatKind::Wild = arm.pat.kind { - return eq_expr_value(cx, match_expr, strip_return(arm_expr)); + if !eq_expr_value(cx, match_expr, strip_return(arm_expr)) { + return false; + } } else if !pat_same_as_expr(arm.pat, arm_expr) { return false; } diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints/src/methods/bytecount.rs new file mode 100644 index 000000000..6a7c63d76 --- /dev/null +++ b/clippy_lints/src/methods/bytecount.rs @@ -0,0 +1,70 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::match_type; +use clippy_utils::visitors::is_local_used; +use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, UintTy}; +use rustc_span::sym; + +use super::NAIVE_BYTECOUNT; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + filter_recv: &'tcx Expr<'_>, + filter_arg: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind; + let body = cx.tcx.hir().body(body); + if let [param] = body.params; + if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; + if let ExprKind::Binary(ref op, l, r) = body.value.kind; + if op.node == BinOpKind::Eq; + if match_type(cx, + cx.typeck_results().expr_ty(filter_recv).peel_refs(), + &paths::SLICE_ITER); + let operand_is_arg = |expr| { + let expr = peel_ref_operators(cx, peel_blocks(expr)); + path_to_local_id(expr, arg_id) + }; + let needle = if operand_is_arg(l) { + r + } else if operand_is_arg(r) { + l + } else { + return; + }; + if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); + if !is_local_used(cx, needle, arg_id); + then { + let haystack = if let ExprKind::MethodCall(path, args, _) = + filter_recv.kind { + let p = path.ident.name; + if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { + &args[0] + } else { + filter_recv + } + } else { + filter_recv + }; + let mut applicability = Applicability::MaybeIncorrect; + span_lint_and_sugg( + cx, + NAIVE_BYTECOUNT, + expr.span, + "you appear to be counting bytes the naive way", + "consider using the bytecount crate", + format!("bytecount::count({}, {})", + snippet_with_applicability(cx, haystack.span, "..", &mut applicability), + snippet_with_applicability(cx, needle.span, "..", &mut applicability)), + applicability, + ); + } + }; +} diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs new file mode 100644 index 000000000..fcfc25b52 --- /dev/null +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::BYTES_COUNT_TO_LEN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + count_recv: &'tcx hir::Expr<'_>, + bytes_recv: &'tcx hir::Expr<'_>, +) { + if_chain! { + if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(bytes_id); + if cx.tcx.type_of(impl_id).is_str(); + let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs(); + if ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BYTES_COUNT_TO_LEN, + expr.span, + "using long and hard to read `.bytes().count()`", + "consider calling `.len()` instead", + format!("{}.len()", snippet_with_applicability(cx, bytes_recv.span, "..", &mut applicability)), + applicability + ); + } + }; +} diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs new file mode 100644 index 000000000..b3c2c7c9a --- /dev/null +++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -0,0 +1,41 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::{source_map::Spanned, symbol::sym, Span}; + +use super::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + call_span: Span, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_str(); + if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = arg.kind; + if (2..=6).contains(&ext_literal.as_str().len()); + let ext_str = ext_literal.as_str(); + if ext_str.starts_with('.'); + if ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit()) + || ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()); + let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if recv_ty.is_str() || is_type_diagnostic_item(cx, recv_ty, sym::String); + then { + span_lint_and_help( + cx, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + call_span, + "case-sensitive file extension comparison", + None, + "consider using a case-insensitive comparison instead", + ); + } + } +} diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints/src/methods/collapsible_str_replace.rs new file mode 100644 index 000000000..561033be5 --- /dev/null +++ b/clippy_lints/src/methods/collapsible_str_replace.rs @@ -0,0 +1,96 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{eq_expr_value, get_parent_expr}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use std::collections::VecDeque; + +use super::method_call; +use super::COLLAPSIBLE_STR_REPLACE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + from: &'tcx hir::Expr<'tcx>, + to: &'tcx hir::Expr<'tcx>, +) { + let replace_methods = collect_replace_calls(cx, expr, to); + if replace_methods.methods.len() > 1 { + let from_kind = cx.typeck_results().expr_ty(from).peel_refs().kind(); + // If the parent node's `to` argument is the same as the `to` argument + // of the last replace call in the current chain, don't lint as it was already linted + if let Some(parent) = get_parent_expr(cx, expr) + && let Some(("replace", [_, current_from, current_to], _)) = method_call(parent) + && eq_expr_value(cx, to, current_to) + && from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind() + { + return; + } + + check_consecutive_replace_calls(cx, expr, &replace_methods, to); + } +} + +struct ReplaceMethods<'tcx> { + methods: VecDeque<&'tcx hir::Expr<'tcx>>, + from_args: VecDeque<&'tcx hir::Expr<'tcx>>, +} + +fn collect_replace_calls<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + to_arg: &'tcx hir::Expr<'tcx>, +) -> ReplaceMethods<'tcx> { + let mut methods = VecDeque::new(); + let mut from_args = VecDeque::new(); + + let _: Option<()> = for_each_expr(expr, |e| { + if let Some(("replace", [_, from, to], _)) = method_call(e) { + if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() { + methods.push_front(e); + from_args.push_front(from); + ControlFlow::Continue(()) + } else { + ControlFlow::BREAK + } + } else { + ControlFlow::Continue(()) + } + }); + + ReplaceMethods { methods, from_args } +} + +/// Check a chain of `str::replace` calls for `collapsible_str_replace` lint. +fn check_consecutive_replace_calls<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + replace_methods: &ReplaceMethods<'tcx>, + to_arg: &'tcx hir::Expr<'tcx>, +) { + let from_args = &replace_methods.from_args; + let from_arg_reprs: Vec = from_args + .iter() + .map(|from_arg| snippet(cx, from_arg.span, "..").to_string()) + .collect(); + let app = Applicability::MachineApplicable; + let earliest_replace_call = replace_methods.methods.front().unwrap(); + if let Some((_, [..], span_lo)) = method_call(earliest_replace_call) { + span_lint_and_sugg( + cx, + COLLAPSIBLE_STR_REPLACE, + expr.span.with_lo(span_lo.lo()), + "used consecutive `str::replace` call", + "replace with", + format!( + "replace([{}], {})", + from_arg_reprs.join(", "), + snippet(cx, to_arg.span, ".."), + ), + app, + ); + } +} diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs index 5ef08ca62..d59fefa1d 100644 --- a/clippy_lints/src/methods/expect_used.rs +++ b/clippy_lints/src/methods/expect_used.rs @@ -7,18 +7,26 @@ use rustc_span::sym; use super::EXPECT_USED; -/// lint use of `expect()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_expect_in_tests: bool) { +/// lint use of `expect()` or `expect_err` for `Result` and `expect()` for `Option`. +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + is_err: bool, + allow_expect_in_tests: bool, +) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { Some((EXPECT_USED, "an Option", "None", "")) } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((EXPECT_USED, "a Result", "Err", "an ")) + Some((EXPECT_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an ")) } else { None }; + let method = if is_err { "expect_err" } else { "expect" }; + if allow_expect_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { return; } @@ -28,7 +36,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr cx, lint, expr.span, - &format!("used `expect()` on `{kind}` value"), + &format!("used `{method}()` on `{kind}` value"), None, &format!("if this value is {none_prefix}`{none_value}`, it will panic"), ); diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints/src/methods/get_first.rs new file mode 100644 index 000000000..4de77de74 --- /dev/null +++ b/clippy_lints/src/methods/get_first.rs @@ -0,0 +1,39 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_slice_of_primitives; +use clippy_utils::source::snippet_with_applicability; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::GET_FIRST; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_slice(); + if let Some(_) = is_slice_of_primitives(cx, recv); + if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind; + then { + let mut app = Applicability::MachineApplicable; + let slice_name = snippet_with_applicability(cx, recv.span, "..", &mut app); + span_lint_and_sugg( + cx, + GET_FIRST, + expr.span, + &format!("accessing first element with `{0}.get(0)`", slice_name), + "try", + format!("{}.first()", slice_name), + app, + ); + } + } +} diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs new file mode 100644 index 000000000..cea7b0d82 --- /dev/null +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -0,0 +1,107 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::{get_expr_use_or_unification_node, is_lang_ctor, is_no_std_crate}; + +use rustc_errors::Applicability; +use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; + +use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; + +enum IterType { + Iter, + IterMut, + IntoIter, +} + +impl IterType { + fn ref_prefix(&self) -> &'static str { + match self { + Self::Iter => "&", + Self::IterMut => "&mut ", + Self::IntoIter => "", + } + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) { + let item = match &recv.kind { + ExprKind::Array(v) if v.len() <= 1 => v.first(), + ExprKind::Path(p) => { + if is_lang_ctor(cx, p, OptionNone) { + None + } else { + return; + } + }, + ExprKind::Call(f, some_args) if some_args.len() == 1 => { + if let ExprKind::Path(p) = &f.kind { + if is_lang_ctor(cx, p, OptionSome) { + Some(&some_args[0]) + } else { + return; + } + } else { + return; + } + }, + _ => return, + }; + let iter_type = match method_name { + "iter" => IterType::Iter, + "iter_mut" => IterType::IterMut, + "into_iter" => IterType::IntoIter, + _ => return, + }; + + let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { + Some((Node::Expr(parent), child_id)) => match parent.kind { + ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, + ExprKind::If(_, _, _) + | ExprKind::Match(_, _, _) + | ExprKind::Closure(_) + | ExprKind::Ret(_) + | ExprKind::Break(_, _) => true, + _ => false, + }, + Some((Node::Stmt(_) | Node::Local(_), _)) => false, + _ => true, + }; + + if is_unified { + return; + } + + if let Some(i) = item { + let sugg = format!( + "{}::iter::once({}{})", + if is_no_std_crate(cx) { "core" } else { "std" }, + iter_type.ref_prefix(), + snippet(cx, i.span, "...") + ); + span_lint_and_sugg( + cx, + ITER_ON_SINGLE_ITEMS, + expr.span, + &format!("`{method_name}` call on a collection with only one item"), + "try", + sugg, + Applicability::MaybeIncorrect, + ); + } else { + span_lint_and_sugg( + cx, + ITER_ON_EMPTY_COLLECTIONS, + expr.span, + &format!("`{method_name}` call on an empty collection"), + "try", + if is_no_std_crate(cx) { + "core::iter::empty()".to_string() + } else { + "std::iter::empty()".to_string() + }, + Applicability::MaybeIncorrect, + ); + } +} diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs new file mode 100644 index 000000000..ffd2f4a38 --- /dev/null +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -0,0 +1,64 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_lang_ctor, path_to_local_id}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{ResultErr, ResultOk}; +use rustc_hir::{Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::MANUAL_OK_OR; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'_>, + or_expr: &'tcx Expr<'_>, + map_expr: &'tcx Expr<'_>, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Option); + if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, [err_arg]) = or_expr.kind; + if is_lang_ctor(cx, err_path, ResultErr); + if is_ok_wrapping(cx, map_expr); + if let Some(recv_snippet) = snippet_opt(cx, recv.span); + if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); + if let Some(indent) = indent_of(cx, expr.span); + then { + let reindented_err_arg_snippet = reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4)); + span_lint_and_sugg( + cx, + MANUAL_OK_OR, + expr.span, + "this pattern reimplements `Option::ok_or`", + "replace with", + format!( + "{}.ok_or({})", + recv_snippet, + reindented_err_arg_snippet + ), + Applicability::MachineApplicable, + ); + } + } +} + +fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { + if let ExprKind::Path(ref qpath) = map_expr.kind { + if is_lang_ctor(cx, qpath, ResultOk) { + return true; + } + } + if_chain! { + if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind; + let body = cx.tcx.hir().body(body); + if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; + if is_lang_ctor(cx, ok_path, ResultOk); + then { path_to_local_id(ok_arg, param_id) } else { false } + } +} diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs new file mode 100644 index 000000000..ffedda95f --- /dev/null +++ b/clippy_lints/src/methods/map_clone.rs @@ -0,0 +1,122 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs, peel_blocks}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::mir::Mutability; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_semver::RustcVersion; +use rustc_span::symbol::Ident; +use rustc_span::{sym, Span}; + +use super::MAP_CLONE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'_>, + e: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + msrv: Option, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id); + if cx.tcx.impl_of_method(method_id) + .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id), sym::Option)) + || is_diag_trait_item(cx, method_id, sym::Iterator); + if let hir::ExprKind::Closure(&hir::Closure{ body, .. }) = arg.kind; + then { + let closure_body = cx.tcx.hir().body(body); + let closure_expr = peel_blocks(&closure_body.value); + match closure_body.params[0].pat.kind { + hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, .., name, None + ) = inner.kind { + if ident_eq(name, closure_expr) { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); + } + }, + hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { + match closure_expr.kind { + hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { + if ident_eq(name, inner) { + if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); + } + } + }, + hir::ExprKind::MethodCall(method, [obj], _) => if_chain! { + if ident_eq(name, obj) && method.ident.name == sym::clone; + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); + if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); + if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); + // no autoderefs + if !cx.typeck_results().expr_adjustments(obj).iter() + .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); + then { + let obj_ty = cx.typeck_results().expr_ty(obj); + if let ty::Ref(_, ty, mutability) = obj_ty.kind() { + if matches!(mutability, Mutability::Not) { + let copy = is_copy(cx, *ty); + lint_explicit_closure(cx, e.span, recv.span, copy, msrv); + } + } else { + lint_needless_cloning(cx, e.span, recv.span); + } + } + }, + _ => {}, + } + }, + _ => {}, + } + } + } +} + +fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind { + path.segments.len() == 1 && path.segments[0].ident == name + } else { + false + } +} + +fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { + span_lint_and_sugg( + cx, + MAP_CLONE, + root.trim_start(receiver).unwrap(), + "you are needlessly cloning iterator elements", + "remove the `map` call", + String::new(), + Applicability::MachineApplicable, + ); +} + +fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: Option) { + let mut applicability = Applicability::MachineApplicable; + + let (message, sugg_method) = if is_copy && meets_msrv(msrv, msrvs::ITERATOR_COPIED) { + ("you are using an explicit closure for copying elements", "copied") + } else { + ("you are using an explicit closure for cloning elements", "cloned") + }; + + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + message, + &format!("consider calling the dedicated `{}` method", sugg_method), + format!( + "{}.{}()", + snippet_with_applicability(cx, root, "..", &mut applicability), + sugg_method, + ), + applicability, + ); +} diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs new file mode 100644 index 000000000..1fb661714 --- /dev/null +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::MAP_ERR_IGNORE; + +pub(super) fn check<'tcx>(cx: &LateContext<'_>, e: &Expr<'_>, arg: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Result) + && let ExprKind::Closure(&Closure { + capture_clause: CaptureBy::Ref, + body, + fn_decl_span, + .. + }) = arg.kind + && let closure_body = cx.tcx.hir().body(body) + && let [param] = closure_body.params + && let PatKind::Wild = param.pat.kind + { + // span the area of the closure capture and warn that the + // original error will be thrown away + span_lint_and_help( + cx, + MAP_ERR_IGNORE, + fn_decl_span, + "`map_err(|_|...` wildcard pattern discards the original error", + None, + "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5ac6b09f0..a0d190a58 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1,5 +1,8 @@ mod bind_instead_of_map; +mod bytecount; +mod bytes_count_to_len; mod bytes_nth; +mod case_sensitive_file_extension_comparisons; mod chars_cmp; mod chars_cmp_with_unwrap; mod chars_last_cmp; @@ -9,6 +12,7 @@ mod chars_next_cmp_with_unwrap; mod clone_on_copy; mod clone_on_ref_ptr; mod cloned_instead_of_copied; +mod collapsible_str_replace; mod err_expect; mod expect_fun_call; mod expect_used; @@ -21,6 +25,7 @@ mod filter_next; mod flat_map_identity; mod flat_map_option; mod from_iter_instead_of_collect; +mod get_first; mod get_last_with_len; mod get_unwrap; mod implicit_clone; @@ -33,55 +38,72 @@ mod iter_count; mod iter_next_slice; mod iter_nth; mod iter_nth_zero; +mod iter_on_single_or_empty_collections; mod iter_overeager_cloned; mod iter_skip_next; mod iter_with_drain; mod iterator_step_by_zero; +mod manual_ok_or; mod manual_saturating_arithmetic; mod manual_str_repeat; +mod map_clone; mod map_collect_result_unit; +mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod mut_mutex_lock; mod needless_option_as_deref; mod needless_option_take; mod no_effect_replace; mod obfuscated_if_else; mod ok_expect; +mod open_options; mod option_as_ref_deref; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; mod or_then_unwrap; +mod path_buf_push_overwrite; +mod range_zip_with_len; +mod repeat_once; mod search_is_some; mod single_char_add_str; mod single_char_insert_string; mod single_char_pattern; mod single_char_push_string; mod skip_while_next; +mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; mod suspicious_map; mod suspicious_splitn; +mod suspicious_to_owned; mod uninit_assumed_init; +mod unit_hash; mod unnecessary_filter_map; mod unnecessary_fold; mod unnecessary_iter_cloned; mod unnecessary_join; mod unnecessary_lazy_eval; +mod unnecessary_sort_by; mod unnecessary_to_owned; mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; mod utils; +mod vec_resize_to_zero; +mod verbose_file_reads; mod wrong_self_convention; mod zst_offset; use bind_instead_of_map::BindInsteadOfMap; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty}; +use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::{ + contains_return, get_trait_def_id, is_trait_method, iter_input_pats, meets_msrv, msrvs, paths, return_ty, +}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::def::Res; @@ -117,6 +139,32 @@ declare_clippy_lint! { "used `cloned` where `copied` could be used instead" } +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive calls to `str::replace` (2 or more) + /// that can be collapsed into a single call. + /// + /// ### Why is this bad? + /// Consecutive `str::replace` calls scan the string multiple times + /// with repetitive code. + /// + /// ### Example + /// ```rust + /// let hello = "hesuo worpd" + /// .replace('s', "l") + /// .replace("u", "l") + /// .replace('p', "l"); + /// ``` + /// Use instead: + /// ```rust + /// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l"); + /// ``` + #[clippy::version = "1.64.0"] + pub COLLAPSIBLE_STR_REPLACE, + perf, + "collapse consecutive calls to str::replace (2 or more) into a single call" +} + declare_clippy_lint! { /// ### What it does /// Checks for usage of `_.cloned().()` where call to `.cloned()` can be postponed. @@ -173,7 +221,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `.unwrap()` calls on `Option`s and on `Result`s. + /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. /// /// ### Why is this bad? /// It is better to handle the `None` or `Err` case, @@ -223,7 +271,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `.expect()` calls on `Option`s and `Result`s. + /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. /// /// ### Why is this bad? /// Usually it is better to handle the `None` or `Err` case. @@ -2006,6 +2054,55 @@ declare_clippy_lint! { "replace `.iter().count()` with `.len()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. + /// + /// ### Why is this bad? + /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` + /// itself, without taking ownership of the `Cow` contents (i.e. + /// it's equivalent to calling `Cow::clone`). + /// The similarly named `into_owned` method, on the other hand, + /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` + /// into a `Cow::Owned`. + /// + /// Given the potential ambiguity, consider replacing `to_owned` + /// with `clone` for better readability or, if getting a `Cow::Owned` + /// was the original intent, using `into_owned` instead. + /// + /// ### Example + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.to_owned(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// Use instead: + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.clone(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// or + /// ```rust + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.into_owned(); + /// assert!(matches!(data, String)) + /// ``` + #[clippy::version = "1.65.0"] + pub SUSPICIOUS_TO_OWNED, + suspicious, + "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to [`splitn`] @@ -2269,7 +2366,7 @@ declare_clippy_lint! { /// "1234".replace("12", "12"); /// "1234".replacen("12", "12", 1); /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub NO_EFFECT_REPLACE, suspicious, "replace with no effect" @@ -2304,6 +2401,640 @@ declare_clippy_lint! { more clearly with `if .. else ..`" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item + /// + /// ### Why is this bad? + /// + /// It is simpler to use the once function from the standard library: + /// + /// ### Example + /// + /// ```rust + /// let a = [123].iter(); + /// let b = Some(123).into_iter(); + /// ``` + /// Use instead: + /// ```rust + /// use std::iter; + /// let a = iter::once(&123); + /// let b = iter::once(123); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.64.0"] + pub ITER_ON_SINGLE_ITEMS, + nursery, + "Iterator for array of length 1" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections + /// + /// ### Why is this bad? + /// + /// It is simpler to use the empty function from the standard library: + /// + /// ### Example + /// + /// ```rust + /// use std::{slice, option}; + /// let a: slice::Iter = [].iter(); + /// let f: option::IntoIter = None.into_iter(); + /// ``` + /// Use instead: + /// ```rust + /// use std::iter; + /// let a: iter::Empty = iter::empty(); + /// let b: iter::Empty = iter::empty(); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.64.0"] + pub ITER_ON_EMPTY_COLLECTIONS, + nursery, + "Iterator for empty array" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for naive byte counts + /// + /// ### Why is this bad? + /// The [`bytecount`](https://crates.io/crates/bytecount) + /// crate has methods to count your bytes faster, especially for large slices. + /// + /// ### Known problems + /// If you have predominantly small slices, the + /// `bytecount::count(..)` method may actually be slower. However, if you can + /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be + /// faster in those cases. + /// + /// ### Example + /// ```rust + /// # let vec = vec![1_u8]; + /// let count = vec.iter().filter(|x| **x == 0u8).count(); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let vec = vec![1_u8]; + /// let count = bytecount::count(&vec, 0u8); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NAIVE_BYTECOUNT, + pedantic, + "use of naive `.filter(|&x| x == y).count()` to count byte values" +} + +declare_clippy_lint! { + /// ### What it does + /// It checks for `str::bytes().count()` and suggests replacing it with + /// `str::len()`. + /// + /// ### Why is this bad? + /// `str::bytes().count()` is longer and may not be as performant as using + /// `str::len()`. + /// + /// ### Example + /// ```rust + /// "hello".bytes().count(); + /// String::from("hello").bytes().count(); + /// ``` + /// Use instead: + /// ```rust + /// "hello".len(); + /// String::from("hello").len(); + /// ``` + #[clippy::version = "1.62.0"] + pub BYTES_COUNT_TO_LEN, + complexity, + "Using `bytes().count()` when `len()` performs the same functionality" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// ### Why is this bad? + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// ### Example + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// let filename = std::path::Path::new(filename); + /// filename.extension() + /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for using `x.get(0)` instead of + /// `x.first()`. + /// + /// ### Why is this bad? + /// Using `x.first()` is easier to read and has the same + /// result. + /// + /// ### Example + /// ```rust + /// let x = vec![2, 3, 5]; + /// let first_element = x.get(0); + /// ``` + /// + /// Use instead: + /// ```rust + /// let x = vec![2, 3, 5]; + /// let first_element = x.first(); + /// ``` + #[clippy::version = "1.63.0"] + pub GET_FIRST, + style, + "Using `x.get(0)` when `x.first()` is simpler" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// ### Why is this bad? + /// + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// ### Examples + /// ```rust + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let foo: Option = None; + /// foo.ok_or("error"); + /// ``` + #[clippy::version = "1.49.0"] + pub MANUAL_OK_OR, + pedantic, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `map(|x| x.clone())` or + /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, + /// and suggests `cloned()` or `copied()` instead + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely + /// + /// ### Example + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.cloned(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MAP_CLONE, + style, + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map_err(|_| Some::Enum)` + /// + /// ### Why is this bad? + /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// + /// ### Example + /// Before: + /// ```rust + /// use std::fmt; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible, + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error {} + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(|_| Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + /// + /// After: + /// ```rust + /// use std::{fmt, num::ParseIntError}; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible(ParseIntError), + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible(_) => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// match self { + /// Error::Indivisible(source) => Some(source), + /// _ => None, + /// } + /// } + /// } + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub MAP_ERR_IGNORE, + restriction, + "`map_err` should not ignore the original error" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `&mut Mutex::lock` calls + /// + /// ### Why is this bad? + /// `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// ### Example + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let value = value_mutex.get_mut().unwrap(); + /// *value += 1; + /// ``` + #[clippy::version = "1.49.0"] + pub MUT_MUTEX_LOCK, + style, + "`&mut Mutex::lock` does unnecessary locking" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// ### Why is this bad? + /// In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// ### Example + /// ```rust + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().read(true).truncate(true); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NONSENSICAL_OPEN_OPTIONS, + correctness, + "nonsensical combination of options for opening a file" +} + +declare_clippy_lint! { + /// ### What it does + ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// ### Why is this bad? + /// Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// ### Example + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("bar"); + /// assert_eq!(x, PathBuf::from("/foo/bar")); + /// ``` + #[clippy::version = "1.36.0"] + pub PATH_BUF_PUSH_OVERWRITE, + nursery, + "calling `push` with file system root on `PathBuf` can overwrite it" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// ### Why is this bad? + /// The code is better expressed with `.enumerate()`. + /// + /// ### Example + /// ```rust + /// # let x = vec![1]; + /// let _ = x.iter().zip(0..x.len()); + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = vec![1]; + /// let _ = x.iter().enumerate(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.repeat(1)` and suggest the following method for each types. + /// - `.to_string()` for `str` + /// - `.clone()` for `String` + /// - `.to_vec()` for `slice` + /// + /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if + /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) + /// + /// ### Why is this bad? + /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning + /// the string is the intention behind this, `clone()` should be used. + /// + /// ### Example + /// ```rust + /// fn main() { + /// let x = String::from("hello world").repeat(1); + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn main() { + /// let x = String::from("hello world").clone(); + /// } + /// ``` + #[clippy::version = "1.47.0"] + pub REPEAT_ONCE, + complexity, + "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " +} + +declare_clippy_lint! { + /// ### What it does + /// When sorting primitive values (integers, bools, chars, as well + /// as arrays, slices, and tuples of such items), it is typically better to + /// use an unstable sort than a stable sort. + /// + /// ### Why is this bad? + /// Typically, using a stable sort consumes more memory and cpu cycles. + /// Because values which compare equal are identical, preserving their + /// relative order (the guarantee that a stable sort provides) means + /// nothing, while the extra costs still apply. + /// + /// ### Known problems + /// + /// As pointed out in + /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), + /// a stable sort can instead be significantly faster for certain scenarios + /// (eg. when a sorted vector is extended with new data and resorted). + /// + /// For more information and benchmarking results, please refer to the + /// issue linked above. + /// + /// ### Example + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort(); + /// ``` + /// Use instead: + /// ```rust + /// let mut vec = vec![2, 1, 3]; + /// vec.sort_unstable(); + /// ``` + #[clippy::version = "1.47.0"] + pub STABLE_SORT_PRIMITIVE, + pedantic, + "use of sort() when sort_unstable() is equivalent" +} + +declare_clippy_lint! { + /// ### What it does + /// Detects `().hash(_)`. + /// + /// ### Why is this bad? + /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. + /// + /// ### Example + /// ```rust + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => ().hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + /// Use instead: + /// ```rust + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => 0_u8.hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + #[clippy::version = "1.58.0"] + pub UNIT_HASH, + correctness, + "hashing a unit value, which does nothing" +} + +declare_clippy_lint! { + /// ### What it does + /// Detects uses of `Vec::sort_by` passing in a closure + /// which compares the two arguments, either directly or indirectly. + /// + /// ### Why is this bad? + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and a more complicated + /// closure. + /// + /// ### Known problems + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already + /// imported by a use statement, then it will need to be added manually. + /// + /// ### Example + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); + /// ``` + /// Use instead: + /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by_key(|a| a.foo()); + /// ``` + #[clippy::version = "1.46.0"] + pub UNNECESSARY_SORT_BY, + complexity, + "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" +} + +declare_clippy_lint! { + /// ### What it does + /// Finds occurrences of `Vec::resize(0, an_int)` + /// + /// ### Why is this bad? + /// This is probably an argument inversion mistake. + /// + /// ### Example + /// ```rust + /// vec!(1, 2, 3, 4, 5).resize(0, 5) + /// ``` + /// + /// Use instead: + /// ```rust + /// vec!(1, 2, 3, 4, 5).clear() + /// ``` + #[clippy::version = "1.46.0"] + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for use of File::read_to_end and File::read_to_string. + /// + /// ### Why is this bad? + /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// + /// ### Example + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # use std::fs; + /// let mut bytes = fs::read("foo.txt").unwrap(); + /// ``` + #[clippy::version = "1.44.0"] + pub VERBOSE_FILE_READS, + restriction, + "use of `File::read_to_end` or `File::read_to_string`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, @@ -2347,6 +3078,7 @@ impl_lint_pass!(Methods => [ CLONE_ON_COPY, CLONE_ON_REF_PTR, CLONE_DOUBLE_REF, + COLLAPSIBLE_STR_REPLACE, ITER_OVEREAGER_CLONED, CLONED_INSTEAD_OF_COPIED, FLAT_MAP_OPTION, @@ -2393,6 +3125,7 @@ impl_lint_pass!(Methods => [ FROM_ITER_INSTEAD_OF_COLLECT, INSPECT_FOR_EACH, IMPLICIT_CLONE, + SUSPICIOUS_TO_OWNED, SUSPICIOUS_SPLITN, MANUAL_STR_REPEAT, EXTEND_WITH_DRAIN, @@ -2406,6 +3139,25 @@ impl_lint_pass!(Methods => [ NEEDLESS_OPTION_TAKE, NO_EFFECT_REPLACE, OBFUSCATED_IF_ELSE, + ITER_ON_SINGLE_ITEMS, + ITER_ON_EMPTY_COLLECTIONS, + NAIVE_BYTECOUNT, + BYTES_COUNT_TO_LEN, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + GET_FIRST, + MANUAL_OK_OR, + MAP_CLONE, + MAP_ERR_IGNORE, + MUT_MUTEX_LOCK, + NONSENSICAL_OPEN_OPTIONS, + PATH_BUF_PUSH_OVERWRITE, + RANGE_ZIP_WITH_LEN, + REPEAT_ONCE, + STABLE_SORT_PRIMITIVE, + UNIT_HASH, + UNNECESSARY_SORT_BY, + VEC_RESIZE_TO_ZERO, + VERBOSE_FILE_READS, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2541,7 +3293,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if contains_adt_constructor(ret_ty, self_adt) { return; } - } else if contains_ty(ret_ty, self_ty) { + } else if ret_ty.contains(self_ty) { return; } @@ -2559,7 +3311,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if contains_adt_constructor(assoc_ty, self_adt) { return; } - } else if contains_ty(assoc_ty, self_ty) { + } else if assoc_ty.contains(self_ty) { return; } } @@ -2608,7 +3360,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if let TraitItemKind::Fn(_, _) = item.kind; let ret_ty = return_ty(cx, item.hir_id()); let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder(); - if !contains_ty(ret_ty, self_ty); + if !ret_ty.contains(self_ty); then { span_lint( @@ -2660,22 +3412,30 @@ impl Methods { }, _ => {}, }, - ("count", []) => match method_call(recv) { + ("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false), Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { iter_count::check(cx, expr, recv2, name2); }, Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), + Some(("filter", [recv2, arg], _)) => bytecount::check(cx, expr, recv2, arg), + Some(("bytes", [recv2], _)) => bytes_count_to_len::check(cx, expr, recv, recv2), _ => {}, }, ("drain", [arg]) => { iter_with_drain::check(cx, expr, recv, span, arg); }, + ("ends_with", [arg]) => { + if let ExprKind::MethodCall(_, _, span) = expr.kind { + case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg); + } + }, ("expect", [_]) => match method_call(recv) { Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv), Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span), - _ => expect_used::check(cx, expr, recv, self.allow_expect_in_tests), + _ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests), }, + ("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests), ("extend", [arg]) => { string_extend_chars::check(cx, expr, recv, arg); extend_with_drain::check(cx, expr, recv, arg); @@ -2702,12 +3462,21 @@ impl Methods { inspect_for_each::check(cx, expr, span2); } }, - ("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg), + ("get", [arg]) => { + get_first::check(cx, expr, recv, arg); + get_last_with_len::check(cx, expr, recv, arg); + }, ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("hash", [arg]) => { + unit_hash::check(cx, expr, recv, arg); + }, ("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv), ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("iter" | "iter_mut" | "into_iter", []) => { + iter_on_single_or_empty_collections::check(cx, expr, name, recv); + }, ("join", [join_arg]) => { if let Some(("collect", _, span)) = method_call(recv) { unnecessary_join::check(cx, expr, recv, join_arg, span); @@ -2720,7 +3489,15 @@ impl Methods { } } }, + ("lock", []) => { + mut_mutex_lock::check(cx, expr, recv, span); + }, (name @ ("map" | "map_err"), [m_arg]) => { + if name == "map" { + map_clone::check(cx, expr, recv, m_arg, self.msrv); + } else { + map_err_ignore::check(cx, expr, m_arg); + } if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) { match (name, args) { ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv), @@ -2736,7 +3513,10 @@ impl Methods { } map_identity::check(cx, expr, recv, m_arg, name, span); }, - ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), + ("map_or", [def, map]) => { + option_map_or_none::check(cx, expr, recv, def, map); + manual_ok_or::check(cx, expr, recv, def, map); + }, ("next", []) => { if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { match (name2, args2) { @@ -2758,11 +3538,46 @@ impl Methods { _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("open", [_]) => { + open_options::check(cx, expr, recv); + }, ("or_else", [arg]) => { if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) { unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); } }, + ("push", [arg]) => { + path_buf_push_overwrite::check(cx, expr, arg); + }, + ("read_to_end", [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG); + }, + ("read_to_string", [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG); + }, + ("repeat", [arg]) => { + repeat_once::check(cx, expr, recv, arg); + }, + (name @ ("replace" | "replacen"), [arg1, arg2] | [arg1, arg2, _]) => { + no_effect_replace::check(cx, expr, arg1, arg2); + + // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint + if name == "replace" && let Some(("replace", ..)) = method_call(recv) { + collapsible_str_replace::check(cx, expr, arg1, arg2); + } + }, + ("resize", [count_arg, default_arg]) => { + vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span); + }, + ("sort", []) => { + stable_sort_primitive::check(cx, expr, recv); + }, + ("sort_by", [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, false); + }, + ("sort_unstable_by", [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, true); + }, ("splitn" | "rsplitn", [count_arg, pat_arg]) => { if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { suspicious_splitn::check(cx, name, expr, recv, count); @@ -2789,7 +3604,12 @@ impl Methods { } unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); }, - ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { + ("to_owned", []) => { + if !suspicious_to_owned::check(cx, expr, recv) { + implicit_clone::check(cx, name, expr, recv); + } + }, + ("to_os_string" | "to_path_buf" | "to_vec", []) => { implicit_clone::check(cx, name, expr, recv); }, ("unwrap", []) => { @@ -2805,8 +3625,9 @@ impl Methods { }, _ => {}, } - unwrap_used::check(cx, expr, recv, self.allow_unwrap_in_tests); + unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests); }, + ("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests), ("unwrap_or", [u_arg]) => match method_call(recv) { Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => { manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]); @@ -2827,8 +3648,12 @@ impl Methods { unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); }, }, - ("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => { - no_effect_replace::check(cx, expr, arg1, arg2); + ("zip", [arg]) => { + if let ExprKind::MethodCall(name, [iter_recv], _) = recv.kind + && name.ident.name == sym::iter + { + range_zip_with_len::check(cx, expr, iter_recv, arg); + } }, _ => {}, } diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs new file mode 100644 index 000000000..bd8458a22 --- /dev/null +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -0,0 +1,30 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::{sym, Span}; + +use super::MUT_MUTEX_LOCK; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { + if_chain! { + if let ty::Ref(_, _, Mutability::Mut) = cx.typeck_results().expr_ty(recv).kind(); + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Mutex); + then { + span_lint_and_sugg( + cx, + MUT_MUTEX_LOCK, + name_span, + "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", + "change this to", + "get_mut".to_owned(), + Applicability::MaybeIncorrect, + ); + } + } +} diff --git a/clippy_lints/src/open_options.rs b/clippy_lints/src/methods/open_options.rs similarity index 80% rename from clippy_lints/src/open_options.rs rename to clippy_lints/src/methods/open_options.rs index 5a0b50420..c3112823e 100644 --- a/clippy_lints/src/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -3,43 +3,19 @@ use clippy_utils::paths; use clippy_utils::ty::match_type; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_lint::LateContext; use rustc_span::source_map::{Span, Spanned}; -declare_clippy_lint! { - /// ### What it does - /// Checks for duplicate open options as well as combinations - /// that make no sense. - /// - /// ### Why is this bad? - /// In the best case, the code will be harder to read than - /// necessary. I don't know the worst case. - /// - /// ### Example - /// ```rust - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().read(true).truncate(true); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NONSENSICAL_OPEN_OPTIONS, - correctness, - "nonsensical combination of options for opening a file" -} +use super::NONSENSICAL_OPEN_OPTIONS; -declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]); - -impl<'tcx> LateLintPass<'tcx> for OpenOptions { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::MethodCall(path, [self_arg, ..], _) = &e.kind { - let obj_ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) { - let mut options = Vec::new(); - get_open_options(cx, self_arg, &mut options); - check_open_options(cx, &options, e.span); - } - } +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && match_type(cx, cx.tcx.type_of(impl_id), &paths::OPEN_OPTIONS) + { + let mut options = Vec::new(); + get_open_options(cx, recv, &mut options); + check_open_options(cx, &options, e.span); } } diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 6c641af59..3c4002a3a 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -78,7 +78,7 @@ pub(super) fn check<'tcx>( map_span, String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }), ), - (expr.span.with_lo(unwrap_recv.span.hi()), String::from("")), + (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), ]; if !unwrap_snippet_none { diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs new file mode 100644 index 000000000..0cc28c0dc --- /dev/null +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -0,0 +1,37 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use std::path::{Component, Path}; + +use super::PATH_BUF_PUSH_OVERWRITE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::PathBuf); + if let ExprKind::Lit(ref lit) = arg.kind; + if let LitKind::Str(ref path_lit, _) = lit.node; + if let pushed_path = Path::new(path_lit.as_str()); + if let Some(pushed_path_lit) = pushed_path.to_str(); + if pushed_path.has_root(); + if let Some(root) = pushed_path.components().next(); + if root == Component::RootDir; + then { + span_lint_and_sugg( + cx, + PATH_BUF_PUSH_OVERWRITE, + lit.span, + "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", + "try", + format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints/src/methods/range_zip_with_len.rs new file mode 100644 index 000000000..00a2a0d14 --- /dev/null +++ b/clippy_lints/src/methods/range_zip_with_len.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::source::snippet; +use clippy_utils::{higher, SpanlessEq}; +use clippy_utils::{is_integer_const, is_trait_method}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::RANGE_ZIP_WITH_LEN; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { + if_chain! { + if is_trait_method(cx, expr, sym::Iterator); + // range expression in `.zip()` call: `0..x.len()` + if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg); + if is_integer_const(cx, start, 0); + // `.len()` call + if let ExprKind::MethodCall(len_path, [len_recv], _) = end.kind; + if len_path.ident.name == sym::len; + // `.iter()` and `.len()` called on same `Path` + if let ExprKind::Path(QPath::Resolved(_, iter_path)) = recv.kind; + if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind; + if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments); + then { + span_lint(cx, + RANGE_ZIP_WITH_LEN, + expr.span, + &format!("it is more idiomatic to use `{}.iter().enumerate()`", + snippet(cx, recv.span, "_")) + ); + } + } +} diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints/src/methods/repeat_once.rs new file mode 100644 index 000000000..0a14f9216 --- /dev/null +++ b/clippy_lints/src/methods/repeat_once.rs @@ -0,0 +1,52 @@ +use clippy_utils::consts::{constant_context, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::REPEAT_ONCE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + repeat_arg: &'tcx Expr<'_>, +) { + if constant_context(cx, cx.typeck_results()).expr(repeat_arg) == Some(Constant::Int(1)) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if ty.is_str() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on str", + "consider using `.to_string()` instead", + format!("{}.to_string()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if ty.builtin_index().is_some() { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on slice", + "consider using `.to_vec()` instead", + format!("{}.to_vec()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } else if is_type_diagnostic_item(cx, ty, sym::String) { + span_lint_and_sugg( + cx, + REPEAT_ONCE, + expr.span, + "calling `repeat(1)` on a string literal", + "consider using `.clone()` instead", + format!("{}.clone()", snippet(cx, recv.span, r#""...""#)), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints/src/methods/stable_sort_primitive.rs new file mode 100644 index 000000000..91951c65b --- /dev/null +++ b/clippy_lints/src/methods/stable_sort_primitive.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_slice_of_primitives; +use clippy_utils::source::snippet_with_context; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +use super::STABLE_SORT_PRIMITIVE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && cx.tcx.type_of(impl_id).is_slice() + && let Some(slice_type) = is_slice_of_primitives(cx, recv) + { + span_lint_and_then( + cx, + STABLE_SORT_PRIMITIVE, + e.span, + &format!("used `sort` on primitive type `{}`", slice_type), + |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snip = snippet_with_context(cx, recv.span, e.span.ctxt(), "..", &mut app).0; + diag.span_suggestion(e.span, "try", format!("{}.sort_unstable()", recv_snip), app); + diag.note( + "an unstable sort typically performs faster without any observable difference for this data type", + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints/src/methods/suspicious_to_owned.rs new file mode 100644 index 000000000..6b306fbf0 --- /dev/null +++ b/clippy_lints/src/methods/suspicious_to_owned.rs @@ -0,0 +1,36 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_diag_trait_item; +use clippy_utils::source::snippet_with_context; +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::sym; + +use super::SUSPICIOUS_TO_OWNED; + +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { + if_chain! { + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if is_diag_trait_item(cx, method_def_id, sym::ToOwned); + let input_type = cx.typeck_results().expr_ty(expr); + if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind(); + if cx.tcx.is_diagnostic_item(sym::Cow, adt.did()); + then { + let mut app = Applicability::MaybeIncorrect; + let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; + span_lint_and_sugg( + cx, + SUSPICIOUS_TO_OWNED, + expr.span, + &format!("this `to_owned` call clones the {0} itself and does not cause the {0} contents to become owned", input_type), + "consider using, depending on intent", + format!("{0}.clone()` or `{0}.into_owned()", recv_snip), + app, + ); + return true; + } + } + false +} diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index 77d21f1d3..a1c629473 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty}; +use clippy_utils::{is_path_diagnostic_item, ty::is_uninit_value_valid_for_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_lint::LateContext; @@ -12,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if_chain! { if let hir::ExprKind::Call(callee, args) = recv.kind; if args.is_empty(); - if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); + if is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)); then { span_lint( diff --git a/clippy_lints/src/methods/unit_hash.rs b/clippy_lints/src/methods/unit_hash.rs new file mode 100644 index 000000000..3c7955bc4 --- /dev/null +++ b/clippy_lints/src/methods/unit_hash.rs @@ -0,0 +1,29 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_trait_method; +use clippy_utils::source::snippet; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::UNIT_HASH; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { + if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { + span_lint_and_then( + cx, + UNIT_HASH, + expr.span, + "this call to `hash` on the unit type will do nothing", + |diag| { + diag.span_suggestion( + expr.span, + "remove the call to `hash` or consider using", + format!("0_u8.hash({})", snippet(cx, arg.span, ".."),), + Applicability::MaybeIncorrect, + ); + diag.note("the implementation of `Hash` for `()` is a no-op"); + }, + ); + } +} diff --git a/clippy_lints/src/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs similarity index 63% rename from clippy_lints/src/unnecessary_sort_by.rs rename to clippy_lints/src/methods/unnecessary_sort_by.rs index ea5aadbbc..1966990bd 100644 --- a/clippy_lints/src/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -1,51 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_trait_method; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::implements_trait; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::ty::{self, subst::GenericArgKind}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; -declare_clippy_lint! { - /// ### What it does - /// Detects uses of `Vec::sort_by` passing in a closure - /// which compares the two arguments, either directly or indirectly. - /// - /// ### Why is this bad? - /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if - /// possible) than to use `Vec::sort_by` and a more complicated - /// closure. - /// - /// ### Known problems - /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already - /// imported by a use statement, then it will need to be added manually. - /// - /// ### Example - /// ```rust - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec = Vec::new(); - /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); - /// ``` - /// Use instead: - /// ```rust - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec = Vec::new(); - /// vec.sort_by_key(|a| a.foo()); - /// ``` - #[clippy::version = "1.46.0"] - pub UNNECESSARY_SORT_BY, - complexity, - "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" -} - -declare_lint_pass!(UnnecessarySortBy => [UNNECESSARY_SORT_BY]); +use super::UNNECESSARY_SORT_BY; enum LintTrigger { Sort(SortDetection), @@ -54,7 +20,6 @@ enum LintTrigger { struct SortDetection { vec_name: String, - unstable: bool, } struct SortByKeyDetection { @@ -62,7 +27,6 @@ struct SortByKeyDetection { closure_arg: String, closure_body: String, reverse: bool, - unstable: bool, } /// Detect if the two expressions are mirrored (identical, except one @@ -150,20 +114,20 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident } } -fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { +fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option { if_chain! { - if let ExprKind::MethodCall(name_ident, args, _) = &expr.kind; - if let name = name_ident.ident.name.to_ident_string(); - if name == "sort_by" || name == "sort_unstable_by"; - if let [vec, Expr { kind: ExprKind::Closure(Closure { body: closure_body_id, .. }), .. }] = args; - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(vec), sym::Vec); - if let closure_body = cx.tcx.hir().body(*closure_body_id); + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if cx.tcx.type_of(impl_id).is_slice(); + if let ExprKind::Closure(&Closure { body, .. }) = arg.kind; + if let closure_body = cx.tcx.hir().body(body); if let &[ Param { pat: Pat { kind: PatKind::Binding(_, _, left_ident, _), .. }, ..}, Param { pat: Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. } ] = &closure_body.params; - if let ExprKind::MethodCall(method_path, [ref left_expr, ref right_expr], _) = &closure_body.value.kind; + if let ExprKind::MethodCall(method_path, [left_expr, right_expr], _) = closure_body.value.kind; if method_path.ident.name == sym::cmp; + if is_trait_method(cx, &closure_body.value, sym::Ord); then { let (closure_body, closure_arg, reverse) = if mirrored_exprs( left_expr, @@ -177,19 +141,18 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { } else { return None; }; - let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); - let unstable = name == "sort_unstable_by"; + let vec_name = Sugg::hir(cx, recv, "..").to_string(); if_chain! { - if let ExprKind::Path(QPath::Resolved(_, Path { - segments: [PathSegment { ident: left_name, .. }], .. - })) = &left_expr.kind; - if left_name == left_ident; - if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[]) - }); + if let ExprKind::Path(QPath::Resolved(_, Path { + segments: [PathSegment { ident: left_name, .. }], .. + })) = &left_expr.kind; + if left_name == left_ident; + if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[]) + }); then { - return Some(LintTrigger::Sort(SortDetection { vec_name, unstable })); + return Some(LintTrigger::Sort(SortDetection { vec_name })); } } @@ -199,7 +162,6 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { closure_arg, closure_body, reverse, - unstable, })); } } @@ -213,46 +175,50 @@ fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(ty.kind(), ty::Ref(..)) || ty.walk().any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))) } -impl LateLintPass<'_> for UnnecessarySortBy { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - match detect_lint(cx, expr) { - Some(LintTrigger::SortByKey(trigger)) => span_lint_and_sugg( - cx, - UNNECESSARY_SORT_BY, - expr.span, - "use Vec::sort_by_key here instead", - "try", - format!( - "{}.sort{}_by_key(|{}| {})", - trigger.vec_name, - if trigger.unstable { "_unstable" } else { "" }, - trigger.closure_arg, - if trigger.reverse { - format!("std::cmp::Reverse({})", trigger.closure_body) - } else { - trigger.closure_body.to_string() - }, - ), +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, + is_unstable: bool, +) { + match detect_lint(cx, expr, recv, arg) { + Some(LintTrigger::SortByKey(trigger)) => span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort_by_key here instead", + "try", + format!( + "{}.sort{}_by_key(|{}| {})", + trigger.vec_name, + if is_unstable { "_unstable" } else { "" }, + trigger.closure_arg, if trigger.reverse { - Applicability::MaybeIncorrect + format!("std::cmp::Reverse({})", trigger.closure_body) } else { - Applicability::MachineApplicable + trigger.closure_body.to_string() }, ), - Some(LintTrigger::Sort(trigger)) => span_lint_and_sugg( - cx, - UNNECESSARY_SORT_BY, - expr.span, - "use Vec::sort here instead", - "try", - format!( - "{}.sort{}()", - trigger.vec_name, - if trigger.unstable { "_unstable" } else { "" }, - ), - Applicability::MachineApplicable, + if trigger.reverse { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }, + ), + Some(LintTrigger::Sort(trigger)) => span_lint_and_sugg( + cx, + UNNECESSARY_SORT_BY, + expr.span, + "use Vec::sort here instead", + "try", + format!( + "{}.sort{}()", + trigger.vec_name, + if is_unstable { "_unstable" } else { "" }, ), - None => {}, - } + Applicability::MachineApplicable, + ), + None => {}, } } diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index b3276f139..44bf84352 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -3,11 +3,10 @@ use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{ - contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, + get_associated_type, get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, peel_mid_ty_refs, }; -use clippy_utils::{meets_msrv, msrvs}; - use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; +use clippy_utils::{meets_msrv, msrvs}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -279,7 +278,19 @@ fn check_other_call_arg<'tcx>( &trait_predicate.trait_ref.substs.iter().skip(1).collect::>()[..], call_substs, ); - implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + // if `expr` is a `String` and generic target is [u8], skip + // (https://github.com/rust-lang/rust-clippy/issues/9317). + if let [subst] = composed_substs[..] + && let GenericArgKind::Type(arg_ty) = subst.unpack() + && arg_ty.is_slice() + && let inner_ty = arg_ty.builtin_index().unwrap() + && let ty::Uint(ty::UintTy::U8) = inner_ty.kind() + && let self_ty = cx.typeck_results().expr_ty(expr).peel_refs() + && is_type_diagnostic_item(cx, self_ty, sym::String) { + false + } else { + implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + } } else { false }; @@ -292,7 +303,7 @@ fn check_other_call_arg<'tcx>( // (https://github.com/rust-lang/rust-clippy/issues/8507). if (n_refs == 0 && !receiver_ty.is_ref()) || trait_predicate.def_id() != as_ref_trait_id - || !contains_ty(fn_sig.output(), input); + || !fn_sig.output().contains(input); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { span_lint_and_sugg( @@ -360,25 +371,15 @@ fn get_input_traits_and_projections<'tcx>( ) -> (Vec>, Vec>) { let mut trait_predicates = Vec::new(); let mut projection_predicates = Vec::new(); - for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() { - // `substs` should have 1 + n elements. The first is the type on the left hand side of an - // `as`. The remaining n are trait parameters. - let is_input_substs = |substs: SubstsRef<'tcx>| { - if_chain! { - if let Some(arg) = substs.iter().next(); - if let GenericArgKind::Type(arg_ty) = arg.unpack(); - if arg_ty == input; - then { true } else { false } - } - }; + for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() { match predicate.kind().skip_binder() { PredicateKind::Trait(trait_predicate) => { - if is_input_substs(trait_predicate.trait_ref.substs) { + if trait_predicate.trait_ref.self_ty() == input { trait_predicates.push(trait_predicate); } }, PredicateKind::Projection(projection_predicate) => { - if is_input_substs(projection_predicate.projection_ty.substs) { + if projection_predicate.projection_ty.self_ty() == input { projection_predicates.push(projection_predicate); } }, diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs index ce1a52e54..ee17f2d78 100644 --- a/clippy_lints/src/methods/unwrap_used.rs +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -7,18 +7,26 @@ use rustc_span::sym; use super::{EXPECT_USED, UNWRAP_USED}; -/// lint use of `unwrap()` for `Option`s and `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) { +/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`. +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + recv: &hir::Expr<'_>, + is_err: bool, + allow_unwrap_in_tests: bool, +) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { Some((UNWRAP_USED, "an Option", "None", "")) } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((UNWRAP_USED, "a Result", "Err", "an ")) + Some((UNWRAP_USED, "a Result", if is_err { "Ok" } else { "Err" }, "an ")) } else { None }; + let method_suffix = if is_err { "_err" } else { "" }; + if allow_unwrap_in_tests && is_in_test_function(cx.tcx, expr.hir_id) { return; } @@ -27,7 +35,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { format!( "if you don't want to handle the `{none_value}` case gracefully, consider \ - using `expect()` to provide a better panic message" + using `expect{method_suffix}()` to provide a better panic message" ) } else { format!("if this value is {none_prefix}`{none_value}`, it will panic") @@ -37,7 +45,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr cx, lint, expr.span, - &format!("used `unwrap()` on `{kind}` value"), + &format!("used `unwrap{method_suffix}()` on `{kind}` value"), None, &help, ); diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints/src/methods/vec_resize_to_zero.rs new file mode 100644 index 000000000..02d8364cb --- /dev/null +++ b/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -0,0 +1,45 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; +use rustc_span::{sym, Span}; + +use super::VEC_RESIZE_TO_ZERO; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + count_arg: &'tcx Expr<'_>, + default_arg: &'tcx Expr<'_>, + name_span: Span, +) { + if_chain! { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let Some(impl_id) = cx.tcx.impl_of_method(method_id); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Vec); + if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = count_arg.kind; + if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = default_arg.kind; + then { + let method_call_span = expr.span.with_lo(name_span.lo()); + span_lint_and_then( + cx, + VEC_RESIZE_TO_ZERO, + expr.span, + "emptying a vector with `resize`", + |db| { + db.help("the arguments may be inverted..."); + db.span_suggestion( + method_call_span, + "...or you can empty the vector with", + "clear()".to_string(), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/methods/verbose_file_reads.rs b/clippy_lints/src/methods/verbose_file_reads.rs new file mode 100644 index 000000000..2fe5ae9a9 --- /dev/null +++ b/clippy_lints/src/methods/verbose_file_reads.rs @@ -0,0 +1,28 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_trait_method; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::VERBOSE_FILE_READS; + +pub(super) const READ_TO_END_MSG: (&str, &str) = ("use of `File::read_to_end`", "consider using `fs::read` instead"); +pub(super) const READ_TO_STRING_MSG: (&str, &str) = ( + "use of `File::read_to_string`", + "consider using `fs::read_to_string` instead", +); + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + recv: &'tcx Expr<'_>, + (msg, help): (&str, &str), +) { + if is_trait_method(cx, expr, sym::IoRead) + && matches!(recv.kind, ExprKind::Path(QPath::Resolved(None, _))) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(recv).peel_refs(), sym::File) + { + span_lint_and_help(cx, VERBOSE_FILE_READS, expr.span, msg, None, help); + } +} diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index df044538f..7c4ae746e 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -46,7 +46,7 @@ fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { "these patterns are unneeded as the `..` pattern can match those elements" }, if only_one { "remove it" } else { "remove them" }, - "".to_string(), + String::new(), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index f763e0d24..020efeaeb 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -40,7 +40,7 @@ declare_clippy_lint! { /// } /// impl Foo {} /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub MISMATCHING_TYPE_PARAM_ORDER, pedantic, "type parameter positioned inconsistently between type def and impl block" diff --git a/clippy_lints/src/multi_assignments.rs b/clippy_lints/src/multi_assignments.rs new file mode 100644 index 000000000..81eb1a085 --- /dev/null +++ b/clippy_lints/src/multi_assignments.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for nested assignments. + /// + /// ### Why is this bad? + /// While this is in most cases already a type mismatch, + /// the result of an assignment being `()` can throw off people coming from languages like python or C, + /// where such assignments return a copy of the assigned value. + /// + /// ### Example + /// ```rust + ///# let (a, b); + /// a = b = 42; + /// ``` + /// Use instead: + /// ```rust + ///# let (a, b); + /// b = 42; + /// a = b; + /// ``` + #[clippy::version = "1.65.0"] + pub MULTI_ASSIGNMENTS, + suspicious, + "instead of using `a = b = c;` use `a = c; b = c;`" +} + +declare_lint_pass!(MultiAssignments => [MULTI_ASSIGNMENTS]); + +fn strip_paren_blocks(expr: &Expr) -> &Expr { + match &expr.kind { + ExprKind::Paren(e) => strip_paren_blocks(e), + ExprKind::Block(b, _) => { + if let [ + Stmt { + kind: StmtKind::Expr(e), + .. + }, + ] = &b.stmts[..] + { + strip_paren_blocks(e) + } else { + expr + } + }, + _ => expr, + } +} + +impl EarlyLintPass for MultiAssignments { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Assign(target, source, _) = &expr.kind { + if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(target).kind { + span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); + }; + if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(source).kind { + span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); + } + }; + } +} diff --git a/clippy_lints/src/mut_mutex_lock.rs b/clippy_lints/src/mut_mutex_lock.rs deleted file mode 100644 index b7f981faa..000000000 --- a/clippy_lints/src/mut_mutex_lock.rs +++ /dev/null @@ -1,70 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `&mut Mutex::lock` calls - /// - /// ### Why is this bad? - /// `Mutex::lock` is less efficient than - /// calling `Mutex::get_mut`. In addition you also have a statically - /// guarantee that the mutex isn't locked, instead of just a runtime - /// guarantee. - /// - /// ### Example - /// ```rust - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let mut value = value_mutex.lock().unwrap(); - /// *value += 1; - /// ``` - /// Use instead: - /// ```rust - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let value = value_mutex.get_mut().unwrap(); - /// *value += 1; - /// ``` - #[clippy::version = "1.49.0"] - pub MUT_MUTEX_LOCK, - style, - "`&mut Mutex::lock` does unnecessary locking" -} - -declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]); - -impl<'tcx> LateLintPass<'tcx> for MutMutexLock { - fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { - if_chain! { - if let ExprKind::MethodCall(path, [self_arg, ..], _) = &ex.kind; - if path.ident.name == sym!(lock); - let ty = cx.typeck_results().expr_ty(self_arg); - if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); - if is_type_diagnostic_item(cx, *inner_ty, sym::Mutex); - then { - span_lint_and_sugg( - cx, - MUT_MUTEX_LOCK, - path.ident.span, - "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", - "change this to", - "get_mut".to_owned(), - Applicability::MaybeIncorrect, - ); - } - } - } -} diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 413a740be..774a3540d 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -1,25 +1,16 @@ -use std::collections::VecDeque; - -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_lint_allowed; -use itertools::{izip, Itertools}; -use rustc_ast::{walk_list, Label, Mutability}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{get_expr_use_or_unification_node, get_parent_node, path_def_id, path_to_local, path_to_local_id}; +use core::cell::Cell; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; -use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor}; -use rustc_hir::{ - Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, - PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp, -}; +use rustc_hir::hir_id::HirIdMap; +use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_middle::ty::{Ty, TyCtxt, TypeckResults}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::kw; -use rustc_span::symbol::Ident; +use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::{kw, Ident}; use rustc_span::Span; declare_clippy_lint! { @@ -89,572 +80,323 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.61.0"] pub ONLY_USED_IN_RECURSION, - nursery, + complexity, "arguments that is only used in recursion can be removed" } -declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); +impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); + +#[derive(Clone, Copy)] +enum FnKind { + Fn, + TraitFn, + // This is a hack. Ideally we would store a `SubstsRef<'tcx>` type here, but a lint pass must be `'static`. + // Substitutions are, however, interned. This allows us to store the pointer as a `usize` when comparing for + // equality. + ImplTraitFn(usize), +} + +struct Param { + /// The function this is a parameter for. + fn_id: DefId, + fn_kind: FnKind, + /// The index of this parameter. + idx: usize, + ident: Ident, + /// Whether this parameter should be linted. Set by `Params::flag_for_linting`. + apply_lint: Cell, + /// All the uses of this parameter. + uses: Vec, +} +impl Param { + fn new(fn_id: DefId, fn_kind: FnKind, idx: usize, ident: Ident) -> Self { + Self { + fn_id, + fn_kind, + idx, + ident, + apply_lint: Cell::new(true), + uses: Vec::new(), + } + } +} + +#[derive(Debug)] +struct Usage { + span: Span, + idx: usize, +} +impl Usage { + fn new(span: Span, idx: usize) -> Self { + Self { span, idx } + } +} + +/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the +/// `DefId` of the function paired with the parameter's index. +#[derive(Default)] +struct Params { + params: Vec, + by_id: HirIdMap, + by_fn: FxHashMap<(DefId, usize), usize>, +} +impl Params { + fn insert(&mut self, param: Param, id: HirId) { + let idx = self.params.len(); + self.by_id.insert(id, idx); + self.by_fn.insert((param.fn_id, param.idx), idx); + self.params.push(param); + } + + fn remove_by_id(&mut self, id: HirId) { + if let Some(param) = self.get_by_id_mut(id) { + param.uses = Vec::new(); + let key = (param.fn_id, param.idx); + self.by_fn.remove(&key); + self.by_id.remove(&id); + } + } + + fn get_by_id_mut(&mut self, id: HirId) -> Option<&mut Param> { + self.params.get_mut(*self.by_id.get(&id)?) + } + + fn get_by_fn(&self, id: DefId, idx: usize) -> Option<&Param> { + self.params.get(*self.by_fn.get(&(id, idx))?) + } + + fn clear(&mut self) { + self.params.clear(); + self.by_id.clear(); + self.by_fn.clear(); + } + + /// Sets the `apply_lint` flag on each parameter. + fn flag_for_linting(&mut self) { + // Stores the list of parameters currently being resolved. Needed to avoid cycles. + let mut eval_stack = Vec::new(); + for param in &self.params { + self.try_disable_lint_for_param(param, &mut eval_stack); + } + } + + // Use by calling `flag_for_linting`. + fn try_disable_lint_for_param(&self, param: &Param, eval_stack: &mut Vec) -> bool { + if !param.apply_lint.get() { + true + } else if param.uses.is_empty() { + // Don't lint on unused parameters. + param.apply_lint.set(false); + true + } else if eval_stack.contains(¶m.idx) { + // Already on the evaluation stack. Returning false will continue to evaluate other dependencies. + false + } else { + eval_stack.push(param.idx); + // Check all cases when used at a different parameter index. + // Needed to catch cases like: `fn f(x: u32, y: u32) { f(y, x) }` + for usage in param.uses.iter().filter(|u| u.idx != param.idx) { + if self + .get_by_fn(param.fn_id, usage.idx) + // If the parameter can't be found, then it's used for more than just recursion. + .map_or(true, |p| self.try_disable_lint_for_param(p, eval_stack)) + { + param.apply_lint.set(false); + eval_stack.pop(); + return true; + } + } + eval_stack.pop(); + false + } + } +} + +#[derive(Default)] +pub struct OnlyUsedInRecursion { + /// Track the top-level body entered. Needed to delay reporting when entering nested bodies. + entered_body: Option, + params: Params, +} impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - decl: &'tcx rustc_hir::FnDecl<'tcx>, - body: &'tcx Body<'tcx>, - _: Span, - id: HirId, - ) { - if is_lint_allowed(cx, ONLY_USED_IN_RECURSION, id) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) { + if body.value.span.from_expansion() { return; } - if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind { - let def_id = id.owner.to_def_id(); - let data = cx.tcx.def_path(def_id).data; - - if data.len() > 1 { - match data.get(data.len() - 2) { - Some(DisambiguatedDefPathData { - data: DefPathData::Impl, - disambiguator, - }) if *disambiguator != 0 => return, - _ => {}, - } - } - - let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None); - - let ty_res = cx.typeck_results(); - let param_span = body - .params - .iter() - .flat_map(|param| { - let mut v = Vec::new(); - param.pat.each_binding(|_, hir_id, span, ident| { - v.push((hir_id, span, ident)); - }); - v - }) - .skip(if has_self { 1 } else { 0 }) - .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_')) - .collect_vec(); - - let params = body.params.iter().map(|param| param.pat).collect(); - - let mut visitor = SideEffectVisit { - graph: FxHashMap::default(), - has_side_effect: FxHashSet::default(), - ret_vars: Vec::new(), - contains_side_effect: false, - break_vars: FxHashMap::default(), - params, - fn_ident: ident, - fn_def_id: def_id, - is_method: matches!(kind, FnKind::Method(..)), - has_self, - ty_res, - tcx: cx.tcx, - visited_exprs: FxHashSet::default(), - }; - - visitor.visit_expr(&body.value); - let vars = std::mem::take(&mut visitor.ret_vars); - // this would set the return variables to side effect - visitor.add_side_effect(vars); - - let mut queue = visitor.has_side_effect.iter().copied().collect::>(); - - // a simple BFS to check all the variables that have side effect - while let Some(id) = queue.pop_front() { - if let Some(next) = visitor.graph.get(&id) { - for i in next { - if !visitor.has_side_effect.contains(i) { - visitor.has_side_effect.insert(*i); - queue.push_back(*i); - } - } - } - } - - for (id, span, ident) in param_span { - // if the variable is not used in recursion, it would be marked as unused - if !visitor.has_side_effect.contains(&id) { - let mut queue = VecDeque::new(); - let mut visited = FxHashSet::default(); - - queue.push_back(id); - - // a simple BFS to check the graph can reach to itself - // if it can't, it means the variable is never used in recursion - while let Some(id) = queue.pop_front() { - if let Some(next) = visitor.graph.get(&id) { - for i in next { - if !visited.contains(i) { - visited.insert(id); - queue.push_back(*i); - } - } - } - } - - if visited.contains(&id) { - span_lint_and_sugg( - cx, - ONLY_USED_IN_RECURSION, - span, - "parameter is only used in recursion", - "if this is intentional, prefix with an underscore", - format!("_{}", ident.name.as_str()), - Applicability::MaybeIncorrect, - ); - } - } - } - } - } -} - -pub fn is_primitive(ty: Ty<'_>) -> bool { - let ty = ty.peel_refs(); - ty.is_primitive() || ty.is_str() -} - -pub fn is_array(ty: Ty<'_>) -> bool { - let ty = ty.peel_refs(); - ty.is_array() || ty.is_array_slice() -} - -/// This builds the graph of side effect. -/// The edge `a -> b` means if `a` has side effect, `b` will have side effect. -/// -/// There are some example in following code: -/// ```rust, ignore -/// let b = 1; -/// let a = b; // a -> b -/// let (c, d) = (a, b); // c -> b, d -> b -/// -/// let e = if a == 0 { // e -> a -/// c // e -> c -/// } else { -/// d // e -> d -/// }; -/// ``` -pub struct SideEffectVisit<'tcx> { - graph: FxHashMap>, - has_side_effect: FxHashSet, - // bool for if the variable was dereferenced from mutable reference - ret_vars: Vec<(HirId, bool)>, - contains_side_effect: bool, - // break label - break_vars: FxHashMap>, - params: Vec<&'tcx Pat<'tcx>>, - fn_ident: Ident, - fn_def_id: DefId, - is_method: bool, - has_self: bool, - ty_res: &'tcx TypeckResults<'tcx>, - tcx: TyCtxt<'tcx>, - visited_exprs: FxHashSet, -} - -impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> { - fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { - match s.kind { - StmtKind::Local(Local { - pat, init: Some(init), .. - }) => { - self.visit_pat_expr(pat, init, false); - }, - StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => { - walk_stmt(self, s); - }, - StmtKind::Local(_) => {}, - } - self.ret_vars.clear(); - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if !self.visited_exprs.insert(ex.hir_id) { - return; - } - match ex.kind { - ExprKind::Array(exprs) | ExprKind::Tup(exprs) => { - self.ret_vars = exprs - .iter() - .flat_map(|expr| { - self.visit_expr(expr); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - }, - ExprKind::Call(callee, args) => self.visit_fn(callee, args), - ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args), - ExprKind::Binary(_, lhs, rhs) => { - self.visit_bin_op(lhs, rhs); - }, - ExprKind::Unary(op, expr) => self.visit_un_op(op, expr), - ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false), - ExprKind::If(bind, then_expr, else_expr) => { - self.visit_if(bind, then_expr, else_expr); - }, - ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms), - // since analysing the closure is not easy, just set all variables in it to side-effect - ExprKind::Closure(&Closure { body, .. }) => { - let body = self.tcx.hir().body(body); - self.visit_body(body); - let vars = std::mem::take(&mut self.ret_vars); - self.add_side_effect(vars); - }, - ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => { - self.visit_block_label(block, label); - }, - ExprKind::Assign(bind, expr, _) => { - self.visit_assign(bind, expr); - }, - ExprKind::AssignOp(_, bind, expr) => { - self.visit_assign(bind, expr); - self.visit_bin_op(bind, expr); - }, - ExprKind::Field(expr, _) => { - self.visit_expr(expr); - if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { - self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + // `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions. + // It can't be renamed, and it can't be removed without removing it from multiple functions. + let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) { + Some(Node::Item(i)) => (i.def_id.to_def_id(), FnKind::Fn, 0), + Some(Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(ref sig, _), + def_id, + .. + })) => ( + def_id.to_def_id(), + FnKind::TraitFn, + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ), + Some(Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(ref sig, _), + def_id, + .. + })) => { + #[allow(trivial_casts)] + if let Some(Node::Item(item)) = get_parent_node(cx.tcx, cx.tcx.hir().local_def_id_to_hir_id(def_id)) + && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.def_id) + && let Some(trait_item_id) = cx.tcx.associated_item(def_id).trait_item_def_id + { + ( + trait_item_id, + FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const _ as usize), + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ) + } else { + (def_id.to_def_id(), FnKind::Fn, 0) } }, - ExprKind::Index(expr, index) => { - self.visit_expr(expr); - let mut vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(index); - self.ret_vars.append(&mut vars); - - if !is_array(self.ty_res.expr_ty(expr)) { - self.add_side_effect(self.ret_vars.clone()); - } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { - self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); - } - }, - ExprKind::Break(dest, Some(expr)) => { - self.visit_expr(expr); - if let Some(label) = dest.label { - self.break_vars - .entry(label.ident) - .or_insert(Vec::new()) - .append(&mut self.ret_vars); - } - self.contains_side_effect = true; - }, - ExprKind::Ret(Some(expr)) => { - self.visit_expr(expr); - let vars = std::mem::take(&mut self.ret_vars); - self.add_side_effect(vars); - self.contains_side_effect = true; - }, - ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => { - self.contains_side_effect = true; - }, - ExprKind::Struct(_, exprs, expr) => { - let mut ret_vars = exprs - .iter() - .flat_map(|field| { - self.visit_expr(field.expr); - std::mem::take(&mut self.ret_vars) - }) - .collect(); - - walk_list!(self, visit_expr, expr); - self.ret_vars.append(&mut ret_vars); - }, - _ => walk_expr(self, ex), - } - } - - fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) { - if let Res::Local(id) = path.res { - self.ret_vars.push((id, false)); - } - } -} - -impl<'tcx> SideEffectVisit<'tcx> { - fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) { - // Just support array and tuple unwrapping for now. - // - // ex) `(a, b) = (c, d);` - // The graph would look like this: - // a -> c - // b -> d - // - // This would minimize the connection of the side-effect graph. - match (&lhs.kind, &rhs.kind) { - (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => { - // if not, it is a compile error - debug_assert!(lhs.len() == rhs.len()); - izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs)); - }, - // in other assigns, we have to connect all each other - // because they can be connected somehow - _ => { - self.visit_expr(lhs); - let lhs_vars = std::mem::take(&mut self.ret_vars); - self.visit_expr(rhs); - let rhs_vars = std::mem::take(&mut self.ret_vars); - self.connect_assign(&lhs_vars, &rhs_vars, false); - }, - } - } - - fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option + ImplTrait {} + +fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds_unfixable.stderr b/tests/ui/trait_duplication_in_bounds_unfixable.stderr new file mode 100644 index 000000000..fbd9abb00 --- /dev/null +++ b/tests/ui/trait_duplication_in_bounds_unfixable.stderr @@ -0,0 +1,71 @@ +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds_unfixable.rs:6:15 + | +LL | fn bad_foo(arg0: T, arg1: Z) + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/trait_duplication_in_bounds_unfixable.rs:1:9 + | +LL | #![deny(clippy::trait_duplication_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider removing this trait bound + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds_unfixable.rs:6:23 + | +LL | fn bad_foo(arg0: T, arg1: Z) + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:35:15 + | +LL | Self: Default; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:49:15 + | +LL | Self: Default + Clone; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:55:15 + | +LL | Self: Default + Clone; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:55:25 + | +LL | Self: Default + Clone; + | ^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:58:15 + | +LL | Self: Default; + | ^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds_unfixable.rs:93:15 + | +LL | Self: Iterator, + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: aborting due to 8 previous errors + diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index ebcaa7a84..5aad0b442 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -4,6 +4,7 @@ use core::any::TypeId; use core::ffi::c_void; use core::mem::{size_of, transmute, MaybeUninit}; +use core::ptr::NonNull; fn value() -> T { unimplemented!() @@ -109,6 +110,17 @@ fn main() { let _: Ty2 = transmute(value::>>()); // Ok let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec>()); // Ok + + let _: *const Ty2 = transmute(value::<*const Ty2C, u32>>()); // Ok + let _: *const Ty2C, u32> = transmute(value::<*const Ty2>()); // Ok + let _: *const Ty2 = transmute(value::<*const Ty2C<(), Ty2>>()); // Ok + let _: *const Ty2C<(), Ty2> = transmute(value::<*const Ty2>()); // Ok + + let _: *const Ty2 = transmute(value::<*const Ty2C>>()); // Err + let _: *const Ty2C> = transmute(value::<*const Ty2>()); // Err + + let _: NonNull = transmute(value::>()); // Ok + let _: NonNull<(String, String)> = transmute(value::>()); // Ok } } diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 28bfba6c7..e50a77329 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -1,5 +1,5 @@ error: transmute from `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:27:33 + --> $DIR/transmute_undefined_repr.rs:28:33 | LL | let _: Ty2C = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | let _: Ty2C = transmute(value::>()); // Lin = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings` error: transmute into `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:28:32 + --> $DIR/transmute_undefined_repr.rs:29:32 | LL | let _: Ty2 = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from `Ty>` to `Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:33:32 + --> $DIR/transmute_undefined_repr.rs:34:32 | LL | let _: Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | let _: Ty2 = transmute(value::>>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty2` to `Ty>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:34:36 + --> $DIR/transmute_undefined_repr.rs:35:36 | LL | let _: Ty> = transmute(value::>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -29,7 +29,7 @@ LL | let _: Ty> = transmute(value::>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty<&Ty2>` to `&Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:39:33 + --> $DIR/transmute_undefined_repr.rs:40:33 | LL | let _: &Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL | let _: &Ty2 = transmute(value::>>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&Ty2` to `Ty<&Ty2>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:40:37 + --> $DIR/transmute_undefined_repr.rs:41:37 | LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `std::boxed::Box>` to `&mut Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:57:45 + --> $DIR/transmute_undefined_repr.rs:58:45 | LL | let _: &'static mut Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -53,15 +53,31 @@ LL | let _: &'static mut Ty2 = transmute(value::` to `std::boxed::Box>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:58:37 + --> $DIR/transmute_undefined_repr.rs:59:37 | LL | let _: Box> = transmute(value::<&'static mut Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts +error: transmute into `*const Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:119:39 + | +LL | let _: *const Ty2 = transmute(value::<*const Ty2C>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the contained type `Ty2` has an undefined layout + +error: transmute from `*const Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:120:50 + | +LL | let _: *const Ty2C> = transmute(value::<*const Ty2>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the contained type `Ty2` has an undefined layout + error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:138:35 + --> $DIR/transmute_undefined_repr.rs:150:35 | LL | let _: Vec> = transmute(value::>>()); // Err | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,12 +85,12 @@ LL | let _: Vec> = transmute(value::>>()); / = note: two instances of the same generic type (`Vec`) may have different layouts error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:139:35 + --> $DIR/transmute_undefined_repr.rs:151:35 | LL | let _: Vec> = transmute(value::>>()); // Err | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Vec`) may have different layouts -error: aborting due to 10 previous errors +error: aborting due to 12 previous errors diff --git a/tests/ui/unicode.fixed b/tests/ui/unicode.fixed index 328cda369..94b472345 100644 --- a/tests/ui/unicode.fixed +++ b/tests/ui/unicode.fixed @@ -1,4 +1,7 @@ // run-rustfix +// compile-flags: --test +#![allow(dead_code)] + #[warn(clippy::invisible_characters)] fn zero() { print!("Here >\u{200B}< is a ZWS, and \u{200B}another"); @@ -15,22 +18,43 @@ fn canon() { print!("a\u{0300}h?"); // also ok } -#[warn(clippy::non_ascii_literal)] -fn uni() { - print!("\u{dc}ben!"); - print!("\u{DC}ben!"); // this is ok -} +mod non_ascii_literal { + #![deny(clippy::non_ascii_literal)] -// issue 8013 -#[warn(clippy::non_ascii_literal)] -fn single_quote() { - const _EMPTY_BLOCK: char = '\u{25b1}'; - const _FULL_BLOCK: char = '\u{25b0}'; + fn uni() { + print!("\u{dc}ben!"); + print!("\u{DC}ben!"); // this is ok + } + + // issue 8013 + fn single_quote() { + const _EMPTY_BLOCK: char = '\u{25b1}'; + const _FULL_BLOCK: char = '\u{25b0}'; + } + + #[test] + pub fn issue_7739() { + // Ryū crate: https://github.com/dtolnay/ryu + } + + mod issue_8263 { + #![deny(clippy::non_ascii_literal)] + + // Re-allow for a single test + #[test] + #[allow(clippy::non_ascii_literal)] + fn allowed() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + + #[test] + fn denied() { + let _ = "\u{60b2}\u{3057}\u{3044}\u{304b}\u{306a}\u{3001}\u{3053}\u{3053}\u{306b}\u{65e5}\u{672c}\u{8a9e}\u{3092}\u{66f8}\u{304f}\u{3053}\u{3068}\u{306f}\u{3067}\u{304d}\u{306a}\u{3044}\u{3002}"; + } + } } fn main() { zero(); - uni(); canon(); - single_quote(); } diff --git a/tests/ui/unicode.rs b/tests/ui/unicode.rs index 7828d6bcb..6ad0b255b 100644 --- a/tests/ui/unicode.rs +++ b/tests/ui/unicode.rs @@ -1,4 +1,7 @@ // run-rustfix +// compile-flags: --test +#![allow(dead_code)] + #[warn(clippy::invisible_characters)] fn zero() { print!("Here >​< is a ZWS, and ​another"); @@ -15,22 +18,43 @@ fn canon() { print!("a\u{0300}h?"); // also ok } -#[warn(clippy::non_ascii_literal)] -fn uni() { - print!("Üben!"); - print!("\u{DC}ben!"); // this is ok -} +mod non_ascii_literal { + #![deny(clippy::non_ascii_literal)] -// issue 8013 -#[warn(clippy::non_ascii_literal)] -fn single_quote() { - const _EMPTY_BLOCK: char = '▱'; - const _FULL_BLOCK: char = '▰'; + fn uni() { + print!("Üben!"); + print!("\u{DC}ben!"); // this is ok + } + + // issue 8013 + fn single_quote() { + const _EMPTY_BLOCK: char = '▱'; + const _FULL_BLOCK: char = '▰'; + } + + #[test] + pub fn issue_7739() { + // Ryū crate: https://github.com/dtolnay/ryu + } + + mod issue_8263 { + #![deny(clippy::non_ascii_literal)] + + // Re-allow for a single test + #[test] + #[allow(clippy::non_ascii_literal)] + fn allowed() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + + #[test] + fn denied() { + let _ = "悲しいかな、ここに日本語を書くことはできない。"; + } + } } fn main() { zero(); - uni(); canon(); - single_quote(); } diff --git a/tests/ui/unicode.stderr b/tests/ui/unicode.stderr index 01d3f3c02..ea74a8145 100644 --- a/tests/ui/unicode.stderr +++ b/tests/ui/unicode.stderr @@ -1,5 +1,5 @@ error: invisible character detected - --> $DIR/unicode.rs:4:12 + --> $DIR/unicode.rs:7:12 | LL | print!("Here >​< is a ZWS, and ​another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{200B}< is a ZWS, and /u{200B}another"` @@ -7,19 +7,19 @@ LL | print!("Here >​< is a ZWS, and ​another"); = note: `-D clippy::invisible-characters` implied by `-D warnings` error: invisible character detected - --> $DIR/unicode.rs:6:12 + --> $DIR/unicode.rs:9:12 | LL | print!("Here >­< is a SHY, and ­another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{AD}< is a SHY, and /u{AD}another"` error: invisible character detected - --> $DIR/unicode.rs:8:12 + --> $DIR/unicode.rs:11:12 | LL | print!("Here >⁠< is a WJ, and ⁠another"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{2060}< is a WJ, and /u{2060}another"` error: non-NFC Unicode sequence detected - --> $DIR/unicode.rs:14:12 + --> $DIR/unicode.rs:17:12 | LL | print!("̀àh?"); | ^^^^^ help: consider replacing the string with: `"̀àh?"` @@ -27,24 +27,40 @@ LL | print!("̀àh?"); = note: `-D clippy::unicode-not-nfc` implied by `-D warnings` error: literal non-ASCII character detected - --> $DIR/unicode.rs:20:12 + --> $DIR/unicode.rs:25:16 | -LL | print!("Üben!"); - | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` +LL | print!("Üben!"); + | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` | - = note: `-D clippy::non-ascii-literal` implied by `-D warnings` +note: the lint level is defined here + --> $DIR/unicode.rs:22:13 + | +LL | #![deny(clippy::non_ascii_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: literal non-ASCII character detected - --> $DIR/unicode.rs:27:32 + --> $DIR/unicode.rs:31:36 | -LL | const _EMPTY_BLOCK: char = '▱'; - | ^^^ help: consider replacing the string with: `'/u{25b1}'` +LL | const _EMPTY_BLOCK: char = '▱'; + | ^^^ help: consider replacing the string with: `'/u{25b1}'` error: literal non-ASCII character detected - --> $DIR/unicode.rs:28:31 + --> $DIR/unicode.rs:32:35 | -LL | const _FULL_BLOCK: char = '▰'; - | ^^^ help: consider replacing the string with: `'/u{25b0}'` +LL | const _FULL_BLOCK: char = '▰'; + | ^^^ help: consider replacing the string with: `'/u{25b0}'` -error: aborting due to 7 previous errors +error: literal non-ASCII character detected + --> $DIR/unicode.rs:52:21 + | +LL | let _ = "悲しいかな、ここに日本語を書くことはできない。"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"/u{60b2}/u{3057}/u{3044}/u{304b}/u{306a}/u{3001}/u{3053}/u{3053}/u{306b}/u{65e5}/u{672c}/u{8a9e}/u{3092}/u{66f8}/u{304f}/u{3053}/u{3068}/u{306f}/u{3067}/u{304d}/u{306a}/u{3044}/u{3002}"` + | +note: the lint level is defined here + --> $DIR/unicode.rs:41:17 + | +LL | #![deny(clippy::non_ascii_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_cast.fixed b/tests/ui/unnecessary_cast.fixed index b352b285c..ee9f15734 100644 --- a/tests/ui/unnecessary_cast.fixed +++ b/tests/ui/unnecessary_cast.fixed @@ -88,4 +88,13 @@ mod fixable { } type I32Alias = i32; + + fn issue_9380() { + let _: i32 = -1_i32; + let _: f32 = -(1) as f32; + let _: i64 = -1_i64; + let _: i64 = -(1.0) as i64; + + let _ = -(1 + 1) as i64; + } } diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index 6c8cc3eff..5b7041242 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -88,4 +88,13 @@ mod fixable { } type I32Alias = i32; + + fn issue_9380() { + let _: i32 = -(1) as i32; + let _: f32 = -(1) as f32; + let _: i64 = -(1) as i64; + let _: i64 = -(1.0) as i64; + + let _ = -(1 + 1) as i64; + } } diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index bad45f002..f7829ff3b 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -150,5 +150,17 @@ error: casting float literal to `f32` is unnecessary LL | let _ = -1.0 as f32; | ^^^^^^^^^^^ help: try: `-1.0_f32` -error: aborting due to 25 previous errors +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast.rs:93:22 + | +LL | let _: i32 = -(1) as i32; + | ^^^^^^^^^^^ help: try: `-1_i32` + +error: casting integer literal to `i64` is unnecessary + --> $DIR/unnecessary_cast.rs:95:22 + | +LL | let _: i64 = -(1) as i64; + | ^^^^^^^^^^^ help: try: `-1_i64` + +error: aborting due to 27 previous errors diff --git a/tests/ui/unnecessary_owned_empty_strings.fixed b/tests/ui/unnecessary_owned_empty_strings.fixed index f95f91329..40052c410 100644 --- a/tests/ui/unnecessary_owned_empty_strings.fixed +++ b/tests/ui/unnecessary_owned_empty_strings.fixed @@ -12,6 +12,7 @@ fn main() { ref_str_argument(""); // should be linted + #[allow(clippy::manual_string_new)] ref_str_argument(""); // should not be linted diff --git a/tests/ui/unnecessary_owned_empty_strings.rs b/tests/ui/unnecessary_owned_empty_strings.rs index 0cbdc151e..2304dff51 100644 --- a/tests/ui/unnecessary_owned_empty_strings.rs +++ b/tests/ui/unnecessary_owned_empty_strings.rs @@ -12,6 +12,7 @@ fn main() { ref_str_argument(&String::new()); // should be linted + #[allow(clippy::manual_string_new)] ref_str_argument(&String::from("")); // should not be linted diff --git a/tests/ui/unnecessary_owned_empty_strings.stderr b/tests/ui/unnecessary_owned_empty_strings.stderr index 46bc4597b..1eb198a86 100644 --- a/tests/ui/unnecessary_owned_empty_strings.stderr +++ b/tests/ui/unnecessary_owned_empty_strings.stderr @@ -7,7 +7,7 @@ LL | ref_str_argument(&String::new()); = note: `-D clippy::unnecessary-owned-empty-strings` implied by `-D warnings` error: usage of `&String::from("")` for a function expecting a `&str` argument - --> $DIR/unnecessary_owned_empty_strings.rs:15:22 + --> $DIR/unnecessary_owned_empty_strings.rs:16:22 | LL | ref_str_argument(&String::from("")); | ^^^^^^^^^^^^^^^^^ help: try: `""` diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index f4f76cd3d..9cd5bc73b 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -329,3 +329,31 @@ mod issue_8759_variant { rw.set_view(&rw.default_view().to_owned()); } } + +mod issue_9317 { + #![allow(dead_code)] + + struct Bytes {} + + impl ToString for Bytes { + fn to_string(&self) -> String { + "123".to_string() + } + } + + impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + &[1, 2, 3] + } + } + + fn consume>(c: C) { + let _ = c; + } + + pub fn main() { + let b = Bytes {}; + // Should not lint. + consume(b.to_string()); + } +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index fe09a489a..7f62ba3ab 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -329,3 +329,31 @@ mod issue_8759_variant { rw.set_view(&rw.default_view().to_owned()); } } + +mod issue_9317 { + #![allow(dead_code)] + + struct Bytes {} + + impl ToString for Bytes { + fn to_string(&self) -> String { + "123".to_string() + } + } + + impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + &[1, 2, 3] + } + } + + fn consume>(c: C) { + let _ = c; + } + + pub fn main() { + let b = Bytes {}; + // Should not lint. + consume(b.to_string()); + } +} diff --git a/tests/ui/unused_peekable.rs b/tests/ui/unused_peekable.rs new file mode 100644 index 000000000..153457e36 --- /dev/null +++ b/tests/ui/unused_peekable.rs @@ -0,0 +1,144 @@ +#![warn(clippy::unused_peekable)] +#![allow(clippy::no_effect)] + +use std::iter::Empty; +use std::iter::Peekable; + +fn main() { + invalid(); + valid(); +} + +#[allow(clippy::unused_unit)] +fn invalid() { + let peekable = std::iter::empty::().peekable(); + + // Only lint `new_local` + let old_local = std::iter::empty::().peekable(); + let new_local = old_local; + + // Behind mut ref + let mut by_mut_ref_test = std::iter::empty::().peekable(); + let by_mut_ref = &mut by_mut_ref_test; + + // Explicitly returns `Peekable` + fn returns_peekable() -> Peekable> { + std::iter::empty().peekable() + } + + let peekable_from_fn = returns_peekable(); + + // Using a method not exclusive to `Peekable` + let mut peekable_using_iterator_method = std::iter::empty::().peekable(); + peekable_using_iterator_method.next(); + + // Passed by ref to another function + fn takes_ref(_peek: &Peekable>) {} + let passed_along_ref = std::iter::empty::().peekable(); + takes_ref(&passed_along_ref); + + // `by_ref` without `peek` + let mut by_ref_test = std::iter::empty::().peekable(); + let _by_ref = by_ref_test.by_ref(); + + let mut peekable_in_for_loop = std::iter::empty::().peekable(); + for x in peekable_in_for_loop {} +} + +fn valid() { + fn takes_peekable(_peek: Peekable>) {} + + // Passed to another function + let passed_along = std::iter::empty::().peekable(); + takes_peekable(passed_along); + + // Passed to another method + struct PeekableConsumer; + impl PeekableConsumer { + fn consume(&self, _: Peekable>) {} + fn consume_mut_ref(&self, _: &mut Peekable>) {} + } + + let peekable_consumer = PeekableConsumer; + let mut passed_along_to_method = std::iter::empty::().peekable(); + peekable_consumer.consume_mut_ref(&mut passed_along_to_method); + peekable_consumer.consume(passed_along_to_method); + + // `peek` called in another block + let mut peekable_in_block = std::iter::empty::().peekable(); + { + peekable_in_block.peek(); + } + + // Check the other `Peekable` methods :) + { + let mut peekable_with_peek_mut = std::iter::empty::().peekable(); + peekable_with_peek_mut.peek_mut(); + + let mut peekable_with_next_if = std::iter::empty::().peekable(); + peekable_with_next_if.next_if(|_| true); + + let mut peekable_with_next_if_eq = std::iter::empty::().peekable(); + peekable_with_next_if_eq.next_if_eq(&3); + } + + let mut peekable_in_closure = std::iter::empty::().peekable(); + let call_peek = |p: &mut Peekable>| { + p.peek(); + }; + call_peek(&mut peekable_in_closure); + + // From a macro + macro_rules! make_me_a_peekable_please { + () => { + std::iter::empty::().peekable() + }; + } + + let _unsuspecting_macro_user = make_me_a_peekable_please!(); + + // Generic Iterator returned + fn return_an_iter() -> impl Iterator { + std::iter::empty::().peekable() + } + + let _unsuspecting_user = return_an_iter(); + + // Call `peek` in a macro + macro_rules! peek_iter { + ($iter:ident) => { + $iter.peek(); + }; + } + + let mut peek_in_macro = std::iter::empty::().peekable(); + peek_iter!(peek_in_macro); + + // Behind mut ref + let mut by_mut_ref_test = std::iter::empty::().peekable(); + let by_mut_ref = &mut by_mut_ref_test; + by_mut_ref.peek(); + + // Behind ref + let mut by_ref_test = std::iter::empty::().peekable(); + let by_ref = &by_ref_test; + by_ref_test.peek(); + + // In struct + struct PeekableWrapper { + f: Peekable>, + } + + let struct_test = std::iter::empty::().peekable(); + PeekableWrapper { f: struct_test }; + + // `by_ref` before `peek` + let mut by_ref_test = std::iter::empty::().peekable(); + let peeked_val = by_ref_test.by_ref().peek(); + + // `peek` called in another block as the last expression + let mut peekable_last_expr = std::iter::empty::().peekable(); + { + peekable_last_expr.peek(); + } +} diff --git a/tests/ui/unused_peekable.stderr b/tests/ui/unused_peekable.stderr new file mode 100644 index 000000000..d557f5417 --- /dev/null +++ b/tests/ui/unused_peekable.stderr @@ -0,0 +1,67 @@ +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:14:9 + | +LL | let peekable = std::iter::empty::().peekable(); + | ^^^^^^^^ + | + = note: `-D clippy::unused-peekable` implied by `-D warnings` + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:18:9 + | +LL | let new_local = old_local; + | ^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:22:9 + | +LL | let by_mut_ref = &mut by_mut_ref_test; + | ^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:29:9 + | +LL | let peekable_from_fn = returns_peekable(); + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:32:13 + | +LL | let mut peekable_using_iterator_method = std::iter::empty::().peekable(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:37:9 + | +LL | let passed_along_ref = std::iter::empty::().peekable(); + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:42:9 + | +LL | let _by_ref = by_ref_test.by_ref(); + | ^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: `peek` never called on `Peekable` iterator + --> $DIR/unused_peekable.rs:44:13 + | +LL | let mut peekable_in_for_loop = std::iter::empty::().peekable(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing the call to `peekable` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/unwrap.rs b/tests/ui/unwrap.rs index a4a3cd1d3..d9fd402e7 100644 --- a/tests/ui/unwrap.rs +++ b/tests/ui/unwrap.rs @@ -6,8 +6,9 @@ fn unwrap_option() { } fn unwrap_result() { - let res: Result = Ok(0); + let res: Result = Ok(0); let _ = res.unwrap(); + let _ = res.unwrap_err(); } fn main() { diff --git a/tests/ui/unwrap.stderr b/tests/ui/unwrap.stderr index 4f0858005..784227578 100644 --- a/tests/ui/unwrap.stderr +++ b/tests/ui/unwrap.stderr @@ -15,5 +15,13 @@ LL | let _ = res.unwrap(); | = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message -error: aborting due to 2 previous errors +error: used `unwrap_err()` on `a Result` value + --> $DIR/unwrap.rs:11:13 + | +LL | let _ = res.unwrap_err(); + | ^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `Ok` case gracefully, consider using `expect_err()` to provide a better panic message + +error: aborting due to 3 previous errors diff --git a/tests/ui/unwrap_expect_used.rs b/tests/ui/unwrap_expect_used.rs index 0d4a0504a..9f27fef82 100644 --- a/tests/ui/unwrap_expect_used.rs +++ b/tests/ui/unwrap_expect_used.rs @@ -1,10 +1,35 @@ #![warn(clippy::unwrap_used, clippy::expect_used)] +trait OptionExt { + type Item; + + fn unwrap_err(self) -> Self::Item; + + fn expect_err(self, msg: &str) -> Self::Item; +} + +impl OptionExt for Option { + type Item = T; + fn unwrap_err(self) -> T { + panic!(); + } + + fn expect_err(self, msg: &str) -> T { + panic!(); + } +} + fn main() { Some(3).unwrap(); Some(3).expect("Hello world!"); + // Don't trigger on unwrap_err on an option + Some(3).unwrap_err(); + Some(3).expect_err("Hellow none!"); + let a: Result = Ok(3); a.unwrap(); a.expect("Hello world!"); + a.unwrap_err(); + a.expect_err("Hello error!"); } diff --git a/tests/ui/unwrap_expect_used.stderr b/tests/ui/unwrap_expect_used.stderr index f54bfd617..1a19459b2 100644 --- a/tests/ui/unwrap_expect_used.stderr +++ b/tests/ui/unwrap_expect_used.stderr @@ -1,5 +1,5 @@ error: used `unwrap()` on `an Option` value - --> $DIR/unwrap_expect_used.rs:4:5 + --> $DIR/unwrap_expect_used.rs:23:5 | LL | Some(3).unwrap(); | ^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | Some(3).unwrap(); = help: if this value is `None`, it will panic error: used `expect()` on `an Option` value - --> $DIR/unwrap_expect_used.rs:5:5 + --> $DIR/unwrap_expect_used.rs:24:5 | LL | Some(3).expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | Some(3).expect("Hello world!"); = help: if this value is `None`, it will panic error: used `unwrap()` on `a Result` value - --> $DIR/unwrap_expect_used.rs:8:5 + --> $DIR/unwrap_expect_used.rs:31:5 | LL | a.unwrap(); | ^^^^^^^^^^ @@ -25,12 +25,28 @@ LL | a.unwrap(); = help: if this value is an `Err`, it will panic error: used `expect()` on `a Result` value - --> $DIR/unwrap_expect_used.rs:9:5 + --> $DIR/unwrap_expect_used.rs:32:5 | LL | a.expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: if this value is an `Err`, it will panic -error: aborting due to 4 previous errors +error: used `unwrap_err()` on `a Result` value + --> $DIR/unwrap_expect_used.rs:33:5 + | +LL | a.unwrap_err(); + | ^^^^^^^^^^^^^^ + | + = help: if this value is an `Ok`, it will panic + +error: used `expect_err()` on `a Result` value + --> $DIR/unwrap_expect_used.rs:34:5 + | +LL | a.expect_err("Hello error!"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if this value is an `Ok`, it will panic + +error: aborting due to 6 previous errors diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs index 39f54c27b..4acf5b5fa 100644 --- a/tests/ui/useless_conversion_try.rs +++ b/tests/ui/useless_conversion_try.rs @@ -29,10 +29,10 @@ fn main() { let _ = String::try_from("foo".to_string()).unwrap(); let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); let _: String = format!("Hello {}", "world").try_into().unwrap(); - let _: String = "".to_owned().try_into().unwrap(); + let _: String = String::new().try_into().unwrap(); let _: String = match String::from("_").try_into() { Ok(a) => a, - Err(_) => "".into(), + Err(_) => String::new(), }; // FIXME this is a false negative #[allow(clippy::cmp_owned)] diff --git a/tests/ui/useless_conversion_try.stderr b/tests/ui/useless_conversion_try.stderr index b691c13f7..12e74d614 100644 --- a/tests/ui/useless_conversion_try.stderr +++ b/tests/ui/useless_conversion_try.stderr @@ -62,7 +62,7 @@ LL | let _: String = format!("Hello {}", "world").try_into().unwrap(); error: useless conversion to the same type: `std::string::String` --> $DIR/useless_conversion_try.rs:32:21 | -LL | let _: String = "".to_owned().try_into().unwrap(); +LL | let _: String = String::new().try_into().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider removing `.try_into()` diff --git a/tests/ui/vec_resize_to_zero.rs b/tests/ui/vec_resize_to_zero.rs index 7ed27439e..a8307e741 100644 --- a/tests/ui/vec_resize_to_zero.rs +++ b/tests/ui/vec_resize_to_zero.rs @@ -1,15 +1,19 @@ #![warn(clippy::vec_resize_to_zero)] fn main() { + let mut v = vec![1, 2, 3, 4, 5]; + // applicable here - vec![1, 2, 3, 4, 5].resize(0, 5); + v.resize(0, 5); // not applicable - vec![1, 2, 3, 4, 5].resize(2, 5); + v.resize(2, 5); + + let mut v = vec!["foo", "bar", "baz"]; // applicable here, but only implemented for integer literals for now - vec!["foo", "bar", "baz"].resize(0, "bar"); + v.resize(0, "bar"); // not applicable - vec!["foo", "bar", "baz"].resize(2, "bar") + v.resize(2, "bar") } diff --git a/tests/ui/vec_resize_to_zero.stderr b/tests/ui/vec_resize_to_zero.stderr index feb846298..7428cf62d 100644 --- a/tests/ui/vec_resize_to_zero.stderr +++ b/tests/ui/vec_resize_to_zero.stderr @@ -1,10 +1,10 @@ error: emptying a vector with `resize` - --> $DIR/vec_resize_to_zero.rs:5:5 + --> $DIR/vec_resize_to_zero.rs:7:5 | -LL | vec![1, 2, 3, 4, 5].resize(0, 5); - | ^^^^^^^^^^^^^^^^^^^^------------ - | | - | help: ...or you can empty the vector with: `clear()` +LL | v.resize(0, 5); + | ^^------------ + | | + | help: ...or you can empty the vector with: `clear()` | = note: `-D clippy::vec-resize-to-zero` implied by `-D warnings` = help: the arguments may be inverted... diff --git a/tests/ui/verbose_file_reads.rs b/tests/ui/verbose_file_reads.rs index e0065e05a..df267e987 100644 --- a/tests/ui/verbose_file_reads.rs +++ b/tests/ui/verbose_file_reads.rs @@ -18,7 +18,7 @@ fn main() -> std::io::Result<()> { s.read_to_end(); s.read_to_string(); // Should catch this - let mut f = File::open(&path)?; + let mut f = File::open(path)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; // ...and this diff --git a/tests/workspace.rs b/tests/workspace.rs index e13efb3e0..95325e060 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -20,8 +20,8 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .current_dir(&cwd) .env("CARGO_TARGET_DIR", &target_dir) .arg("clean") - .args(&["-p", "subcrate"]) - .args(&["-p", "path_dep"]) + .args(["-p", "subcrate"]) + .args(["-p", "path_dep"]) .output() .unwrap(); @@ -32,11 +32,11 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--no-deps") .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir - .args(&["--cfg", r#"feature="primary_package_test""#]) + .args(["--cfg", r#"feature="primary_package_test""#]) .output() .unwrap(); println!("status: {}", output.status); @@ -52,10 +52,10 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir - .args(&["--cfg", r#"feature="primary_package_test""#]) + .args(["--cfg", r#"feature="primary_package_test""#]) .output() .unwrap(); println!("status: {}", output.status); @@ -79,7 +79,7 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { .env("CARGO_INCREMENTAL", "0") .env("CARGO_TARGET_DIR", &target_dir) .arg("clippy") - .args(&["-p", "subcrate"]) + .args(["-p", "subcrate"]) .arg("--") .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir .output()