From 7be6e2178e89dad8b619742c566fdee948414acc Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 27 Feb 2024 15:25:18 +0100 Subject: [PATCH] Merge commit '10136170fe9ed01e46aeb4f4479175b79eb0e3c7' into clippy-subtree-update --- .github/driver.sh | 4 +- CHANGELOG.md | 5 + Cargo.toml | 6 +- book/src/configuration.md | 50 +- book/src/development/emitting_lints.md | 10 +- book/src/development/writing_tests.md | 6 +- book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 4 +- clippy_config/src/lib.rs | 2 +- clippy_dev/Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- clippy_lints/src/asm_syntax.rs | 37 +- clippy_lints/src/attrs.rs | 223 ++++++-- clippy_lints/src/blocks_in_conditions.rs | 17 +- clippy_lints/src/booleans.rs | 110 ++++ clippy_lints/src/box_default.rs | 71 ++- .../src/casts/cast_possible_truncation.rs | 3 +- clippy_lints/src/casts/cast_sign_loss.rs | 314 ++++++++--- clippy_lints/src/casts/ref_as_ptr.rs | 12 +- clippy_lints/src/casts/unnecessary_cast.rs | 5 +- clippy_lints/src/collection_is_never_read.rs | 8 +- clippy_lints/src/declared_lints.rs | 5 + clippy_lints/src/dereference.rs | 8 +- clippy_lints/src/disallowed_macros.rs | 84 +-- clippy_lints/src/doc/mod.rs | 61 ++- clippy_lints/src/drop_forget_ref.rs | 5 +- clippy_lints/src/format_args.rs | 503 +++++++++--------- clippy_lints/src/format_impl.rs | 203 +++---- clippy_lints/src/implied_bounds_in_impls.rs | 250 +++++---- clippy_lints/src/incompatible_msrv.rs | 18 +- clippy_lints/src/index_refutable_slice.rs | 4 +- clippy_lints/src/indexing_slicing.rs | 1 + clippy_lints/src/item_name_repetitions.rs | 1 - .../src/iter_not_returning_iterator.rs | 5 +- clippy_lints/src/lib.rs | 6 +- clippy_lints/src/loops/infinite_loop.rs | 11 +- clippy_lints/src/manual_is_ascii_check.rs | 2 +- clippy_lints/src/matches/match_same_arms.rs | 66 ++- clippy_lints/src/matches/needless_match.rs | 62 ++- clippy_lints/src/matches/redundant_guards.rs | 12 +- .../matches/significant_drop_in_scrutinee.rs | 7 +- clippy_lints/src/mem_replace.rs | 6 +- ...se_sensitive_file_extension_comparisons.rs | 1 + clippy_lints/src/methods/clone_on_copy.rs | 7 +- clippy_lints/src/methods/filter_map.rs | 2 +- clippy_lints/src/methods/mod.rs | 54 +- clippy_lints/src/methods/needless_collect.rs | 184 ++++--- .../src/methods/read_line_without_trim.rs | 95 +++- .../unnecessary_fallible_conversions.rs | 139 +++-- .../src/methods/unnecessary_get_then_check.rs | 85 +++ .../src/methods/unnecessary_to_owned.rs | 98 +++- clippy_lints/src/min_ident_chars.rs | 46 +- clippy_lints/src/multiple_bound_locations.rs | 84 +++ clippy_lints/src/needless_bool.rs | 7 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 5 +- clippy_lints/src/new_without_default.rs | 4 - clippy_lints/src/no_effect.rs | 13 +- clippy_lints/src/non_canonical_impls.rs | 4 +- clippy_lints/src/non_expressive_names.rs | 144 ++--- clippy_lints/src/only_used_in_recursion.rs | 16 +- .../src/operators/double_comparison.rs | 1 - clippy_lints/src/operators/op_ref.rs | 2 +- clippy_lints/src/pub_underscore_fields.rs | 10 +- clippy_lints/src/question_mark.rs | 14 +- clippy_lints/src/redundant_closure_call.rs | 10 +- clippy_lints/src/same_name_method.rs | 36 +- clippy_lints/src/single_call_fn.rs | 130 ++--- .../src/slow_vector_initialization.rs | 2 +- clippy_lints/src/strlen_on_c_strings.rs | 8 +- clippy_lints/src/to_string_trait_impl.rs | 3 + clippy_lints/src/tuple_array_conversions.rs | 5 +- .../src/undocumented_unsafe_blocks.rs | 200 ++++--- clippy_lints/src/unit_types/let_unit_value.rs | 3 +- clippy_lints/src/unused_unit.rs | 1 + .../internal_lints/metadata_collector.rs | 2 - clippy_lints/src/vec.rs | 351 ++++++------ clippy_utils/Cargo.toml | 2 +- clippy_utils/src/ast_utils.rs | 8 +- clippy_utils/src/diagnostics.rs | 10 +- clippy_utils/src/hir_utils.rs | 5 +- clippy_utils/src/lib.rs | 13 +- clippy_utils/src/paths.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 6 +- declare_clippy_lint/Cargo.toml | 2 +- lintcheck/src/main.rs | 6 +- rust-toolchain | 2 +- src/driver.rs | 2 + tests/compile-test.rs | 39 +- .../multiple_config_files/warn/Cargo.stderr | 2 +- .../check_clippy_version_attribute.stderr | 10 +- tests/ui-internal/check_formulation.stderr | 4 +- .../collapsible_span_lint_calls.stderr | 12 +- .../default_deprecation_reason.stderr | 4 +- tests/ui-internal/default_lint.stderr | 4 +- tests/ui-internal/disallow_span_lint.stderr | 4 +- .../interning_defined_symbol.stderr | 10 +- .../ui-internal/invalid_msrv_attr_impl.stderr | 6 +- tests/ui-internal/invalid_paths.stderr | 6 +- .../ui-internal/lint_without_lint_pass.stderr | 4 +- tests/ui-internal/outer_expn_data.stderr | 4 +- tests/ui-internal/unnecessary_def_path.stderr | 32 +- ...unnecessary_def_path_hardcoded_path.stderr | 6 +- .../ui-internal/unnecessary_symbol_str.stderr | 12 +- .../absolute_paths.allow_crates.stderr | 8 +- .../absolute_paths.disallow_crates.stderr | 22 +- .../uninlined_format_args.stderr | 12 +- .../arithmetic_side_effects_allowed.stderr | 18 +- .../array_size_threshold.stderr | 6 +- .../await_holding_invalid_type.stderr | 6 +- tests/ui-toml/bad_toml/conf_bad_toml.stderr | 2 +- .../bad_toml_type/conf_bad_type.stderr | 2 +- .../conf_deprecated_key.stderr | 6 +- tests/ui-toml/dbg_macro/dbg_macro.stderr | 18 +- .../decimal_literal_representation.stderr | 2 +- .../auxiliary/proc_macros.rs | 29 + tests/ui-toml/disallowed_macros/clippy.toml | 1 + .../disallowed_macros/disallowed_macros.rs | 6 + .../disallowed_macros.stderr | 38 +- .../disallowed_names.stderr | 4 +- .../disallowed_names.stderr | 2 +- .../disallowed_script_idents.stderr | 2 +- .../doc_markdown.stderr | 2 +- .../doc_markdown.stderr | 6 +- .../duplicated_keys/duplicated_keys.stderr | 2 +- .../duplicated_keys.stderr | 4 +- .../duplicated_keys.stderr | 4 +- .../enum_variant_size.stderr | 2 +- .../excessive_nesting.stderr | 74 +-- tests/ui-toml/expect_used/expect_used.stderr | 4 +- .../explicit_iter_loop.stderr | 4 +- .../fn_params_excessive_bools/test.stderr | 2 +- tests/ui-toml/functions_maxlines/test.stderr | 8 +- .../ifs_same_cond/ifs_same_cond.stderr | 4 +- .../impl_trait_in_params.stderr | 2 +- .../invalid_min_rust_version.stderr | 2 +- .../threshold5/item_name_repetitions.stderr | 4 +- .../large_futures/large_futures.stderr | 2 +- .../large_include_file.stderr | 4 +- .../large_stack_frames.stderr | 2 +- .../large_types_passed_by_value.stderr | 2 +- .../lint_decimal_readability/test.stderr | 4 +- .../manual_let_else/manual_let_else.stderr | 2 +- .../index_refutable_slice.stderr | 4 +- .../min_ident_chars/auxiliary/extern_types.rs | 6 + .../min_ident_chars/min_ident_chars.rs | 5 +- .../min_ident_chars/min_ident_chars.stderr | 28 +- .../min_rust_version/min_rust_version.stderr | 2 +- ...conf_missing_enforced_import_rename.stderr | 12 +- .../module_inception/module_inception.stderr | 4 +- .../modulo_arithmetic.stderr | 8 +- .../conf_nonstandard_macro_braces.stderr | 16 +- tests/ui-toml/print_macro/print_macro.stderr | 4 +- .../private-doc-errors/doc_lints.stderr | 20 +- .../pub_crate_missing_doc.stderr | 14 +- ...ub_underscore_fields.all_pub_fields.stderr | 14 +- .../pub_underscore_fields.exported.stderr | 2 +- .../pub_underscore_fields.rs | 6 + .../result_large_err/result_large_err.stderr | 2 +- tests/ui-toml/semicolon_block/both.stderr | 8 +- .../semicolon_inside_block.stderr | 2 +- .../semicolon_outside_block.stderr | 6 +- .../test.stderr | 28 +- .../struct_excessive_bools/test.stderr | 2 +- .../suppress_lint_in_const/test.stderr | 12 +- .../conf_french_disallowed_name.stderr | 14 +- .../conf_disallowed_methods.stderr | 28 +- .../conf_disallowed_types.stderr | 42 +- tests/ui-toml/toml_trivially_copy/test.stderr | 4 +- .../toml_unknown_key/conf_unknown_key.stderr | 6 +- .../too_large_for_stack/boxed_local.stderr | 2 +- .../too_large_for_stack/useless_vec.stderr | 2 +- .../too_many_arguments.stderr | 2 +- .../type_complexity/type_complexity.stderr | 2 +- .../type_repetition_in_bounds/main.stderr | 2 +- .../undocumented_unsafe_blocks.default.stderr | 74 +-- ...undocumented_unsafe_blocks.disabled.stderr | 94 ++-- .../unnecessary_box_returns.stderr | 2 +- tests/ui-toml/unwrap_used/unwrap_used.stderr | 56 +- .../upper_case_acronyms.stderr | 26 +- tests/ui-toml/vec_box_sized/test.stderr | 6 +- .../verbose_bit_mask/verbose_bit_mask.stderr | 2 +- .../wildcard_imports/wildcard_imports.stderr | 6 +- .../wildcard_imports.stderr | 2 +- tests/ui/absurd-extreme-comparisons.stderr | 36 +- tests/ui/allow_attributes.stderr | 4 +- .../ui/allow_attributes_without_reason.stderr | 10 +- tests/ui/almost_complete_range.stderr | 54 +- tests/ui/approx_const.rs | 1 - tests/ui/approx_const.stderr | 46 +- tests/ui/arc_with_non_send_sync.stderr | 6 +- tests/ui/arithmetic_side_effects.stderr | 238 ++++----- tests/ui/as_conversions.stderr | 6 +- tests/ui/as_ptr_cast_mut.stderr | 4 +- tests/ui/as_underscore.stderr | 4 +- tests/ui/asm_syntax_not_x86.rs | 24 + ...ntax.stderr => asm_syntax_x86.i686.stderr} | 36 +- tests/ui/{asm_syntax.rs => asm_syntax_x86.rs} | 23 +- tests/ui/asm_syntax_x86.x86_64.stderr | 70 +++ tests/ui/assertions_on_constants.stderr | 20 +- tests/ui/assertions_on_result_states.stderr | 14 +- tests/ui/assign_ops.stderr | 22 +- tests/ui/assign_ops2.stderr | 20 +- tests/ui/async_yields_async.stderr | 12 +- tests/ui/attrs.stderr | 6 +- tests/ui/auxiliary/proc_macro_attr.rs | 20 + tests/ui/await_holding_lock.stderr | 52 +- tests/ui/await_holding_refcell_ref.stderr | 24 +- tests/ui/bind_instead_of_map.stderr | 8 +- tests/ui/bind_instead_of_map_multipart.stderr | 12 +- tests/ui/bit_masks.stderr | 34 +- .../blanket_clippy_restriction_lints.stderr | 6 +- tests/ui/blocks_in_conditions.fixed | 13 + tests/ui/blocks_in_conditions.rs | 13 + tests/ui/blocks_in_conditions.stderr | 8 +- tests/ui/blocks_in_conditions_closure.stderr | 6 +- tests/ui/bool_assert_comparison.stderr | 66 +-- tests/ui/bool_comparison.fixed | 2 +- tests/ui/bool_comparison.rs | 2 +- tests/ui/bool_comparison.stderr | 50 +- tests/ui/bool_to_int_with_if.stderr | 18 +- tests/ui/borrow_as_ptr.stderr | 4 +- tests/ui/borrow_as_ptr_no_std.stderr | 4 +- tests/ui/borrow_box.stderr | 22 +- tests/ui/borrow_deref_ref.stderr | 6 +- tests/ui/borrow_deref_ref_unfixable.stderr | 2 +- .../enums.stderr | 20 +- .../others.stderr | 30 +- .../traits.stderr | 32 +- tests/ui/box_collection.stderr | 18 +- tests/ui/box_default.fixed | 18 +- tests/ui/box_default.rs | 16 + tests/ui/box_default.stderr | 48 +- tests/ui/boxed_local.stderr | 8 +- .../shared_at_bottom.stderr | 20 +- .../shared_at_top.stderr | 20 +- .../shared_at_top_and_bottom.stderr | 22 +- .../valid_if_blocks.stderr | 22 +- tests/ui/builtin_type_shadow.stderr | 4 +- tests/ui/bytecount.stderr | 8 +- tests/ui/bytes_count_to_len.stderr | 8 +- tests/ui/bytes_nth.stderr | 6 +- ...sensitive_file_extension_comparisons.fixed | 5 + ...se_sensitive_file_extension_comparisons.rs | 5 + ...ensitive_file_extension_comparisons.stderr | 12 +- tests/ui/cast.rs | 67 ++- tests/ui/cast.stderr | 270 +++++++--- tests/ui/cast_abs_to_unsigned.stderr | 36 +- tests/ui/cast_alignment.stderr | 8 +- tests/ui/cast_enum_constructor.stderr | 4 +- tests/ui/cast_lossless_bool.stderr | 28 +- tests/ui/cast_lossless_float.stderr | 22 +- tests/ui/cast_lossless_integer.stderr | 42 +- tests/ui/cast_nan_to_int.stderr | 12 +- tests/ui/cast_raw_slice_pointer_cast.stderr | 14 +- tests/ui/cast_size.32bit.stderr | 38 +- tests/ui/cast_size.64bit.stderr | 36 +- tests/ui/cast_slice_different_sizes.stderr | 28 +- tests/ui/cfg_attr_cargo_clippy.fixed | 13 + tests/ui/cfg_attr_cargo_clippy.rs | 13 + tests/ui/cfg_attr_cargo_clippy.stderr | 47 ++ tests/ui/cfg_attr_rustfmt.stderr | 6 +- tests/ui/cfg_features.stderr | 16 +- tests/ui/char_lit_as_u8.stderr | 2 +- tests/ui/char_lit_as_u8_suggestions.stderr | 8 +- tests/ui/checked_conversions.stderr | 34 +- .../complex_conditionals.stderr | 44 +- .../complex_conditionals_nested.stderr | 8 +- .../checked_unwrap/simple_conditionals.stderr | 54 +- tests/ui/clear_with_drain.stderr | 42 +- tests/ui/clone_on_copy.stderr | 18 +- tests/ui/cloned_instead_of_copied.stderr | 16 +- tests/ui/cmp_null.stderr | 4 +- .../ui/cmp_owned/asymmetric_partial_eq.stderr | 12 +- tests/ui/cmp_owned/comparison_flip.stderr | 4 +- tests/ui/cmp_owned/with_suggestion.fixed | 2 +- tests/ui/cmp_owned/with_suggestion.stderr | 12 +- tests/ui/cmp_owned/without_suggestion.stderr | 6 +- tests/ui/cognitive_complexity.stderr | 40 +- .../ui/cognitive_complexity_attr_used.stderr | 2 +- tests/ui/collapsible_else_if.stderr | 16 +- tests/ui/collapsible_if.stderr | 18 +- tests/ui/collapsible_match.stderr | 48 +- tests/ui/collapsible_match2.stderr | 20 +- tests/ui/collapsible_str_replace.stderr | 28 +- tests/ui/collection_is_never_read.stderr | 40 +- tests/ui/comparison_chain.stderr | 14 +- tests/ui/comparison_to_empty.stderr | 18 +- tests/ui/const_comparisons.stderr | 62 +-- tests/ui/copy_iterator.stderr | 2 +- tests/ui/crashes/ice-10148.stderr | 2 +- tests/ui/crashes/ice-10645.stderr | 4 +- tests/ui/crashes/ice-10912.stderr | 4 +- tests/ui/crashes/ice-11065.rs | 1 - tests/ui/crashes/ice-11422.stderr | 2 +- tests/ui/crashes/ice-11803.stderr | 4 +- tests/ui/crashes/ice-12253.rs | 5 + tests/ui/crashes/ice-2774.stderr | 2 +- tests/ui/crashes/ice-360.stderr | 6 +- tests/ui/crashes/ice-3717.stderr | 4 +- tests/ui/crashes/ice-3891.stderr | 2 +- tests/ui/crashes/ice-3969.stderr | 10 +- tests/ui/crashes/ice-5497.stderr | 2 +- tests/ui/crashes/ice-5835.stderr | 2 +- tests/ui/crashes/ice-5872.stderr | 2 +- tests/ui/crashes/ice-6250.stderr | 4 +- tests/ui/crashes/ice-6251.stderr | 8 +- tests/ui/crashes/ice-6252.stderr | 6 +- tests/ui/crashes/ice-6255.rs | 2 +- tests/ui/crashes/ice-6255.stderr | 2 +- tests/ui/crashes/ice-6256.stderr | 2 +- tests/ui/crashes/ice-7169.stderr | 2 +- tests/ui/crashes/ice-7868.stderr | 2 +- tests/ui/crashes/ice-7869.stderr | 2 +- tests/ui/crashes/ice-8250.stderr | 2 +- tests/ui/crashes/ice-8850.stderr | 6 +- tests/ui/crashes/ice-9041.stderr | 2 +- tests/ui/crashes/ice-9405.stderr | 2 +- tests/ui/crashes/ice-9445.stderr | 2 +- tests/ui/crashes/ice-9463.stderr | 8 +- tests/ui/crashes/ice-96721.stderr | 2 +- .../needless_lifetimes_impl_trait.stderr | 4 +- ...needless_pass_by_value-w-late-bound.stderr | 4 +- tests/ui/crate_in_macro_def.stderr | 2 +- .../ui/crate_level_checks/no_std_swap.stderr | 2 +- .../std_main_recursion.stderr | 2 +- tests/ui/create_dir.stderr | 4 +- tests/ui/dbg_macro/dbg_macro.stderr | 38 +- tests/ui/debug_assert_with_mut_call.stderr | 56 +- .../ui/decimal_literal_representation.stderr | 14 +- .../enums.stderr | 24 +- .../declare_interior_mutable_const/others.rs | 5 +- .../others.stderr | 10 +- .../declare_interior_mutable_const/traits.rs | 3 +- .../traits.stderr | 22 +- tests/ui/def_id_nocore.stderr | 2 +- .../default_constructed_unit_structs.stderr | 12 +- tests/ui/default_instead_of_iter_empty.stderr | 6 +- ...efault_instead_of_iter_empty_no_std.stderr | 4 +- tests/ui/default_numeric_fallback_f64.stderr | 46 +- tests/ui/default_numeric_fallback_i32.stderr | 56 +- tests/ui/default_trait_access.stderr | 18 +- tests/ui/default_union_representation.stderr | 8 +- tests/ui/deprecated.stderr | 32 +- tests/ui/deprecated_old.stderr | 6 +- tests/ui/deref_addrof.stderr | 20 +- tests/ui/deref_addrof_double_trigger.stderr | 6 +- tests/ui/deref_by_slicing.stderr | 18 +- tests/ui/derivable_impls.stderr | 16 +- tests/ui/derive.stderr | 20 +- tests/ui/derive_ord_xor_partial_ord.stderr | 16 +- tests/ui/derive_partial_eq_without_eq.stderr | 22 +- tests/ui/derived_hash_with_manual_eq.stderr | 8 +- tests/ui/disallowed_names.stderr | 28 +- tests/ui/disallowed_script_idents.stderr | 6 +- tests/ui/diverging_sub_expression.stderr | 22 +- tests/ui/doc/doc-fixable.stderr | 62 +-- tests/ui/doc/unbalanced_ticks.stderr | 16 +- tests/ui/doc_errors.stderr | 14 +- tests/ui/doc_link_with_quotes.stderr | 4 +- tests/ui/doc_unsafe.stderr | 12 +- tests/ui/double_comparison.stderr | 16 +- tests/ui/double_must_use.stderr | 8 +- tests/ui/double_neg.stderr | 2 +- tests/ui/double_parens.stderr | 12 +- tests/ui/drain_collect.stderr | 22 +- tests/ui/drop_non_drop.stderr | 8 +- tests/ui/duplicate_underscore_argument.stderr | 2 +- tests/ui/duration_subsec.stderr | 10 +- tests/ui/eager_transmute.stderr | 34 +- tests/ui/else_if_without_else.stderr | 4 +- tests/ui/empty_docs.rs | 67 +++ tests/ui/empty_docs.stderr | 77 +++ tests/ui/empty_drop.stderr | 4 +- tests/ui/empty_enum.stderr | 2 +- .../empty_enum_variants_with_brackets.stderr | 8 +- tests/ui/empty_line_after_doc_comments.stderr | 6 +- .../empty_line_after_outer_attribute.stderr | 12 +- tests/ui/empty_loop.stderr | 6 +- tests/ui/empty_loop_no_std.stderr | 4 +- tests/ui/empty_structs_with_brackets.stderr | 4 +- tests/ui/endian_bytes.stderr | 172 +++--- tests/ui/entry.stderr | 20 +- tests/ui/entry_btree.stderr | 2 +- tests/ui/entry_with_else.stderr | 14 +- tests/ui/enum_clike_unportable_variant.stderr | 18 +- tests/ui/enum_glob_use.stderr | 6 +- tests/ui/enum_variants.stderr | 32 +- tests/ui/eprint_with_newline.stderr | 18 +- tests/ui/eq_op.stderr | 58 +- tests/ui/eq_op_macros.stderr | 24 +- tests/ui/equatable_if_let.stderr | 28 +- tests/ui/erasing_op.stderr | 10 +- tests/ui/err_expect.stderr | 4 +- tests/ui/error_impl_error.stderr | 14 +- tests/ui/eta.stderr | 62 +-- tests/ui/excessive_precision.stderr | 30 +- tests/ui/exhaustive_items.stderr | 10 +- tests/ui/exit1.stderr | 2 +- tests/ui/exit2.stderr | 2 +- tests/ui/expect.stderr | 6 +- tests/ui/expect_fun_call.stderr | 30 +- tests/ui/expect_tool_lint_rfc_2383.stderr | 12 +- tests/ui/explicit_auto_deref.stderr | 90 ++-- tests/ui/explicit_counter_loop.stderr | 18 +- tests/ui/explicit_deref_methods.stderr | 24 +- tests/ui/explicit_into_iter_loop.stderr | 12 +- tests/ui/explicit_iter_loop.stderr | 38 +- tests/ui/explicit_write.stderr | 26 +- tests/ui/extend_with_drain.stderr | 8 +- tests/ui/extra_unused_lifetimes.stderr | 12 +- tests/ui/extra_unused_type_parameters.stderr | 16 +- ...ra_unused_type_parameters_unfixable.stderr | 6 +- tests/ui/fallible_impl_from.stderr | 18 +- tests/ui/field_reassign_with_default.stderr | 44 +- tests/ui/filetype_is_file.stderr | 6 +- tests/ui/filter_map_bool_then.stderr | 20 +- tests/ui/filter_map_identity.stderr | 8 +- tests/ui/filter_map_next.stderr | 2 +- tests/ui/filter_map_next_fixable.stderr | 4 +- tests/ui/flat_map_identity.stderr | 6 +- tests/ui/flat_map_option.stderr | 4 +- tests/ui/float_arithmetic.stderr | 34 +- tests/ui/float_cmp.stderr | 12 +- tests/ui/float_cmp_const.stderr | 16 +- tests/ui/float_equality_without_abs.stderr | 22 +- tests/ui/floating_point_abs.stderr | 16 +- tests/ui/floating_point_exp.stderr | 10 +- tests/ui/floating_point_hypot.stderr | 6 +- tests/ui/floating_point_log.stderr | 58 +- tests/ui/floating_point_logbase.stderr | 10 +- tests/ui/floating_point_mul_add.stderr | 26 +- tests/ui/floating_point_powf.stderr | 62 +-- tests/ui/floating_point_powi.stderr | 28 +- tests/ui/floating_point_rad.stderr | 16 +- tests/ui/fn_address_comparisons.stderr | 4 +- tests/ui/fn_params_excessive_bools.stderr | 14 +- tests/ui/fn_to_numeric_cast.32bit.stderr | 46 +- tests/ui/fn_to_numeric_cast.64bit.stderr | 46 +- tests/ui/fn_to_numeric_cast_any.stderr | 34 +- tests/ui/for_kv_map.stderr | 10 +- tests/ui/forget_non_drop.stderr | 8 +- tests/ui/format.stderr | 30 +- tests/ui/format_args.stderr | 50 +- tests/ui/format_args_unfixable.stderr | 36 +- tests/ui/format_collect.stderr | 18 +- tests/ui/format_push_string.stderr | 10 +- tests/ui/formatting.stderr | 12 +- tests/ui/four_forward_slashes.stderr | 10 +- .../ui/four_forward_slashes_first_line.stderr | 2 +- tests/ui/from_iter_instead_of_collect.stderr | 30 +- tests/ui/from_over_into.stderr | 14 +- tests/ui/from_over_into_unfixable.stderr | 8 +- tests/ui/from_raw_with_void_ptr.stderr | 20 +- tests/ui/from_str_radix_10.stderr | 16 +- tests/ui/functions.stderr | 32 +- tests/ui/functions_maxlines.stderr | 2 +- tests/ui/future_not_send.stderr | 36 +- tests/ui/get_first.stderr | 10 +- tests/ui/get_last_with_len.stderr | 12 +- tests/ui/get_unwrap.stderr | 62 +-- tests/ui/identity_op.stderr | 104 ++-- tests/ui/if_let_mutex.stderr | 6 +- tests/ui/if_not_else.stderr | 4 +- tests/ui/if_same_then_else.stderr | 24 +- tests/ui/if_same_then_else2.stderr | 24 +- tests/ui/if_then_some_else_none.stderr | 10 +- tests/ui/ifs_same_cond.stderr | 16 +- tests/ui/ignored_unit_patterns.stderr | 18 +- tests/ui/impl.stderr | 16 +- ...impl_hash_with_borrow_str_and_bytes.stderr | 6 +- tests/ui/impl_trait_in_params.stderr | 8 +- tests/ui/implicit_clone.stderr | 22 +- tests/ui/implicit_hasher.stderr | 12 +- tests/ui/implicit_return.stderr | 32 +- tests/ui/implicit_saturating_add.stderr | 48 +- tests/ui/implicit_saturating_sub.stderr | 46 +- tests/ui/implied_bounds_in_impls.fixed | 52 ++ tests/ui/implied_bounds_in_impls.rs | 52 ++ tests/ui/implied_bounds_in_impls.stderr | 118 +++- tests/ui/incompatible_msrv.rs | 13 + tests/ui/incompatible_msrv.stderr | 6 +- tests/ui/inconsistent_digit_grouping.stderr | 22 +- .../ui/inconsistent_struct_constructor.stderr | 4 +- .../if_let_slice_binding.stderr | 22 +- .../slice_indexing_in_macro.stderr | 4 +- tests/ui/indexing_slicing_index.stderr | 32 +- tests/ui/indexing_slicing_slice.stderr | 32 +- tests/ui/ineffective_open_options.stderr | 4 +- tests/ui/inefficient_to_string.stderr | 14 +- .../ui/infallible_destructuring_match.stderr | 8 +- tests/ui/infinite_iter.stderr | 32 +- tests/ui/infinite_loop.stderr | 22 +- tests/ui/infinite_loops.rs | 27 + tests/ui/infinite_loops.stderr | 34 +- tests/ui/inherent_to_string.stderr | 4 +- tests/ui/inline_fn_without_body.stderr | 6 +- tests/ui/inspect_for_each.stderr | 2 +- tests/ui/int_plus_one.stderr | 8 +- tests/ui/integer_division.stderr | 6 +- tests/ui/into_iter_on_ref.stderr | 54 +- tests/ui/into_iter_without_iter.stderr | 12 +- tests/ui/invalid_null_ptr_usage.stderr | 50 +- tests/ui/invalid_upcast_comparisons.stderr | 54 +- tests/ui/is_digit_ascii_radix.stderr | 6 +- tests/ui/issue-3145.stderr | 2 +- tests/ui/issue-7447.stderr | 4 +- tests/ui/issue_2356.stderr | 4 +- tests/ui/issue_4266.stderr | 6 +- tests/ui/items_after_statement.stderr | 6 +- .../in_submodule.stderr | 2 +- .../root_module.stderr | 2 +- tests/ui/iter_cloned_collect.stderr | 10 +- tests/ui/iter_count.stderr | 50 +- tests/ui/iter_filter_is_ok.stderr | 24 +- tests/ui/iter_filter_is_some.fixed | 3 +- tests/ui/iter_filter_is_some.rs | 3 +- tests/ui/iter_filter_is_some.stderr | 20 +- tests/ui/iter_kv_map.stderr | 76 +-- tests/ui/iter_next_loop.stderr | 2 +- tests/ui/iter_next_slice.stderr | 8 +- tests/ui/iter_not_returning_iterator.stderr | 6 +- tests/ui/iter_nth.stderr | 14 +- tests/ui/iter_nth_zero.stderr | 6 +- tests/ui/iter_on_empty_collections.stderr | 12 +- tests/ui/iter_on_single_items.stderr | 12 +- tests/ui/iter_out_of_bounds.stderr | 30 +- tests/ui/iter_over_hash_type.stderr | 26 +- tests/ui/iter_overeager_cloned.stderr | 38 +- tests/ui/iter_skip_next.stderr | 14 +- tests/ui/iter_skip_next_unfixable.stderr | 12 +- tests/ui/iter_skip_zero.stderr | 10 +- tests/ui/iter_with_drain.stderr | 12 +- tests/ui/iter_without_into_iter.stderr | 16 +- tests/ui/iterator_step_by_zero.stderr | 14 +- tests/ui/join_absolute_paths.stderr | 8 +- tests/ui/large_const_arrays.stderr | 18 +- tests/ui/large_digit_groups.stderr | 10 +- tests/ui/large_enum_variant.32bit.stderr | 44 +- tests/ui/large_enum_variant.64bit.stderr | 44 +- tests/ui/large_futures.stderr | 16 +- tests/ui/large_stack_arrays.stderr | 14 +- tests/ui/large_stack_frames.stderr | 6 +- tests/ui/large_types_passed_by_value.stderr | 16 +- tests/ui/len_without_is_empty.stderr | 42 +- tests/ui/len_zero.stderr | 46 +- tests/ui/len_zero_ranges.stderr | 4 +- tests/ui/let_and_return.stderr | 10 +- tests/ui/let_if_seq.stderr | 8 +- tests/ui/let_underscore_future.stderr | 6 +- tests/ui/let_underscore_lock.stderr | 8 +- tests/ui/let_underscore_must_use.stderr | 24 +- tests/ui/let_underscore_untyped.stderr | 20 +- tests/ui/let_unit.stderr | 6 +- tests/ui/let_with_type_underscore.stderr | 20 +- tests/ui/lines_filter_map_ok.stderr | 24 +- tests/ui/linkedlist.stderr | 18 +- tests/ui/literals.stderr | 40 +- tests/ui/lossy_float_literal.stderr | 22 +- tests/ui/macro_use_imports.stderr | 8 +- tests/ui/manual_assert.edition2018.stderr | 18 +- tests/ui/manual_assert.edition2021.stderr | 18 +- tests/ui/manual_async_fn.stderr | 26 +- tests/ui/manual_bits.stderr | 58 +- tests/ui/manual_c_str_literals.stderr | 26 +- tests/ui/manual_clamp.stderr | 70 +-- tests/ui/manual_filter.stderr | 30 +- tests/ui/manual_filter_map.stderr | 76 +-- tests/ui/manual_find.stderr | 4 +- tests/ui/manual_find_fixable.stderr | 24 +- tests/ui/manual_find_map.stderr | 78 +-- tests/ui/manual_flatten.stderr | 36 +- tests/ui/manual_float_methods.stderr | 12 +- tests/ui/manual_hash_one.stderr | 8 +- tests/ui/manual_instant_elapsed.stderr | 4 +- tests/ui/manual_is_ascii_check.stderr | 44 +- tests/ui/manual_is_variant_and.stderr | 16 +- tests/ui/manual_let_else.stderr | 60 +-- tests/ui/manual_let_else_match.stderr | 20 +- tests/ui/manual_let_else_question_mark.stderr | 14 +- tests/ui/manual_main_separator_str.stderr | 8 +- tests/ui/manual_map_option.stderr | 42 +- tests/ui/manual_map_option_2.stderr | 10 +- .../manual_memcpy/with_loop_counters.stderr | 22 +- .../without_loop_counters.stderr | 32 +- tests/ui/manual_next_back.stderr | 4 +- tests/ui/manual_non_exhaustive_enum.stderr | 8 +- tests/ui/manual_non_exhaustive_struct.stderr | 20 +- tests/ui/manual_ok_or.stderr | 10 +- tests/ui/manual_range_patterns.stderr | 38 +- tests/ui/manual_rem_euclid.stderr | 20 +- tests/ui/manual_retain.stderr | 76 +-- tests/ui/manual_saturating_arithmetic.stderr | 48 +- tests/ui/manual_slice_size_calculation.stderr | 14 +- tests/ui/manual_split_once.stderr | 38 +- tests/ui/manual_str_repeat.stderr | 20 +- tests/ui/manual_string_new.stderr | 18 +- tests/ui/manual_strip.stderr | 32 +- tests/ui/manual_try_fold.stderr | 8 +- tests/ui/manual_unwrap_or.stderr | 28 +- tests/ui/manual_while_let_some.stderr | 14 +- tests/ui/many_single_char_names.stderr | 10 +- tests/ui/map_clone.stderr | 30 +- tests/ui/map_collect_result_unit.stderr | 4 +- tests/ui/map_err.stderr | 2 +- tests/ui/map_flatten.stderr | 8 +- tests/ui/map_flatten_fixable.stderr | 18 +- tests/ui/map_identity.stderr | 22 +- tests/ui/map_unwrap_or.stderr | 30 +- tests/ui/map_unwrap_or_fixable.stderr | 4 +- tests/ui/match_as_ref.stderr | 6 +- tests/ui/match_bool.stderr | 18 +- tests/ui/match_expr_like_matches_macro.stderr | 28 +- tests/ui/match_on_vec_items.stderr | 16 +- tests/ui/match_overlapping_arm.stderr | 32 +- tests/ui/match_ref_pats.stderr | 10 +- tests/ui/match_result_ok.stderr | 6 +- tests/ui/match_same_arms.stderr | 32 +- tests/ui/match_same_arms2.rs | 14 + tests/ui/match_same_arms2.stderr | 67 ++- .../ui/match_same_arms_non_exhaustive.stderr | 8 +- tests/ui/match_single_binding.stderr | 48 +- tests/ui/match_single_binding2.stderr | 8 +- tests/ui/match_str_case_mismatch.stderr | 14 +- tests/ui/match_wild_err_arm.stderr | 8 +- .../match_wildcard_for_single_variants.stderr | 20 +- tests/ui/mem_forget.stderr | 8 +- tests/ui/mem_replace.fixed | 8 + tests/ui/mem_replace.rs | 8 + tests/ui/mem_replace.stderr | 48 +- tests/ui/mem_replace_macro.rs | 4 +- tests/ui/mem_replace_macro.stderr | 6 +- tests/ui/mem_replace_no_std.stderr | 14 +- tests/ui/methods.stderr | 4 +- tests/ui/methods_fixable.stderr | 2 +- tests/ui/methods_unfixable.stderr | 4 +- tests/ui/min_ident_chars.rs | 6 + tests/ui/min_ident_chars.stderr | 78 +-- tests/ui/min_max.stderr | 26 +- tests/ui/min_rust_version_attr.stderr | 12 +- tests/ui/min_rust_version_invalid_attr.stderr | 12 +- tests/ui/mismatched_target_os_non_unix.stderr | 8 +- tests/ui/mismatched_target_os_unix.stderr | 34 +- tests/ui/mismatching_type_param_order.stderr | 20 +- tests/ui/misnamed_getters.stderr | 36 +- tests/ui/missing_assert_message.stderr | 32 +- tests/ui/missing_asserts_for_indexing.stderr | 94 ++-- ...sing_asserts_for_indexing_unfixable.stderr | 56 +- .../could_be_const.stderr | 22 +- tests/ui/missing_doc.stderr | 26 +- tests/ui/missing_doc_crate_missing.stderr | 2 +- tests/ui/missing_doc_impl.stderr | 14 +- tests/ui/missing_fields_in_debug.stderr | 16 +- tests/ui/missing_inline.stderr | 12 +- tests/ui/missing_panics_doc.stderr | 48 +- tests/ui/missing_spin_loop.stderr | 12 +- tests/ui/missing_spin_loop_no_std.stderr | 2 +- tests/ui/missing_trait_methods.stderr | 8 +- tests/ui/mistyped_literal_suffix.stderr | 32 +- .../ui/mixed_read_write_in_expression.stderr | 16 +- tests/ui/module_inception.stderr | 8 +- tests/ui/module_name_repetitions.stderr | 10 +- tests/ui/modulo_arithmetic_float.stderr | 20 +- tests/ui/modulo_arithmetic_integral.stderr | 34 +- .../modulo_arithmetic_integral_const.stderr | 34 +- tests/ui/modulo_one.stderr | 16 +- tests/ui/multi_assignments.stderr | 12 +- tests/ui/multiple_bound_locations.rs | 60 +++ tests/ui/multiple_bound_locations.stderr | 59 ++ tests/ui/multiple_unsafe_ops_per_block.stderr | 58 +- tests/ui/must_use_candidates.stderr | 10 +- tests/ui/must_use_unit.stderr | 6 +- tests/ui/mut_from_ref.stderr | 24 +- tests/ui/mut_key.stderr | 34 +- tests/ui/mut_mut.stderr | 18 +- tests/ui/mut_mutex_lock.stderr | 2 +- tests/ui/mut_range_bound.stderr | 14 +- tests/ui/mut_reference.stderr | 6 +- tests/ui/mutex_atomic.stderr | 22 +- tests/ui/needless_arbitrary_self_type.stderr | 12 +- ...dless_arbitrary_self_type_unfixable.stderr | 2 +- tests/ui/needless_bitwise_bool.stderr | 2 +- tests/ui/needless_bool/fixable.stderr | 42 +- tests/ui/needless_bool/simple.stderr | 8 +- tests/ui/needless_bool_assign.stderr | 10 +- tests/ui/needless_borrow.stderr | 54 +- tests/ui/needless_borrow_pat.stderr | 24 +- tests/ui/needless_borrowed_ref.stderr | 34 +- .../needless_borrows_for_generic_args.stderr | 24 +- tests/ui/needless_collect.stderr | 38 +- tests/ui/needless_collect_indirect.stderr | 32 +- tests/ui/needless_continue.stderr | 16 +- tests/ui/needless_doc_main.stderr | 8 +- tests/ui/needless_else.stderr | 2 +- tests/ui/needless_for_each_fixable.stderr | 16 +- tests/ui/needless_for_each_unfixable.stderr | 2 +- tests/ui/needless_if.stderr | 14 +- tests/ui/needless_late_init.stderr | 32 +- tests/ui/needless_lifetimes.stderr | 92 ++-- tests/ui/needless_match.stderr | 26 +- tests/ui/needless_option_as_deref.stderr | 6 +- tests/ui/needless_option_take.stderr | 2 +- .../needless_parens_on_range_literals.stderr | 12 +- tests/ui/needless_pass_by_ref_mut.stderr | 62 +-- tests/ui/needless_pass_by_value.stderr | 52 +- tests/ui/needless_pub_self.stderr | 6 +- tests/ui/needless_question_mark.stderr | 30 +- tests/ui/needless_range_loop.stderr | 28 +- tests/ui/needless_range_loop2.stderr | 16 +- tests/ui/needless_raw_string.stderr | 14 +- tests/ui/needless_raw_string_hashes.stderr | 30 +- tests/ui/needless_return.stderr | 104 ++-- .../needless_return_with_question_mark.stderr | 4 +- tests/ui/needless_splitn.stderr | 26 +- tests/ui/needless_update.stderr | 2 +- tests/ui/neg_cmp_op_on_partial_ord.stderr | 8 +- tests/ui/neg_multiply.stderr | 16 +- tests/ui/never_loop.stderr | 32 +- tests/ui/new_ret_no_self.stderr | 24 +- tests/ui/new_ret_no_self_overflow.stderr | 2 +- tests/ui/new_without_default.fixed | 10 +- tests/ui/new_without_default.rs | 4 +- tests/ui/new_without_default.stderr | 35 +- tests/ui/no_effect.stderr | 58 +- tests/ui/no_effect_async_fn.rs | 50 ++ tests/ui/no_effect_async_fn.stderr | 29 + tests/ui/no_effect_replace.stderr | 16 +- tests/ui/no_effect_return.stderr | 18 +- tests/ui/no_mangle_with_rust_abi.stderr | 10 +- tests/ui/non_canonical_clone_impl.stderr | 8 +- .../ui/non_canonical_partial_ord_impl.stderr | 4 +- ...nonical_partial_ord_impl_fully_qual.stderr | 4 +- tests/ui/non_expressive_names.stderr | 12 +- tests/ui/non_minimal_cfg.stderr | 8 +- tests/ui/non_minimal_cfg2.stderr | 2 +- tests/ui/non_octal_unix_permissions.stderr | 8 +- tests/ui/non_send_fields_in_send_ty.stderr | 52 +- tests/ui/nonminimal_bool.rs | 17 + tests/ui/nonminimal_bool.stderr | 127 ++++- tests/ui/nonminimal_bool_methods.stderr | 26 +- tests/ui/numbered_fields.stderr | 4 +- tests/ui/obfuscated_if_else.stderr | 2 +- tests/ui/octal_escapes.stderr | 18 +- tests/ui/ok_expect.stderr | 10 +- tests/ui/only_used_in_recursion.stderr | 64 +-- tests/ui/only_used_in_recursion2.stderr | 20 +- tests/ui/op_ref.stderr | 8 +- tests/ui/open_options.stderr | 16 +- tests/ui/open_options_fixable.stderr | 2 +- tests/ui/option_as_ref_cloned.stderr | 6 +- tests/ui/option_as_ref_deref.stderr | 36 +- tests/ui/option_env_unwrap.stderr | 14 +- tests/ui/option_filter_map.stderr | 16 +- tests/ui/option_if_let_else.stderr | 50 +- tests/ui/option_map_or_err_ok.stderr | 2 +- tests/ui/option_map_or_none.stderr | 10 +- tests/ui/option_map_unit_fn_fixable.stderr | 38 +- tests/ui/option_map_unit_fn_unfixable.stderr | 8 +- tests/ui/option_option.stderr | 26 +- tests/ui/or_fun_call.stderr | 62 +-- tests/ui/or_then_unwrap.stderr | 6 +- .../out_of_bounds_indexing/issue-3102.stderr | 4 +- tests/ui/out_of_bounds_indexing/simple.stderr | 12 +- tests/ui/overflow_check_conditional.stderr | 16 +- tests/ui/overly_complex_bool_expr.stderr | 20 +- tests/ui/panic_in_result_fn.stderr | 8 +- tests/ui/panic_in_result_fn_assertions.stderr | 12 +- tests/ui/panicking_macros.stderr | 32 +- tests/ui/partial_pub_fields.stderr | 8 +- tests/ui/partialeq_ne_impl.stderr | 2 +- tests/ui/partialeq_to_none.stderr | 30 +- tests/ui/path_buf_push_overwrite.stderr | 2 +- tests/ui/path_ends_with_ext.stderr | 4 +- .../pattern_type_mismatch/mutability.stderr | 4 +- .../pattern_alternatives.stderr | 6 +- .../pattern_structs.stderr | 16 +- .../pattern_tuples.stderr | 20 +- tests/ui/pattern_type_mismatch/syntax.stderr | 18 +- tests/ui/patterns.stderr | 6 +- .../ui/permissions_set_readonly_false.stderr | 2 +- tests/ui/precedence.stderr | 24 +- tests/ui/print.stderr | 16 +- tests/ui/print_in_format_impl.stderr | 14 +- tests/ui/print_literal.stderr | 32 +- tests/ui/print_stderr.stderr | 4 +- tests/ui/print_with_newline.stderr | 18 +- tests/ui/println_empty_string.stderr | 8 +- tests/ui/proc_macro.stderr | 2 +- tests/ui/ptr_arg.stderr | 48 +- tests/ui/ptr_as_ptr.stderr | 66 +-- tests/ui/ptr_cast_constness.stderr | 14 +- tests/ui/ptr_eq.stderr | 4 +- tests/ui/ptr_eq_no_std.stderr | 4 +- tests/ui/ptr_offset_with_cast.stderr | 4 +- tests/ui/pub_use.stderr | 2 +- tests/ui/pub_with_shorthand.stderr | 8 +- tests/ui/pub_without_shorthand.stderr | 6 +- tests/ui/question_mark.stderr | 32 +- tests/ui/question_mark_used.stderr | 2 +- tests/ui/range.stderr | 2 +- tests/ui/range_contains.stderr | 42 +- tests/ui/range_plus_minus_one.stderr | 18 +- tests/ui/rc_buffer.stderr | 16 +- tests/ui/rc_buffer_arc.stderr | 16 +- tests/ui/rc_clone_in_vec_init/arc.stderr | 8 +- tests/ui/rc_clone_in_vec_init/rc.stderr | 8 +- tests/ui/rc_clone_in_vec_init/weak.stderr | 16 +- tests/ui/rc_mutex.stderr | 8 +- tests/ui/read_line_without_trim.fixed | 13 + tests/ui/read_line_without_trim.rs | 13 + tests/ui/read_line_without_trim.stderr | 70 ++- tests/ui/read_zero_byte_vec.stderr | 22 +- tests/ui/readonly_write_lock.stderr | 4 +- tests/ui/recursive_format_impl.stderr | 20 +- tests/ui/redundant_allocation.stderr | 40 +- tests/ui/redundant_allocation_fixable.stderr | 24 +- tests/ui/redundant_as_str.stderr | 4 +- tests/ui/redundant_async_block.stderr | 20 +- tests/ui/redundant_at_rest_pattern.stderr | 12 +- tests/ui/redundant_clone.stderr | 60 +-- tests/ui/redundant_closure_call_early.stderr | 4 +- .../ui/redundant_closure_call_fixable.stderr | 34 +- tests/ui/redundant_closure_call_late.stderr | 6 +- tests/ui/redundant_else.stderr | 14 +- tests/ui/redundant_field_names.stderr | 16 +- tests/ui/redundant_guards.fixed | 57 +- tests/ui/redundant_guards.rs | 57 +- tests/ui/redundant_guards.stderr | 98 +++- tests/ui/redundant_locals.stderr | 56 +- ...dundant_pattern_matching_drop_order.stderr | 44 +- ...undant_pattern_matching_if_let_true.stderr | 14 +- .../redundant_pattern_matching_ipaddr.stderr | 40 +- .../redundant_pattern_matching_option.stderr | 60 +-- .../ui/redundant_pattern_matching_poll.stderr | 40 +- .../redundant_pattern_matching_result.stderr | 56 +- tests/ui/redundant_pub_crate.stderr | 32 +- tests/ui/redundant_slicing.stderr | 6 +- tests/ui/redundant_static_lifetimes.stderr | 36 +- ...redundant_static_lifetimes_multiple.stderr | 20 +- tests/ui/redundant_type_annotations.stderr | 34 +- tests/ui/ref_as_ptr.fixed | 88 +-- tests/ui/ref_as_ptr.rs | 88 +-- tests/ui/ref_as_ptr.stderr | 244 ++++----- tests/ui/ref_binding_to_reference.stderr | 14 +- tests/ui/ref_option_ref.stderr | 22 +- tests/ui/ref_patterns.stderr | 6 +- tests/ui/regex.stderr | 48 +- tests/ui/rename.stderr | 116 ++-- tests/ui/renamed_builtin_attr.stderr | 2 +- tests/ui/repeat_once.stderr | 12 +- tests/ui/repeat_vec_with_capacity.stderr | 6 +- tests/ui/repl_uninit.stderr | 8 +- tests/ui/reserve_after_initialization.stderr | 6 +- .../ui/rest_pat_in_fully_bound_structs.stderr | 6 +- tests/ui/result_filter_map.stderr | 8 +- tests/ui/result_large_err.stderr | 24 +- tests/ui/result_map_or_into_option.stderr | 6 +- tests/ui/result_map_unit_fn_fixable.stderr | 36 +- tests/ui/result_map_unit_fn_unfixable.stderr | 12 +- tests/ui/result_unit_error.stderr | 10 +- tests/ui/return_self_not_must_use.stderr | 6 +- tests/ui/reversed_empty_ranges_fixable.stderr | 8 +- ...reversed_empty_ranges_loops_fixable.stderr | 12 +- ...versed_empty_ranges_loops_unfixable.stderr | 4 +- .../ui/reversed_empty_ranges_unfixable.stderr | 6 +- .../ui/same_functions_in_if_condition.stderr | 26 +- tests/ui/same_item_push.stderr | 10 +- tests/ui/same_name_method.stderr | 24 +- tests/ui/search_is_some.stderr | 16 +- tests/ui/search_is_some_fixable_none.stderr | 86 +-- tests/ui/search_is_some_fixable_some.stderr | 94 ++-- tests/ui/seek_from_current.stderr | 2 +- .../ui/seek_to_start_instead_of_rewind.stderr | 6 +- tests/ui/self_assignment.stderr | 22 +- tests/ui/self_named_constructors.stderr | 2 +- tests/ui/semicolon_if_nothing_returned.fixed | 7 +- tests/ui/semicolon_if_nothing_returned.rs | 7 +- tests/ui/semicolon_if_nothing_returned.stderr | 10 +- tests/ui/semicolon_inside_block.stderr | 8 +- tests/ui/semicolon_outside_block.stderr | 8 +- tests/ui/serde.stderr | 2 +- tests/ui/shadow.stderr | 92 ++-- tests/ui/short_circuit_statement.stderr | 6 +- .../ui/should_impl_trait/method_list_1.stderr | 30 +- .../ui/should_impl_trait/method_list_2.stderr | 30 +- tests/ui/should_panic_without_expect.stderr | 4 +- tests/ui/significant_drop_in_scrutinee.stderr | 52 +- tests/ui/significant_drop_tightening.stderr | 8 +- tests/ui/similar_names.rs | 5 +- tests/ui/similar_names.stderr | 50 +- tests/ui/single_call_fn.rs | 22 + tests/ui/single_call_fn.stderr | 80 ++- tests/ui/single_char_add_str.stderr | 30 +- tests/ui/single_char_lifetime_names.stderr | 10 +- tests/ui/single_char_pattern.stderr | 80 +-- tests/ui/single_component_path_imports.stderr | 4 +- ...component_path_imports_nested_first.stderr | 6 +- tests/ui/single_element_loop.stderr | 14 +- tests/ui/single_match.stderr | 36 +- tests/ui/single_match_else.stderr | 18 +- tests/ui/single_range_in_vec_init.stderr | 20 +- .../expressions.stderr | 8 +- .../size_of_in_element_count/functions.stderr | 42 +- tests/ui/size_of_ref.stderr | 6 +- tests/ui/skip_while_next.stderr | 4 +- tests/ui/slow_vector_initialization.stderr | 26 +- tests/ui/stable_sort_primitive.stderr | 14 +- tests/ui/starts_ends_with.stderr | 32 +- tests/ui/std_instead_of_core.stderr | 22 +- tests/ui/str_split.stderr | 20 +- tests/ui/str_to_string.stderr | 4 +- tests/ui/string_add.stderr | 8 +- tests/ui/string_add_assign.stderr | 6 +- tests/ui/string_extend.stderr | 8 +- tests/ui/string_from_utf8_as_bytes.stderr | 2 +- tests/ui/string_lit_as_bytes.stderr | 14 +- tests/ui/string_lit_chars_any.stderr | 10 +- tests/ui/string_slice.stderr | 6 +- tests/ui/string_to_string.stderr | 2 +- tests/ui/strlen_on_c_strings.stderr | 14 +- tests/ui/struct_excessive_bools.stderr | 4 +- tests/ui/struct_fields.rs | 8 +- tests/ui/struct_fields.stderr | 50 +- tests/ui/suspicious_arithmetic_impl.stderr | 18 +- tests/ui/suspicious_command_arg_space.stderr | 4 +- tests/ui/suspicious_doc_comments.stderr | 18 +- .../suspicious_doc_comments_unfixable.stderr | 4 +- tests/ui/suspicious_else_formatting.stderr | 18 +- tests/ui/suspicious_map.stderr | 4 +- .../ui/suspicious_operation_groupings.stderr | 52 +- tests/ui/suspicious_splitn.stderr | 18 +- tests/ui/suspicious_to_owned.stderr | 12 +- .../ui/suspicious_unary_op_formatting.stderr | 8 +- tests/ui/suspicious_xor_used_as_pow.stderr | 14 +- tests/ui/swap.stderr | 34 +- tests/ui/swap_ptr_to_ref.stderr | 8 +- tests/ui/swap_ptr_to_ref_unfixable.stderr | 6 +- tests/ui/tabs_in_doc_comments.stderr | 16 +- tests/ui/temporary_assignment.stderr | 8 +- tests/ui/test_attr_in_doctest.stderr | 6 +- tests/ui/tests_outside_test_module.stderr | 2 +- ...local_initializer_can_be_made_const.stderr | 8 +- tests/ui/to_digit_is_some.stderr | 4 +- ...to_string_in_format_args_incremental.fixed | 8 - tests/ui/to_string_trait_impl.rs | 44 ++ tests/ui/to_string_trait_impl.stderr | 16 +- tests/ui/toplevel_ref_arg.stderr | 12 +- tests/ui/toplevel_ref_arg_non_rustfix.stderr | 4 +- tests/ui/track-diagnostics.stderr | 2 +- tests/ui/trailing_empty_array.stderr | 22 +- tests/ui/trailing_zeros.stderr | 4 +- tests/ui/trait_duplication_in_bounds.stderr | 20 +- .../trait_duplication_in_bounds_unfixable.rs | 1 + ...ait_duplication_in_bounds_unfixable.stderr | 18 +- tests/ui/transmute.stderr | 72 +-- tests/ui/transmute_32bit.stderr | 8 +- tests/ui/transmute_64bit.stderr | 4 +- tests/ui/transmute_collection.stderr | 36 +- tests/ui/transmute_float_to_int.stderr | 12 +- tests/ui/transmute_int_to_char.stderr | 4 +- tests/ui/transmute_int_to_char_no_std.stderr | 4 +- tests/ui/transmute_int_to_non_zero.stderr | 20 +- tests/ui/transmute_null_to_fn.stderr | 12 +- tests/ui/transmute_ptr_to_ptr.stderr | 14 +- tests/ui/transmute_ptr_to_ref.stderr | 44 +- tests/ui/transmute_ref_to_ref.stderr | 8 +- tests/ui/transmute_ref_to_ref_no_std.stderr | 8 +- tests/ui/transmute_undefined_repr.stderr | 24 +- ...transmutes_expressible_as_ptr_casts.stderr | 20 +- tests/ui/transmuting_null.stderr | 6 +- tests/ui/trim_split_whitespace.stderr | 16 +- tests/ui/trivially_copy_pass_by_ref.stderr | 38 +- tests/ui/try_err.stderr | 24 +- tests/ui/tuple_array_conversions.stderr | 20 +- tests/ui/type_complexity.stderr | 30 +- tests/ui/type_id_on_box.stderr | 6 +- tests/ui/type_repetition_in_bounds.rs | 2 +- tests/ui/type_repetition_in_bounds.stderr | 12 +- tests/ui/types.stderr | 2 +- .../ui/unchecked_duration_subtraction.stderr | 8 +- tests/ui/unconditional_recursion.rs | 2 +- tests/ui/unconditional_recursion.stderr | 91 ++-- tests/ui/unicode.stderr | 20 +- tests/ui/uninhabited_references.stderr | 8 +- tests/ui/uninit.stderr | 6 +- tests/ui/uninit_vec.stderr | 22 +- tests/ui/uninlined_format_args.stderr | 142 ++--- ...lined_format_args_panic.edition2018.stderr | 2 +- ...lined_format_args_panic.edition2021.stderr | 12 +- tests/ui/unit_arg.stderr | 20 +- tests/ui/unit_arg_empty_blocks.stderr | 8 +- tests/ui/unit_cmp.stderr | 12 +- tests/ui/unit_hash.stderr | 6 +- tests/ui/unit_return_expecting_ord.stderr | 12 +- tests/ui/unknown_attribute.stderr | 2 +- tests/ui/unknown_clippy_lints.stderr | 18 +- tests/ui/unnecessary_box_returns.stderr | 8 +- tests/ui/unnecessary_cast.fixed | 1 + tests/ui/unnecessary_cast.rs | 1 + tests/ui/unnecessary_cast.stderr | 80 +-- tests/ui/unnecessary_cast_unfixable.stderr | 4 +- tests/ui/unnecessary_clippy_cfg.rs | 23 + tests/ui/unnecessary_clippy_cfg.stderr | 61 +++ tests/ui/unnecessary_clone.stderr | 18 +- .../ui/unnecessary_fallible_conversions.fixed | 37 ++ tests/ui/unnecessary_fallible_conversions.rs | 37 ++ .../unnecessary_fallible_conversions.stderr | 124 ++++- ...sary_fallible_conversions_unfixable.stderr | 12 +- tests/ui/unnecessary_filter_map.stderr | 10 +- tests/ui/unnecessary_find_map.stderr | 10 +- tests/ui/unnecessary_fold.stderr | 32 +- tests/ui/unnecessary_get_then_check.fixed | 26 + tests/ui/unnecessary_get_then_check.rs | 26 + tests/ui/unnecessary_get_then_check.stderr | 75 +++ tests/ui/unnecessary_iter_cloned.stderr | 4 +- tests/ui/unnecessary_join.stderr | 4 +- tests/ui/unnecessary_lazy_eval.stderr | 126 ++--- .../ui/unnecessary_lazy_eval_unfixable.stderr | 8 +- tests/ui/unnecessary_literal_unwrap.stderr | 106 ++-- ...nnecessary_literal_unwrap_unfixable.stderr | 204 +++---- .../ui/unnecessary_map_on_constructor.stderr | 16 +- tests/ui/unnecessary_operation.stderr | 38 +- .../ui/unnecessary_owned_empty_strings.stderr | 4 +- .../ui/unnecessary_result_map_or_else.stderr | 8 +- tests/ui/unnecessary_safety_comment.stderr | 36 +- tests/ui/unnecessary_self_imports.stderr | 4 +- tests/ui/unnecessary_sort_by.stderr | 24 +- .../unnecessary_struct_initialization.stderr | 12 +- tests/ui/unnecessary_to_owned.fixed | 36 ++ tests/ui/unnecessary_to_owned.rs | 36 ++ tests/ui/unnecessary_to_owned.stderr | 202 ++++--- tests/ui/unnecessary_to_owned_on_split.stderr | 18 +- tests/ui/unnecessary_unsafety_doc.stderr | 14 +- tests/ui/unnecessary_wraps.stderr | 14 +- tests/ui/unneeded_field_pattern.stderr | 4 +- tests/ui/unneeded_wildcard_pattern.stderr | 30 +- tests/ui/unnested_or_patterns.stderr | 34 +- tests/ui/unnested_or_patterns2.stderr | 16 +- tests/ui/unreadable_literal.stderr | 20 +- tests/ui/unsafe_derive_deserialize.stderr | 8 +- tests/ui/unsafe_removed_from_name.stderr | 10 +- tests/ui/unseparated_prefix_literals.stderr | 18 +- tests/ui/unused_async.stderr | 10 +- tests/ui/unused_enumerate_index.stderr | 4 +- tests/ui/unused_format_specs_unfixable.stderr | 8 +- tests/ui/unused_io_amount.stderr | 70 +-- tests/ui/unused_peekable.stderr | 16 +- tests/ui/unused_rounding.stderr | 10 +- tests/ui/unused_self.stderr | 18 +- tests/ui/unused_unit.fixed | 7 + tests/ui/unused_unit.rs | 7 + tests/ui/unused_unit.stderr | 40 +- tests/ui/unwrap.stderr | 6 +- tests/ui/unwrap_expect_used.stderr | 12 +- tests/ui/unwrap_in_result.stderr | 8 +- tests/ui/unwrap_or.stderr | 4 +- tests/ui/unwrap_or_else_default.stderr | 32 +- tests/ui/upper_case_acronyms.stderr | 24 +- tests/ui/use_self.stderr | 84 +-- tests/ui/use_self_trait.stderr | 32 +- tests/ui/used_underscore_binding.stderr | 24 +- tests/ui/useless_asref.stderr | 38 +- tests/ui/useless_attribute.fixed | 10 +- tests/ui/useless_attribute.rs | 10 +- tests/ui/useless_attribute.stderr | 10 +- tests/ui/useless_conversion.stderr | 76 +-- tests/ui/useless_conversion_try.stderr | 20 +- tests/ui/vec.fixed | 4 + tests/ui/vec.rs | 4 + tests/ui/vec.stderr | 48 +- tests/ui/vec_box_sized.stderr | 18 +- tests/ui/vec_init_then_push.stderr | 16 +- tests/ui/vec_resize_to_zero.stderr | 2 +- tests/ui/verbose_file_reads.stderr | 4 +- tests/ui/waker_clone_wake.stderr | 4 +- tests/ui/while_let_loop.stderr | 10 +- tests/ui/while_let_on_iterator.stderr | 54 +- tests/ui/wild_in_or_pats.stderr | 8 +- tests/ui/wildcard_enum_match_arm.stderr | 14 +- tests/ui/wildcard_imports.stderr | 44 +- .../wildcard_imports_2021.edition2018.stderr | 44 +- .../wildcard_imports_2021.edition2021.stderr | 44 +- tests/ui/write_literal.stderr | 24 +- tests/ui/write_literal_2.stderr | 28 +- tests/ui/write_with_newline.stderr | 18 +- tests/ui/writeln_empty_string.stderr | 4 +- tests/ui/wrong_self_convention.stderr | 48 +- tests/ui/wrong_self_convention2.stderr | 4 +- tests/ui/wrong_self_conventions_mut.stderr | 4 +- tests/ui/zero_div_zero.stderr | 8 +- tests/ui/zero_offset.stderr | 16 +- tests/ui/zero_ptr.stderr | 10 +- tests/ui/zero_ptr_no_std.stderr | 8 +- tests/ui/zero_sized_btreemap_values.stderr | 26 +- tests/ui/zero_sized_hashmap_values.stderr | 26 +- triagebot.toml | 4 +- util/gh-pages/index.html | 14 +- 1096 files changed, 14195 insertions(+), 10884 deletions(-) create mode 100644 clippy_lints/src/methods/unnecessary_get_then_check.rs create mode 100644 clippy_lints/src/multiple_bound_locations.rs create mode 100644 tests/ui-toml/disallowed_macros/auxiliary/proc_macros.rs create mode 100644 tests/ui/asm_syntax_not_x86.rs rename tests/ui/{asm_syntax.stderr => asm_syntax_x86.i686.stderr} (58%) rename tests/ui/{asm_syntax.rs => asm_syntax_x86.rs} (64%) create mode 100644 tests/ui/asm_syntax_x86.x86_64.stderr create mode 100644 tests/ui/cfg_attr_cargo_clippy.fixed create mode 100644 tests/ui/cfg_attr_cargo_clippy.rs create mode 100644 tests/ui/cfg_attr_cargo_clippy.stderr create mode 100644 tests/ui/crashes/ice-12253.rs create mode 100644 tests/ui/empty_docs.rs create mode 100644 tests/ui/empty_docs.stderr create mode 100644 tests/ui/multiple_bound_locations.rs create mode 100644 tests/ui/multiple_bound_locations.stderr create mode 100644 tests/ui/no_effect_async_fn.rs create mode 100644 tests/ui/no_effect_async_fn.stderr delete mode 100644 tests/ui/to_string_in_format_args_incremental.fixed create mode 100644 tests/ui/unnecessary_clippy_cfg.rs create mode 100644 tests/ui/unnecessary_clippy_cfg.stderr create mode 100644 tests/ui/unnecessary_get_then_check.fixed create mode 100644 tests/ui/unnecessary_get_then_check.rs create mode 100644 tests/ui/unnecessary_get_then_check.stderr diff --git a/.github/driver.sh b/.github/driver.sh index 40a2aad0f..11fd6b5c7 100755 --- a/.github/driver.sh +++ b/.github/driver.sh @@ -32,7 +32,7 @@ test "$sysroot" = $desired_sysroot ) # Check that the --sysroot argument is only passed once via arg_file.txt (SYSROOT is ignored) -( +( echo "fn main() {}" > target/driver_test.rs echo "--sysroot="$(./target/debug/clippy-driver --print sysroot)"" > arg_file.txt echo "--verbose" >> arg_file.txt @@ -45,7 +45,7 @@ unset CARGO_MANIFEST_DIR # Run a lint and make sure it produces the expected output. It's also expected to exit with code 1 # FIXME: How to match the clippy invocation in compile-test.rs? ./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/double_neg.rs 2>double_neg.stderr && exit 1 -sed -e "s,tests/ui,\$DIR," -e "/= help: for/d" double_neg.stderr > normalized.stderr +sed -e "/= help: for/d" double_neg.stderr > normalized.stderr diff -u normalized.stderr tests/ui/double_neg.stderr # make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8535672..f0b01742d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5125,6 +5125,7 @@ Released 2018-09-13 [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access [`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr +[`deprecated_clippy_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_clippy_cfg_attr [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver [`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof [`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing @@ -5157,6 +5158,7 @@ Released 2018-09-13 [`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec [`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute [`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else +[`empty_docs`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_docs [`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop [`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum [`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets @@ -5429,6 +5431,7 @@ Released 2018-09-13 [`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_bound_locations`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations [`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 [`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block @@ -5725,10 +5728,12 @@ Released 2018-09-13 [`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints [`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast +[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg [`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold +[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check [`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap diff --git a/Cargo.toml b/Cargo.toml index 321424880..5d1d0ce2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,17 +27,17 @@ rustc_tools_util = "0.3.0" tempfile = { version = "3.2", optional = true } termize = "0.1" color-print = "0.3.4" -anstream = "0.5.0" +anstream = "0.6.0" [dev-dependencies] -ui_test = "0.21.2" +ui_test = "0.22.2" tester = "0.9" regex = "1.5" toml = "0.7.3" walkdir = "2.3" # This is used by the `collect-metadata` alias. filetime = "0.2" -itertools = "0.11" +itertools = "0.12" # UI test dependencies clippy_utils = { path = "clippy_utils" } diff --git a/book/src/configuration.md b/book/src/configuration.md index e8274bc45..9eb067abd 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -33,26 +33,29 @@ disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"] To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable. -### Allowing/denying lints +### Allowing/Denying Lints -You can add options to your code to `allow`/`warn`/`deny` Clippy lints: +#### Attributes in Code -* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) +You can add attributes to your code to `allow`/`warn`/`deny` Clippy lints: -* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, - `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false - positives. +* the whole set of `warn`-by-default lints using the `clippy` lint group (`#![allow(clippy::all)]`) + +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![warn(clippy::all, clippy::pedantic)]`. Note + that `clippy::pedantic` contains some very aggressive lints prone to false positives. * only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) * `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` -the lint will emit an error, when triggering for your code. An error causes clippy to exit with an error code, so is -useful in scripts like CI/CD. +the lint will emit an error, when triggering for your code. An error causes Clippy to exit with an error code, so is +most useful in scripts used in CI/CD. -If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra -flags to Clippy during the run: +#### Command Line Flags + +If you do not want to include your lint levels in the code, you can globally enable/disable lints by passing extra flags +to Clippy during the run: To allow `lint_name`, run @@ -66,19 +69,33 @@ And to warn on `lint_name`, run cargo clippy -- -W clippy::lint_name ``` -This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled: +This also works with lint groups. For example, you can run Clippy with warnings for all pedantic lints enabled: ```terminal cargo clippy -- -W clippy::pedantic ``` -If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are +If you care only about a certain lints, you can allow all others and then explicitly warn on the lints you are interested in: ```terminal cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... ``` +#### Lints Section in `Cargo.toml` + +Finally, lints can be allowed/denied using [the lints +section](https://doc.rust-lang.org/nightly/cargo/reference/manifest.html#the-lints-section)) in the `Cargo.toml` file: + +To deny `clippy::enum_glob_use`, put the following in the `Cargo.toml`: + +```toml +[lints.clippy] +enum_glob_use = "deny" +``` + +For more details and options, refer to the Cargo documentation. + ### Specifying the minimum supported Rust version Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the @@ -113,17 +130,14 @@ found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the -`cargo-clippy` feature is not set. You may need to provide a stub so that the code compiles: +`clippy` cfg is not set. You may need to provide a stub so that the code compiles: ```rust -#[cfg(not(feature = "cargo-clippy"))] +#[cfg(not(clippy)] include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); -#[cfg(feature = "cargo-clippy")] +#[cfg(clippy)] fn my_big_function(_input: &str) -> Option { None } ``` - -This feature is not actually part of your crate, so specifying `--all-features` to other tools, e.g. `cargo test ---all-features`, will not disable it. diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md index a12f6aa91..d70f4fc17 100644 --- a/book/src/development/emitting_lints.md +++ b/book/src/development/emitting_lints.md @@ -82,7 +82,7 @@ The output looks something like this (from the example earlier): ```text error: an inclusive range would be more readable - --> $DIR/range_plus_minus_one.rs:37:14 + --> tests/ui/range_plus_minus_one.rs:37:14 | LL | for _ in 1..1 + 1 {} | ^^^^^^^^ help: use: `1..=1` @@ -135,14 +135,14 @@ Examples: ```text error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. - --> $DIR/drop_forget_ref.rs:10:5 + --> tests/ui/drop_forget_ref.rs:10:5 | 10 | forget(&SomeStruct); | ^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::forget-ref` implied by `-D warnings` note: argument has type &SomeStruct - --> $DIR/drop_forget_ref.rs:10:12 + --> tests/ui/drop_forget_ref.rs:10:12 | 10 | forget(&SomeStruct); | ^^^^^^^^^^^ @@ -158,7 +158,7 @@ Example: ```text error: constant division of 0.0 with 0.0 will always result in NaN - --> $DIR/zero_div_zero.rs:6:25 + --> tests/ui/zero_div_zero.rs:6:25 | 6 | let other_f64_nan = 0.0f64 / 0.0; | ^^^^^^^^^^^^ @@ -176,7 +176,7 @@ Example: ```text error: This `.fold` can be more succinctly expressed as `.any` ---> $DIR/methods.rs:390:13 +--> tests/ui/methods.rs:390:13 | 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` diff --git a/book/src/development/writing_tests.md b/book/src/development/writing_tests.md index 8937e0d8e..39a5ad966 100644 --- a/book/src/development/writing_tests.md +++ b/book/src/development/writing_tests.md @@ -97,19 +97,19 @@ failures: ---- compile_test stdout ---- normalized stderr: error: function called "foo" - --> $DIR/foo_functions.rs:6:12 + --> tests/ui/foo_functions.rs:6:12 | LL | pub fn foo(&self) {} | ^^^ | = note: `-D clippy::foo-functions` implied by `-D warnings` error: function called "foo" - --> $DIR/foo_functions.rs:13:8 + --> tests/ui/foo_functions.rs:13:8 | LL | fn foo(&self) {} | ^^^ error: function called "foo" - --> $DIR/foo_functions.rs:19:4 + --> tests/ui/foo_functions.rs:19:4 | LL | fn foo() {} | ^^^ diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index f2357e2b5..214a60d3b 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -278,7 +278,7 @@ The minimum number of struct fields for the lints about field names to trigger --- **Affected lints:** -* [`struct_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_variant_names) +* [`struct_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_field_names) ## `enum-variant-size-threshold` diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 9741b94d5..b781259ad 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -325,7 +325,7 @@ define_Conf! { /// /// The minimum number of enum variants for the lints about variant names to trigger (enum_variant_name_threshold: u64 = 3), - /// Lint: STRUCT_VARIANT_NAMES. + /// Lint: STRUCT_FIELD_NAMES. /// /// The minimum number of struct fields for the lints about field names to trigger (struct_field_name_threshold: u64 = 3), @@ -648,7 +648,7 @@ fn deserialize(file: &SourceFile) -> TryConf { extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); // TODO: THIS SHOULD BE TESTED, this comment will be gone soon - if conf.conf.allowed_idents_below_min_chars.contains(&"..".to_owned()) { + if conf.conf.allowed_idents_below_min_chars.contains("..") { conf.conf .allowed_idents_below_min_chars .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string)); diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index 5449feed0..dab311989 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -6,7 +6,7 @@ clippy::missing_panics_doc, rustc::diagnostic_outside_of_impl, rustc::untranslatable_diagnostic, - rustc::untranslatable_diagnostic_trivial, + rustc::untranslatable_diagnostic_trivial )] extern crate rustc_ast; diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 5ec67554e..42a953039 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" aho-corasick = "1.0" clap = "4.1.4" indoc = "1.0" -itertools = "0.11" +itertools = "0.12" opener = "0.6" shell-escape = "0.1" walkdir = "2.3" diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 6e6e315bb..6ae089b3e 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -14,7 +14,7 @@ cargo_metadata = "0.18" clippy_config = { path = "../clippy_config" } clippy_utils = { path = "../clippy_utils" } declare_clippy_lint = { path = "../declare_clippy_lint" } -itertools = "0.11" +itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" serde = { version = "1.0", features = ["derive"] } diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index feb6437ee..c2fa56e13 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -2,8 +2,11 @@ use std::fmt; use clippy_utils::diagnostics::span_lint_and_help; use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; -use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; +use rustc_ast::{InlineAsm, Item, ItemKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_target::asm::InlineAsmArch; #[derive(Clone, Copy, PartialEq, Eq)] enum AsmStyle { @@ -31,8 +34,14 @@ impl std::ops::Not for AsmStyle { } } -fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) { - if let ExprKind::InlineAsm(ref inline_asm) = expr.kind { +fn check_asm_syntax( + lint: &'static Lint, + cx: &EarlyContext<'_>, + inline_asm: &InlineAsm, + span: Span, + check_for: AsmStyle, +) { + if matches!(cx.sess().asm_arch, Some(InlineAsmArch::X86 | InlineAsmArch::X86_64)) { let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) { AsmStyle::Att } else { @@ -43,7 +52,7 @@ fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr span_lint_and_help( cx, lint, - expr.span, + span, &format!("{style} x86 assembly syntax used"), None, &format!("use {} x86 assembly syntax", !style), @@ -89,7 +98,15 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); impl EarlyLintPass for InlineAsmX86IntelSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel); + if let ExprKind::InlineAsm(inline_asm) = &expr.kind { + check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel); + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::GlobalAsm(inline_asm) = &item.kind { + check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel); + } } } @@ -130,6 +147,14 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att); + if let ExprKind::InlineAsm(inline_asm) = &expr.kind { + check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att); + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::GlobalAsm(inline_asm) = &item.kind { + check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att); + } } } diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index da3842287..f2937d513 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -1,7 +1,9 @@ //! checks for attributes use clippy_config::msrvs::{self, Msrv}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{ + span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, +}; use clippy_utils::is_from_proc_macro; use clippy_utils::macros::{is_panic, macro_backtrace}; use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; @@ -433,6 +435,56 @@ declare_clippy_lint! { "prevent from misusing the wrong attr name" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for + /// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with + /// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`. + /// + /// ### Why is this bad? + /// This feature has been deprecated for years and shouldn't be used anymore. + /// + /// ### Example + /// ```no_run + /// #[cfg(feature = "cargo-clippy")] + /// struct Bar; + /// ``` + /// + /// Use instead: + /// ```no_run + /// #[cfg(clippy)] + /// struct Bar; + /// ``` + #[clippy::version = "1.78.0"] + pub DEPRECATED_CLIPPY_CFG_ATTR, + suspicious, + "usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]` + /// and suggests to replace it with `#[allow(clippy::lint)]`. + /// + /// ### Why is this bad? + /// There is no reason to put clippy attributes behind a clippy `cfg` as they are not + /// run by anything else than clippy. + /// + /// ### Example + /// ```no_run + /// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))] + /// ``` + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::deprecated_cfg_attr)] + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_CLIPPY_CFG, + suspicious, + "usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`" +} + declare_lint_pass!(Attributes => [ ALLOW_ATTRIBUTES_WITHOUT_REASON, INLINE_ALWAYS, @@ -512,6 +564,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { || is_word(lint, sym::deprecated) || is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unused)) + || is_word(lint, sym!(unused_import_braces)) || extract_clippy_lint(lint).map_or(false, |s| { matches!( s.as_str(), @@ -794,6 +847,8 @@ impl_lint_pass!(EarlyAttributes => [ EMPTY_LINE_AFTER_DOC_COMMENTS, NON_MINIMAL_CFG, MAYBE_MISUSED_CFG, + DEPRECATED_CLIPPY_CFG_ATTR, + UNNECESSARY_CLIPPY_CFG, ]); impl EarlyLintPass for EarlyAttributes { @@ -803,6 +858,7 @@ impl EarlyLintPass for EarlyAttributes { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { check_deprecated_cfg_attr(cx, attr, &self.msrv); + check_deprecated_cfg(cx, attr); check_mismatched_target_os(cx, attr); check_minimal_cfg_condition(cx, attr); check_misused_cfg(cx, attr); @@ -857,42 +913,149 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It } } -fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) { - if msrv.meets(msrvs::TOOL_ATTRIBUTES) - // check cfg_attr - && attr.has_name(sym::cfg_attr) - && let Some(items) = attr.meta_item_list() - && items.len() == 2 - // check for `rustfmt` - && let Some(feature_item) = items[0].meta_item() - && feature_item.has_name(sym::rustfmt) - // check for `rustfmt_skip` and `rustfmt::skip` - && let Some(skip_item) = &items[1].meta_item() - && (skip_item.has_name(sym!(rustfmt_skip)) - || skip_item - .path - .segments - .last() - .expect("empty path in attribute") - .ident - .name - == sym::skip) - // Only lint outer attributes, because custom inner attributes are unstable - // Tracking issue: https://github.com/rust-lang/rust/issues/54726 - && attr.style == AttrStyle::Outer - { +fn check_cargo_clippy_attr(cx: &EarlyContext<'_>, item: &rustc_ast::MetaItem) { + if item.has_name(sym::feature) && item.value_str().is_some_and(|v| v.as_str() == "cargo-clippy") { span_lint_and_sugg( cx, - DEPRECATED_CFG_ATTR, - attr.span, - "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", - "use", - "#[rustfmt::skip]".to_string(), + DEPRECATED_CLIPPY_CFG_ATTR, + item.span, + "`feature = \"cargo-clippy\"` was replaced by `clippy`", + "replace with", + "clippy".to_string(), Applicability::MachineApplicable, ); } } +fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) { + if let Some(ident) = attr.ident() { + if ["any", "all", "not"].contains(&ident.name.as_str()) { + let Some(list) = attr.meta_item_list() else { return }; + for item in list.iter().filter_map(|item| item.meta_item()) { + check_deprecated_cfg_recursively(cx, item); + } + } else { + check_cargo_clippy_attr(cx, attr); + } + } +} + +fn check_deprecated_cfg(cx: &EarlyContext<'_>, attr: &Attribute) { + if attr.has_name(sym::cfg) + && let Some(list) = attr.meta_item_list() + { + for item in list.iter().filter_map(|item| item.meta_item()) { + check_deprecated_cfg_recursively(cx, item); + } + } +} + +fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) { + // check cfg_attr + if attr.has_name(sym::cfg_attr) + && let Some(items) = attr.meta_item_list() + && items.len() == 2 + && let Some(feature_item) = items[0].meta_item() + { + // check for `rustfmt` + if feature_item.has_name(sym::rustfmt) + && msrv.meets(msrvs::TOOL_ATTRIBUTES) + // check for `rustfmt_skip` and `rustfmt::skip` + && let Some(skip_item) = &items[1].meta_item() + && (skip_item.has_name(sym!(rustfmt_skip)) + || skip_item + .path + .segments + .last() + .expect("empty path in attribute") + .ident + .name + == sym::skip) + // Only lint outer attributes, because custom inner attributes are unstable + // Tracking issue: https://github.com/rust-lang/rust/issues/54726 + && attr.style == AttrStyle::Outer + { + span_lint_and_sugg( + cx, + DEPRECATED_CFG_ATTR, + attr.span, + "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", + "use", + "#[rustfmt::skip]".to_string(), + Applicability::MachineApplicable, + ); + } else { + check_deprecated_cfg_recursively(cx, feature_item); + if let Some(behind_cfg_attr) = items[1].meta_item() { + check_clippy_cfg_attr(cx, feature_item, behind_cfg_attr, attr); + } + } + } +} + +fn check_clippy_cfg_attr( + cx: &EarlyContext<'_>, + cfg_attr: &rustc_ast::MetaItem, + behind_cfg_attr: &rustc_ast::MetaItem, + attr: &Attribute, +) { + if cfg_attr.has_name(sym::clippy) + && let Some(ident) = behind_cfg_attr.ident() + && Level::from_symbol(ident.name, Some(attr.id)).is_some() + && let Some(items) = behind_cfg_attr.meta_item_list() + { + let nb_items = items.len(); + let mut clippy_lints = Vec::with_capacity(items.len()); + for item in items { + if let Some(meta_item) = item.meta_item() + && let [part1, _] = meta_item.path.segments.as_slice() + && part1.ident.name == sym::clippy + { + clippy_lints.push(item.span()); + } + } + if clippy_lints.is_empty() { + return; + } + if nb_items == clippy_lints.len() { + if let Some(snippet) = snippet_opt(cx, behind_cfg_attr.span) { + span_lint_and_sugg( + cx, + UNNECESSARY_CLIPPY_CFG, + attr.span, + "no need to put clippy lints behind a `clippy` cfg", + "replace with", + format!( + "#{}[{}]", + if attr.style == AttrStyle::Inner { "!" } else { "" }, + snippet + ), + Applicability::MachineApplicable, + ); + } + } else { + let snippet = clippy_lints + .iter() + .filter_map(|sp| snippet_opt(cx, *sp)) + .collect::>() + .join(","); + span_lint_and_note( + cx, + UNNECESSARY_CLIPPY_CFG, + clippy_lints, + "no need to put clippy lints behind a `clippy` cfg", + None, + &format!( + "write instead: `#{}[{}({})]`", + if attr.style == AttrStyle::Inner { "!" } else { "" }, + ident.name, + snippet + ), + ); + } + } +} + fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) { for item in items { if let NestedMetaItem::MetaItem(meta) = item { diff --git a/clippy_lints/src/blocks_in_conditions.rs b/clippy_lints/src/blocks_in_conditions.rs index ff4dffd06..2eb0dac97 100644 --- a/clippy_lints/src/blocks_in_conditions.rs +++ b/clippy_lints/src/blocks_in_conditions.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_block_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::visitors::{for_each_expr, Descend}; -use clippy_utils::{get_parent_expr, higher}; +use clippy_utils::{get_parent_expr, higher, is_from_proc_macro}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource}; @@ -13,7 +13,7 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Checks for `if` conditions that use blocks containing an + /// Checks for `if` and `match` conditions that use blocks containing an /// expression, statements or conditions that use closures with blocks. /// /// ### Why is this bad? @@ -25,6 +25,11 @@ declare_clippy_lint! { /// if { true } { /* ... */ } /// /// if { let x = somefunc(); x } { /* ... */ } + /// + /// match { let e = somefunc(); e } { + /// // ... + /// # _ => {} + /// } /// ``` /// /// Use instead: @@ -34,6 +39,12 @@ declare_clippy_lint! { /// /// let res = { let x = somefunc(); x }; /// if res { /* ... */ } + /// + /// let res = { let e = somefunc(); e }; + /// match res { + /// // ... + /// # _ => {} + /// } /// ``` #[clippy::version = "1.45.0"] pub BLOCKS_IN_CONDITIONS, @@ -94,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions { } } else { let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); - if span.from_expansion() || expr.span.from_expansion() { + if span.from_expansion() || expr.span.from_expansion() || is_from_proc_macro(cx, cond) { return; } // move block higher diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 2d1c250ac..0d66f2d64 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -85,7 +85,117 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool { ) { NonminimalBoolVisitor { cx }.visit_body(body); } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + match expr.kind { + ExprKind::Unary(UnOp::Not, sub) => check_inverted_condition(cx, expr.span, sub), + // This check the case where an element in a boolean comparison is inverted, like: + // + // ``` + // let a = true; + // !a == false; + // ``` + ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) => { + check_inverted_bool_in_condition(cx, expr.span, op.node, left, right); + }, + _ => {}, + } + } } + +fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> { + match op { + BinOpKind::Eq => Some("!="), + BinOpKind::Ne => Some("=="), + _ => None, + } +} + +fn bin_op_eq_str(op: BinOpKind) -> Option<&'static str> { + match op { + BinOpKind::Eq => Some("=="), + BinOpKind::Ne => Some("!="), + _ => None, + } +} + +fn check_inverted_condition(cx: &LateContext<'_>, expr_span: Span, sub_expr: &Expr<'_>) { + if !expr_span.from_expansion() + && let ExprKind::Binary(op, left, right) = sub_expr.kind + && let Some(left) = snippet_opt(cx, left.span) + && let Some(right) = snippet_opt(cx, right.span) + { + let Some(op) = inverted_bin_op_eq_str(op.node) else { + return; + }; + span_lint_and_sugg( + cx, + NONMINIMAL_BOOL, + expr_span, + "this boolean expression can be simplified", + "try", + format!("{left} {op} {right}",), + Applicability::MachineApplicable, + ); + } +} + +fn check_inverted_bool_in_condition( + cx: &LateContext<'_>, + expr_span: Span, + op: BinOpKind, + left: &Expr<'_>, + right: &Expr<'_>, +) { + if expr_span.from_expansion() + && (!cx.typeck_results().node_types()[left.hir_id].is_bool() + || !cx.typeck_results().node_types()[right.hir_id].is_bool()) + { + return; + } + + let suggestion = match (left.kind, right.kind) { + (ExprKind::Unary(UnOp::Not, left_sub), ExprKind::Unary(UnOp::Not, right_sub)) => { + let Some(left) = snippet_opt(cx, left_sub.span) else { + return; + }; + let Some(right) = snippet_opt(cx, right_sub.span) else { + return; + }; + let Some(op) = bin_op_eq_str(op) else { return }; + format!("{left} {op} {right}") + }, + (ExprKind::Unary(UnOp::Not, left_sub), _) => { + let Some(left) = snippet_opt(cx, left_sub.span) else { + return; + }; + let Some(right) = snippet_opt(cx, right.span) else { + return; + }; + let Some(op) = inverted_bin_op_eq_str(op) else { return }; + format!("{left} {op} {right}") + }, + (_, ExprKind::Unary(UnOp::Not, right_sub)) => { + let Some(left) = snippet_opt(cx, left.span) else { return }; + let Some(right) = snippet_opt(cx, right_sub.span) else { + return; + }; + let Some(op) = inverted_bin_op_eq_str(op) else { return }; + format!("{left} {op} {right}") + }, + _ => return, + }; + span_lint_and_sugg( + cx, + NONMINIMAL_BOOL, + expr_span, + "this boolean expression can be simplified", + "try", + suggestion, + Applicability::MachineApplicable, + ); +} + struct NonminimalBoolVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, } diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index ef12fe344..779ae03c4 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::macro_backtrace; +use clippy_utils::source::snippet_opt; use clippy_utils::ty::expr_sig; -use clippy_utils::{get_parent_node, is_default_equivalent, path_def_id}; +use clippy_utils::{is_default_equivalent, path_def_id}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{walk_ty, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, TyKind}; +use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, Ty, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::print::with_forced_trimmed_paths; @@ -41,13 +42,24 @@ declare_lint_pass!(BoxDefault => [BOX_DEFAULT]); impl LateLintPass<'_> for BoxDefault { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // If the expression is a call (`Box::new(...)`) if let ExprKind::Call(box_new, [arg]) = expr.kind + // And call is of the form `::something` + // Here, it would be `::new` && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind - && let ExprKind::Call(arg_path, ..) = arg.kind - && !in_external_macro(cx.sess(), expr.span) - && (expr.span.eq_ctxt(arg.span) || is_local_vec_expn(cx, arg, expr)) + // And that method is `new` && seg.ident.name == sym::new + // And the call is that of a `Box` method && path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box()) + // And the single argument to the call is another function call + // This is the `T::default()` of `Box::new(T::default())` + && let ExprKind::Call(arg_path, inner_call_args) = arg.kind + // And we are not in a foreign crate's macro + && !in_external_macro(cx.sess(), expr.span) + // And the argument expression has the same context as the outer call expression + // or that we are inside a `vec!` macro expansion + && (expr.span.eq_ctxt(arg.span) || is_local_vec_expn(cx, arg, expr)) + // And the argument is equivalent to `Default::default()` && is_default_equivalent(cx, arg) { span_lint_and_sugg( @@ -59,7 +71,17 @@ impl LateLintPass<'_> for BoxDefault { if is_plain_default(cx, arg_path) || given_type(cx, expr) { "Box::default()".into() } else if let Some(arg_ty) = cx.typeck_results().expr_ty(arg).make_suggestable(cx.tcx, true) { - with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()")) + // Check if we can copy from the source expression in the replacement. + // We need the call to have no argument (see `explicit_default_type`). + if inner_call_args.is_empty() + && let Some(ty) = explicit_default_type(arg_path) + && let Some(s) = snippet_opt(cx, ty.span) + { + format!("Box::<{s}>::default()") + } else { + // Otherwise, use the inferred type's formatting. + with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()")) + } } else { return; }, @@ -81,6 +103,20 @@ fn is_plain_default(cx: &LateContext<'_>, arg_path: &Expr<'_>) -> bool { } } +// Checks whether the call is of the form `A::B::f()`. Returns `A::B` if it is. +// +// In the event we have this kind of construct, it's easy to use `A::B` as a replacement in the +// quickfix. `f` must however have no parameter. Should `f` have some, then some of the type of +// `A::B` may be inferred from the arguments. This would be the case for `Vec::from([0; false])`, +// where the argument to `from` allows inferring this is a `Vec` +fn explicit_default_type<'a>(arg_path: &'a Expr<'_>) -> Option<&'a Ty<'a>> { + if let ExprKind::Path(QPath::TypeRelative(ty, _)) = &arg_path.kind { + Some(ty) + } else { + None + } +} + fn is_local_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>, ref_expr: &Expr<'_>) -> bool { macro_backtrace(expr.span).next().map_or(false, |call| { cx.tcx.is_diagnostic_item(sym::vec_macro, call.def_id) && call.span.eq_ctxt(ref_expr.span) @@ -100,26 +136,23 @@ impl<'tcx> Visitor<'tcx> for InferVisitor { } fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match get_parent_node(cx.tcx, expr.hir_id) { - Some(Node::Local(Local { ty: Some(ty), .. })) => { + match cx.tcx.parent_hir_node(expr.hir_id) { + Node::Local(Local { ty: Some(ty), .. }) => { let mut v = InferVisitor::default(); v.visit_ty(ty); !v.0 }, - Some( - Node::Expr(Expr { + Node::Expr(Expr { + kind: ExprKind::Call(path, args), + .. + }) + | Node::Block(Block { + expr: Some(Expr { kind: ExprKind::Call(path, args), .. - }) - | Node::Block(Block { - expr: - Some(Expr { - kind: ExprKind::Call(path, args), - .. - }), - .. }), - ) => { + .. + }) => { if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) && let Some(sig) = expr_sig(cx, path) && let Some(input) = sig.input(index) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 1543ae803..ab89bb2f5 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -131,8 +131,7 @@ pub(super) fn check( let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),)); let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { - (false, false) if from_nbits > to_nbits => "", - (true, false) if from_nbits > to_nbits => "", + (_, false) if from_nbits > to_nbits => "", (false, true) if from_nbits > 64 => "", (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers", _ => return, diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 1df5a25f6..8fd95d965 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -1,15 +1,47 @@ +use std::convert::Infallible; +use std::ops::ControlFlow; + use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{clip, method_chain_args, sext}; +use clippy_utils::visitors::{for_each_expr, Descend}; +use clippy_utils::{method_chain_args, sext}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, UintTy}; +use rustc_middle::ty::{self, Ty}; use super::CAST_SIGN_LOSS; -const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; +/// A list of methods that can never return a negative value. +/// Includes methods that panic rather than returning a negative value. +/// +/// Methods that can overflow and return a negative value must not be included in this list, +/// because casting their return values can still result in sign loss. +const METHODS_RET_POSITIVE: &[&str] = &[ + "checked_abs", + "saturating_abs", + "isqrt", + "checked_isqrt", + "rem_euclid", + "checked_rem_euclid", + "wrapping_rem_euclid", +]; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { +/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details. +/// +/// Methods that can overflow and return a negative value must not be included in this list, +/// because casting their return values can still result in sign loss. +const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"]; + +/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value. +const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"]; + +pub(super) fn check<'cx>( + cx: &LateContext<'cx>, + expr: &Expr<'_>, + cast_op: &Expr<'_>, + cast_from: Ty<'cx>, + cast_to: Ty<'_>, +) { if should_lint(cx, cast_op, cast_from, cast_to) { span_lint( cx, @@ -20,35 +52,27 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c } } -fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { +fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool { match (cast_from.is_integral(), cast_to.is_integral()) { (true, true) => { if !cast_from.is_signed() || cast_to.is_signed() { return false; } - // Don't lint if `cast_op` is known to be positive. + // Don't lint if `cast_op` is known to be positive, ignoring overflow. if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) { return false; } - let (mut uncertain_count, mut negative_count) = (0, 0); - // Peel off possible binary expressions, e.g. x * x * y => [x, x, y] - let Some(exprs) = exprs_with_selected_binop_peeled(cast_op) else { - // Assume cast sign lose if we cannot determine the sign of `cast_op` - return true; - }; - for expr in exprs { - let ty = cx.typeck_results().expr_ty(expr); - match expr_sign(cx, expr, ty) { - Sign::Negative => negative_count += 1, - Sign::Uncertain => uncertain_count += 1, - Sign::ZeroOrPositive => (), - }; + if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) { + return false; } - // Lint if there are odd number of uncertain or negative results - uncertain_count % 2 == 1 || negative_count % 2 == 1 + if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) { + return false; + } + + true }, (false, true) => !cast_to.is_signed(), @@ -57,7 +81,13 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast } } -fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option { +fn get_const_signed_int_eval<'cx>( + cx: &LateContext<'cx>, + expr: &Expr<'_>, + ty: impl Into>>, +) -> Option { + let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); + if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? && let ty::Int(ity) = *ty.kind() { @@ -66,29 +96,52 @@ fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Opti None } +fn get_const_unsigned_int_eval<'cx>( + cx: &LateContext<'cx>, + expr: &Expr<'_>, + ty: impl Into>>, +) -> Option { + let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); + + if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? + && let ty::Uint(_ity) = *ty.kind() + { + return Some(n); + } + None +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Sign { ZeroOrPositive, Negative, Uncertain, } -fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign { +fn expr_sign<'cx>(cx: &LateContext<'cx>, expr: &Expr<'_>, ty: impl Into>>) -> Sign { // Try evaluate this expr first to see if it's positive - if let Some(val) = get_const_int_eval(cx, expr, ty) { + if let Some(val) = get_const_signed_int_eval(cx, expr, ty) { return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative }; } + if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) { + return Sign::ZeroOrPositive; + } + // Calling on methods that always return non-negative values. if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind { let mut method_name = path.ident.name.as_str(); - if method_name == "unwrap" - && let Some(arglist) = method_chain_args(expr, &["unwrap"]) + // Peel unwrap(), expect(), etc. + while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name) + && let Some(arglist) = method_chain_args(expr, &[found_name]) && let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind { + // The original type has changed, but we can't use `ty` here anyway, because it has been + // moved. method_name = inner_path.ident.name.as_str(); } - if method_name == "pow" + if METHODS_POW.iter().any(|&name| method_name == name) && let [arg] = args { return pow_call_result_sign(cx, caller, arg); @@ -100,53 +153,182 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign { Sign::Uncertain } -/// Return the sign of the `pow` call's result. +/// Return the sign of the `pow` call's result, ignoring overflow. /// -/// If the caller is a positive number, the result is always positive, -/// If the `power_of` is a even number, the result is always positive as well, -/// Otherwise a [`Sign::Uncertain`] will be returned. -fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign { - let caller_ty = cx.typeck_results().expr_ty(caller); - if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty) - && caller_val >= 0 - { - return Sign::ZeroOrPositive; +/// If the base is positive, the result is always positive. +/// If the exponent is a even number, the result is always positive, +/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always +/// negative. +/// +/// Otherwise, returns [`Sign::Uncertain`]. +fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign { + let base_sign = expr_sign(cx, base, None); + + // Rust's integer pow() functions take an unsigned exponent. + let exponent_val = get_const_unsigned_int_eval(cx, exponent, None); + let exponent_is_even = exponent_val.map(|val| val % 2 == 0); + + match (base_sign, exponent_is_even) { + // Non-negative bases always return non-negative results, ignoring overflow. + (Sign::ZeroOrPositive, _) | + // Any base raised to an even exponent is non-negative. + // These both hold even if we don't know the value of the base. + (_, Some(true)) + => Sign::ZeroOrPositive, + + // A negative base raised to an odd exponent is non-negative. + (Sign::Negative, Some(false)) => Sign::Negative, + + // Negative/unknown base to an unknown exponent, or unknown base to an odd exponent. + // Could be negative or positive depending on the actual values. + (Sign::Negative | Sign::Uncertain, None) | + (Sign::Uncertain, Some(false)) => Sign::Uncertain, + } +} + +/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`], +/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details. +/// +/// Returns the sign of the list of peeled expressions. +fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { + let mut negative_count = 0; + + // Peel off possible binary expressions, for example: + // x * x / y => [x, x, y] + // a % b => [a] + let exprs = exprs_with_muldiv_binop_peeled(expr); + for expr in exprs { + match expr_sign(cx, expr, None) { + Sign::Negative => negative_count += 1, + // A mul/div is: + // - uncertain if there are any uncertain values (because they could be negative or positive), + Sign::Uncertain => return Sign::Uncertain, + Sign::ZeroOrPositive => (), + }; } - if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of) - && clip(cx.tcx, n, UintTy::U32) % 2 == 0 - { - return Sign::ZeroOrPositive; + // A mul/div is: + // - negative if there are an odd number of negative values, + // - positive or zero otherwise. + if negative_count % 2 == 1 { + Sign::Negative + } else { + Sign::ZeroOrPositive + } +} + +/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive. +/// See [`exprs_with_add_binop_peeled()`] for details. +/// +/// Returns the sign of the list of peeled expressions. +fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { + let mut negative_count = 0; + let mut positive_count = 0; + + // Peel off possible binary expressions, for example: + // a + b + c => [a, b, c] + let exprs = exprs_with_add_binop_peeled(expr); + for expr in exprs { + match expr_sign(cx, expr, None) { + Sign::Negative => negative_count += 1, + // A sum is: + // - uncertain if there are any uncertain values (because they could be negative or positive), + Sign::Uncertain => return Sign::Uncertain, + Sign::ZeroOrPositive => positive_count += 1, + }; } - Sign::Uncertain + // A sum is: + // - positive or zero if there are only positive (or zero) values, + // - negative if there are only negative (or zero) values, or + // - uncertain if there are both. + // We could split Zero out into its own variant, but we don't yet. + if negative_count == 0 { + Sign::ZeroOrPositive + } else if positive_count == 0 { + Sign::Negative + } else { + Sign::Uncertain + } } /// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`], -/// which the result could always be positive under certain condition. +/// where the result depends on: +/// - the number of negative values in the entire expression, or +/// - the number of negative values on the left hand side of the expression. +/// Ignores overflow. /// -/// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will -/// return `None` -fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option>> { - #[inline] - fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> { - match expr.kind { - ExprKind::Binary(op, lhs, rhs) => { - if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) { - collect_operands(lhs, operands); - operands.push(rhs); - } else { - // Things are complicated when there are other binary ops exist, - // abort checking by returning `None` for now. - return None; - } - }, - _ => operands.push(expr), - } - Some(()) - } - +/// +/// Expressions using other operators are preserved, so we can try to evaluate them later. +fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> { let mut res = vec![]; - collect_operands(expr, &mut res)?; - Some(res) + + for_each_expr(expr, |sub_expr| -> ControlFlow { + // We don't check for mul/div/rem methods here, but we could. + if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind { + if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) { + // For binary operators where both sides contribute to the sign of the result, + // collect all their operands, recursively. This ignores overflow. + ControlFlow::Continue(Descend::Yes) + } else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) { + // For binary operators where the left hand side determines the sign of the result, + // only collect that side, recursively. Overflow panics, so this always holds. + // + // Large left shifts turn negatives into zeroes, so we can't use it here. + // + // > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend + // > ... + // > Arithmetic right shift on signed integer types + // https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators + + // We want to descend into the lhs, but skip the rhs. + // That's tricky to do using for_each_expr(), so we just keep the lhs intact. + res.push(lhs); + ControlFlow::Continue(Descend::No) + } else { + // The sign of the result of other binary operators depends on the values of the operands, + // so try to evaluate the expression. + res.push(sub_expr); + ControlFlow::Continue(Descend::No) + } + } else { + // For other expressions, including unary operators and constants, try to evaluate the expression. + res.push(sub_expr); + ControlFlow::Continue(Descend::No) + } + }); + + res +} + +/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on: +/// - all the expressions being positive, or +/// - all the expressions being negative. +/// Ignores overflow. +/// +/// Expressions using other operators are preserved, so we can try to evaluate them later. +fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> { + let mut res = vec![]; + + for_each_expr(expr, |sub_expr| -> ControlFlow { + // We don't check for add methods here, but we could. + if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind { + if matches!(op.node, BinOpKind::Add) { + // For binary operators where both sides contribute to the sign of the result, + // collect all their operands, recursively. This ignores overflow. + ControlFlow::Continue(Descend::Yes) + } else { + // The sign of the result of other binary operators depends on the values of the operands, + // so try to evaluate the expression. + res.push(sub_expr); + ControlFlow::Continue(Descend::No) + } + } else { + // For other expressions, including unary operators and constants, try to evaluate the expression. + res.push(sub_expr); + ControlFlow::Continue(Descend::No) + } + }); + + res } diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index d600d2aec..9d5a48633 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_no_std_crate; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; +use clippy_utils::{expr_use_ctxt, is_no_std_crate, ExprUseNode}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -9,7 +9,12 @@ use rustc_middle::ty::{self, TypeAndMut}; use super::REF_AS_PTR; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + cast_expr: &'tcx Expr<'_>, + cast_to_hir_ty: &Ty<'_>, +) { let (cast_from, cast_to) = ( cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr), @@ -17,6 +22,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind() + && let Some(use_cx) = expr_use_ctxt(cx, expr) + // TODO: only block the lint if `cast_expr` is a temporary + && !matches!(use_cx.node, ExprUseNode::Local(_) | ExprUseNode::ConstStatic(_)) { let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let fn_name = match to_mutbl { diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index b4a23d0d4..cc513d46b 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::numeric_literal::NumericLiteral; use clippy_utils::source::snippet_opt; use clippy_utils::visitors::{for_each_expr, Visitable}; -use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local}; +use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local}; use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -264,8 +264,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx } // Local usage } else if let Res::Local(hir_id) = res - && let Some(parent) = get_parent_node(cx.tcx, hir_id) - && let Node::Local(l) = parent + && let Node::Local(l) = cx.tcx.parent_hir_node(hir_id) { if let Some(e) = l.init && is_cast_from_ty_alias(cx, e, cast_from) diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index d0c989cff..d820413e1 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::visitors::for_each_expr_with_closures; -use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id}; +use clippy_utils::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -94,7 +94,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc // `id` appearing in the left-hand side of an assignment is not a read access: // // id = ...; // Not reading `id`. - if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) + if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id) && let ExprKind::Assign(lhs, ..) = parent.kind && path_to_local_id(lhs, id) { @@ -108,7 +108,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc // Only assuming this for "official" methods defined on the type. For methods defined in extension // traits (identified as local, based on the orphan rule), pessimistically assume that they might // have side effects, so consider them a read. - if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) + if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id) && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind && path_to_local_id(receiver, id) && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) @@ -117,7 +117,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc // The method call is a statement, so the return value is not used. That's not a read access: // // id.foo(args); - if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) { + if let Node::Stmt(..) = cx.tcx.parent_hir_node(parent.hir_id) { return ControlFlow::Continue(()); } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 0a5baabd9..fb3ae2457 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -51,6 +51,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO, crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO, crate::attrs::DEPRECATED_CFG_ATTR_INFO, + crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO, crate::attrs::DEPRECATED_SEMVER_INFO, crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, @@ -59,6 +60,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::attrs::MISMATCHED_TARGET_OS_INFO, crate::attrs::NON_MINIMAL_CFG_INFO, crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO, + crate::attrs::UNNECESSARY_CLIPPY_CFG_INFO, crate::attrs::USELESS_ATTRIBUTE_INFO, crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO, crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO, @@ -137,6 +139,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_LINK_WITH_QUOTES_INFO, crate::doc::DOC_MARKDOWN_INFO, + crate::doc::EMPTY_DOCS_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO, crate::doc::MISSING_SAFETY_DOC_INFO, @@ -453,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::UNNECESSARY_FILTER_MAP_INFO, crate::methods::UNNECESSARY_FIND_MAP_INFO, crate::methods::UNNECESSARY_FOLD_INFO, + crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO, crate::methods::UNNECESSARY_JOIN_INFO, crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, @@ -497,6 +501,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::module_style::MOD_MODULE_FILES_INFO, crate::module_style::SELF_NAMED_MODULE_FILES_INFO, crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, + crate::multiple_bound_locations::MULTIPLE_BOUND_LOCATIONS_INFO, crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_mut::MUT_MUT_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 0ddfeaa0a..560b2acc1 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -2,9 +2,7 @@ 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::{implements_trait, is_manually_drop, peel_mid_ty_refs}; -use clippy_utils::{ - expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, -}; +use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode}; use core::mem; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; @@ -1008,8 +1006,8 @@ fn report<'tcx>( data.first_expr.span, state.msg, |diag| { - let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) { - Some(Node::Expr(e)) => match e.kind { + let (precedence, calls_field) = match cx.tcx.parent_hir_node(data.first_expr.hir_id) { + Node::Expr(e) => match e.kind { ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false), ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))), _ => (e.precedence().order(), false), diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 656b3d9bf..75379cb4e 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -1,13 +1,16 @@ use clippy_config::types::DisallowedPath; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::macros::macro_backtrace; use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashSet; +use rustc_errors::DiagnosticBuilder; use rustc_hir::def_id::DefIdMap; -use rustc_hir::{Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty}; +use rustc_hir::{ + Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::{ExpnId, Span}; +use rustc_span::{ExpnId, MacroKind, Span}; declare_clippy_lint! { /// ### What it does @@ -57,6 +60,10 @@ pub struct DisallowedMacros { conf_disallowed: Vec, disallowed: DefIdMap, seen: FxHashSet, + + // Track the most recently seen node that can have a `derive` attribute. + // Needed to use the correct lint level. + derive_src: Option, } impl DisallowedMacros { @@ -65,10 +72,11 @@ impl DisallowedMacros { conf_disallowed, disallowed: DefIdMap::default(), seen: FxHashSet::default(), + derive_src: None, } } - fn check(&mut self, cx: &LateContext<'_>, span: Span) { + fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option) { if self.conf_disallowed.is_empty() { return; } @@ -80,18 +88,26 @@ impl DisallowedMacros { if let Some(&index) = self.disallowed.get(&mac.def_id) { let conf = &self.conf_disallowed[index]; - - span_lint_and_then( - cx, - DISALLOWED_MACROS, - mac.span, - &format!("use of a disallowed macro `{}`", conf.path()), - |diag| { - if let Some(reason) = conf.reason() { - diag.note(reason); - } - }, - ); + let msg = format!("use of a disallowed macro `{}`", conf.path()); + let add_note = |diag: &mut DiagnosticBuilder<'_, _>| { + if let Some(reason) = conf.reason() { + diag.note(reason); + } + }; + if matches!(mac.kind, MacroKind::Derive) + && let Some(derive_src) = derive_src + { + span_lint_hir_and_then( + cx, + DISALLOWED_MACROS, + cx.tcx.local_def_id_to_hir_id(derive_src.def_id), + mac.span, + &msg, + add_note, + ); + } else { + span_lint_and_then(cx, DISALLOWED_MACROS, mac.span, &msg, add_note); + } } } } @@ -110,49 +126,57 @@ impl LateLintPass<'_> for DisallowedMacros { } fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - self.check(cx, expr.span); + self.check(cx, expr.span, None); // `$t + $t` can have the context of $t, check also the span of the binary operator if let ExprKind::Binary(op, ..) = expr.kind { - self.check(cx, op.span); + self.check(cx, op.span, None); } } fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { - self.check(cx, stmt.span); + self.check(cx, stmt.span, None); } fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) { - self.check(cx, ty.span); + self.check(cx, ty.span, None); } fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { - self.check(cx, pat.span); + self.check(cx, pat.span, None); } fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - self.check(cx, item.span); - self.check(cx, item.vis_span); + self.check(cx, item.span, self.derive_src); + self.check(cx, item.vis_span, None); + + if matches!( + item.kind, + ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..) + ) && macro_backtrace(item.span).all(|m| !matches!(m.kind, MacroKind::Derive)) + { + self.derive_src = Some(item.owner_id); + } } fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) { - self.check(cx, item.span); - self.check(cx, item.vis_span); + self.check(cx, item.span, None); + self.check(cx, item.vis_span, None); } fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { - self.check(cx, item.span); - self.check(cx, item.vis_span); + self.check(cx, item.span, None); + self.check(cx, item.vis_span, None); } fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { - self.check(cx, item.span); + self.check(cx, item.span, None); } fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { - self.check(cx, path.span); + self.check(cx, path.span, None); } fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { - self.check(cx, attr.span); + self.check(cx, attr.span, self.derive_src); } } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 2b4ce6ddf..9af34c3a7 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -19,7 +19,8 @@ use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_resolve::rustdoc::{ - add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, DocFragment, + add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, span_of_fragments, + DocFragment, }; use rustc_session::impl_lint_pass; use rustc_span::edition::Edition; @@ -338,6 +339,30 @@ declare_clippy_lint! { "suspicious usage of (outer) doc comments" } +declare_clippy_lint! { + /// ### What it does + /// Detects documentation that is empty. + /// ### Why is this bad? + /// Empty docs clutter code without adding value, reducing readability and maintainability. + /// ### Example + /// ```no_run + /// /// + /// fn returns_true() -> bool { + /// true + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn returns_true() -> bool { + /// true + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub EMPTY_DOCS, + suspicious, + "docstrings exist but documentation is empty" +} + #[derive(Clone)] pub struct Documentation { valid_idents: FxHashSet, @@ -364,7 +389,8 @@ impl_lint_pass!(Documentation => [ NEEDLESS_DOCTEST_MAIN, TEST_ATTR_IN_DOCTEST, UNNECESSARY_SAFETY_DOC, - SUSPICIOUS_DOC_COMMENTS + SUSPICIOUS_DOC_COMMENTS, + EMPTY_DOCS, ]); impl<'tcx> LateLintPass<'tcx> for Documentation { @@ -373,11 +399,22 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { check_attrs(cx, &self.valid_idents, attrs); } + fn check_variant(&mut self, cx: &LateContext<'tcx>, variant: &'tcx hir::Variant<'tcx>) { + let attrs = cx.tcx.hir().attrs(variant.hir_id); + check_attrs(cx, &self.valid_idents, attrs); + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, variant: &'tcx hir::FieldDef<'tcx>) { + let attrs = cx.tcx.hir().attrs(variant.hir_id); + check_attrs(cx, &self.valid_idents, attrs); + } + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let attrs = cx.tcx.hir().attrs(item.hir_id()); let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return; }; + match item.kind { hir::ItemKind::Fn(ref sig, _, body_id) => { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { @@ -502,13 +539,23 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ suspicious_doc_comments::check(cx, attrs); let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true); - let mut doc = String::new(); - for fragment in &fragments { - add_doc_fragment(&mut doc, fragment); - } + let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| { + add_doc_fragment(&mut acc, fragment); + acc + }); doc.pop(); - if doc.is_empty() { + if doc.trim().is_empty() { + if let Some(span) = span_of_fragments(&fragments) { + span_lint_and_help( + cx, + EMPTY_DOCS, + span, + "empty doc comment", + None, + "consider removing or filling it", + ); + } return Some(DocHeaders::default()); } diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 124d78fc4..bf6f54c1e 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::is_must_use_func_call; use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; -use clippy_utils::{get_parent_node, is_must_use_func_call}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -144,8 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { // } fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool { if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) { - let parent_node = get_parent_node(cx.tcx, drop_expr.hir_id); - if let Some(Node::Arm(Arm { body, .. })) = &parent_node { + if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) { return body.hir_id == drop_expr.hir_id; } } diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 8af321e4d..61f550ce0 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; use clippy_utils::macros::{ find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, - is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, + is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall, }; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_type_lang_item}; @@ -20,7 +20,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; -use rustc_span::def_id::DefId; use rustc_span::edition::Edition::Edition2021; use rustc_span::{sym, Span, Symbol}; @@ -189,32 +188,18 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { && is_format_macro(cx, macro_call.def_id) && let Some(format_args) = find_format_args(cx, expr, macro_call.expn) { - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece - && let Ok(index) = placeholder.argument.index - && let Some(arg) = format_args.arguments.all_args().get(index) - { - let arg_expr = find_format_arg_expr(expr, arg); + let linter = FormatArgsExpr { + cx, + expr, + macro_call: ¯o_call, + format_args: &format_args, + ignore_mixed: self.ignore_mixed, + }; - check_unused_format_specifier(cx, placeholder, arg_expr); - - if placeholder.format_trait != FormatTrait::Display - || placeholder.format_options != FormatOptions::default() - || is_aliased(&format_args, index) - { - continue; - } - - if let Ok(arg_hir_expr) = arg_expr { - let name = cx.tcx.item_name(macro_call.def_id); - check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr); - check_to_string_in_format_args(cx, name, arg_hir_expr); - } - } - } + linter.check_templates(); if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) { - check_uninlined_args(cx, &format_args, macro_call.span, macro_call.def_id, self.ignore_mixed); + linter.check_uninlined_args(); } } } @@ -222,255 +207,279 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { extract_msrv_attr!(LateContext); } -fn check_unused_format_specifier( - cx: &LateContext<'_>, - placeholder: &FormatPlaceholder, - arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>, -) { - let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs()); +struct FormatArgsExpr<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + macro_call: &'a MacroCall, + format_args: &'a rustc_ast::FormatArgs, + ignore_mixed: bool, +} - let is_format_args = match ty_or_ast_expr { - Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments), - Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)), - }; +impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> { + fn check_templates(&self) { + for piece in &self.format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let Ok(index) = placeholder.argument.index + && let Some(arg) = self.format_args.arguments.all_args().get(index) + { + let arg_expr = find_format_arg_expr(self.expr, arg); - let options = &placeholder.format_options; + self.check_unused_format_specifier(placeholder, arg_expr); - let arg_span = match arg_expr { - Ok(expr) => expr.span, - Err(expr) => expr.span, - }; + if let Ok(arg_expr) = arg_expr + && placeholder.format_trait == FormatTrait::Display + && placeholder.format_options == FormatOptions::default() + && !self.is_aliased(index) + { + let name = self.cx.tcx.item_name(self.macro_call.def_id); + self.check_format_in_format_args(name, arg_expr); + self.check_to_string_in_format_args(name, arg_expr); + } + } + } + } - if let Some(placeholder_span) = placeholder.span - && is_format_args - && *options != FormatOptions::default() - { - span_lint_and_then( - cx, - UNUSED_FORMAT_SPECS, - placeholder_span, - "format specifiers have no effect on `format_args!()`", - |diag| { - let mut suggest_format = |spec| { - let message = format!("for the {spec} to apply consider using `format!()`"); + fn check_unused_format_specifier( + &self, + placeholder: &FormatPlaceholder, + arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>, + ) { + let ty_or_ast_expr = arg_expr.map(|expr| self.cx.typeck_results().expr_ty(expr).peel_refs()); - if let Some(mac_call) = root_macro_call(arg_span) - && cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id) - { - diag.span_suggestion( - cx.sess().source_map().span_until_char(mac_call.span, '!'), - message, - "format", + let is_format_args = match ty_or_ast_expr { + Ok(ty) => is_type_lang_item(self.cx, ty, LangItem::FormatArguments), + Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)), + }; + + let options = &placeholder.format_options; + + let arg_span = match arg_expr { + Ok(expr) => expr.span, + Err(expr) => expr.span, + }; + + if let Some(placeholder_span) = placeholder.span + && is_format_args + && *options != FormatOptions::default() + { + span_lint_and_then( + self.cx, + UNUSED_FORMAT_SPECS, + placeholder_span, + "format specifiers have no effect on `format_args!()`", + |diag| { + let mut suggest_format = |spec| { + let message = format!("for the {spec} to apply consider using `format!()`"); + + if let Some(mac_call) = root_macro_call(arg_span) + && self.cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id) + { + diag.span_suggestion( + self.cx.sess().source_map().span_until_char(mac_call.span, '!'), + message, + "format", + Applicability::MaybeIncorrect, + ); + } else { + diag.help(message); + } + }; + + if options.width.is_some() { + suggest_format("width"); + } + + if options.precision.is_some() { + suggest_format("precision"); + } + + if let Some(format_span) = format_placeholder_format_span(placeholder) { + diag.span_suggestion_verbose( + format_span, + "if the current behavior is intentional, remove the format specifiers", + "", Applicability::MaybeIncorrect, ); - } else { - diag.help(message); } - }; + }, + ); + } + } - if options.width.is_some() { - suggest_format("width"); - } + fn check_uninlined_args(&self) { + if self.format_args.span.from_expansion() { + return; + } + if self.macro_call.span.edition() < Edition2021 + && (is_panic(self.cx, self.macro_call.def_id) || is_assert_macro(self.cx, self.macro_call.def_id)) + { + // panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as + // non-format + return; + } - if options.precision.is_some() { - suggest_format("precision"); - } + let mut fixes = Vec::new(); + // If any of the arguments are referenced by an index number, + // and that argument is not a simple variable and cannot be inlined, + // we cannot remove any other arguments in the format string, + // because the index numbers might be wrong after inlining. + // Example of an un-inlinable format: print!("{}{1}", foo, 2) + for (pos, usage) in self.format_arg_positions() { + if !self.check_one_arg(pos, usage, &mut fixes) { + return; + } + } - if let Some(format_span) = format_placeholder_format_span(placeholder) { - diag.span_suggestion_verbose( - format_span, - "if the current behavior is intentional, remove the format specifiers", - "", - Applicability::MaybeIncorrect, - ); - } + if fixes.is_empty() { + return; + } + + // multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308 + // in those cases, make the code suggestion hidden + let multiline_fix = fixes + .iter() + .any(|(span, _)| self.cx.sess().source_map().is_multiline(*span)); + + // Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`. + fixes.sort_unstable_by_key(|(span, _)| *span); + fixes.dedup_by_key(|(span, _)| *span); + + span_lint_and_then( + self.cx, + UNINLINED_FORMAT_ARGS, + self.macro_call.span, + "variables can be used directly in the `format!` string", + |diag| { + diag.multipart_suggestion_with_style( + "change this to", + fixes, + Applicability::MachineApplicable, + if multiline_fix { CompletelyHidden } else { ShowCode }, + ); }, ); } -} -fn check_uninlined_args( - cx: &LateContext<'_>, - args: &rustc_ast::FormatArgs, - call_site: Span, - def_id: DefId, - ignore_mixed: bool, -) { - if args.span.from_expansion() { - return; - } - if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) { - // panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as - // non-format - return; + fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool { + let index = pos.index.unwrap(); + let arg = &self.format_args.arguments.all_args()[index]; + + if !matches!(arg.kind, FormatArgumentKind::Captured(_)) + && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind + && let [segment] = path.segments.as_slice() + && segment.args.is_none() + && let Some(arg_span) = format_arg_removal_span(self.format_args, index) + && let Some(pos_span) = pos.span + { + let replacement = match usage { + FormatParamUsage::Argument => segment.ident.name.to_string(), + FormatParamUsage::Width => format!("{}$", segment.ident.name), + FormatParamUsage::Precision => format!(".{}$", segment.ident.name), + }; + fixes.push((pos_span, replacement)); + fixes.push((arg_span, String::new())); + true // successful inlining, continue checking + } else { + // Do not continue inlining (return false) in case + // * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)` + // * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already + pos.kind != FormatArgPositionKind::Number + && (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_))) + } } - let mut fixes = Vec::new(); - // If any of the arguments are referenced by an index number, - // and that argument is not a simple variable and cannot be inlined, - // we cannot remove any other arguments in the format string, - // because the index numbers might be wrong after inlining. - // Example of an un-inlinable format: print!("{}{1}", foo, 2) - for (pos, usage) in format_arg_positions(args) { - if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) { + fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) { + let expn_data = arg.span.ctxt().outer_expn_data(); + if expn_data.call_site.from_expansion() { return; } + let Some(mac_id) = expn_data.macro_def_id else { return }; + if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) { + return; + } + span_lint_and_then( + self.cx, + FORMAT_IN_FORMAT_ARGS, + self.macro_call.span, + &format!("`format!` in `{name}!` args"), + |diag| { + diag.help(format!( + "combine the `format!(..)` arguments with the outer `{name}!(..)` call" + )); + diag.help("or consider changing `format!` to `format_args!`"); + }, + ); } - if fixes.is_empty() { - return; - } - - // multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308 - // in those cases, make the code suggestion hidden - let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span)); - - // Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`. - fixes.sort_unstable_by_key(|(span, _)| *span); - fixes.dedup_by_key(|(span, _)| *span); - - span_lint_and_then( - cx, - UNINLINED_FORMAT_ARGS, - call_site, - "variables can be used directly in the `format!` string", - |diag| { - diag.multipart_suggestion_with_style( - "change this to", - fixes, - Applicability::MachineApplicable, - if multiline_fix { CompletelyHidden } else { ShowCode }, - ); - }, - ); -} - -fn check_one_arg( - args: &rustc_ast::FormatArgs, - pos: &FormatArgPosition, - usage: FormatParamUsage, - fixes: &mut Vec<(Span, String)>, - ignore_mixed: bool, -) -> bool { - let index = pos.index.unwrap(); - let arg = &args.arguments.all_args()[index]; - - if !matches!(arg.kind, FormatArgumentKind::Captured(_)) - && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind - && let [segment] = path.segments.as_slice() - && segment.args.is_none() - && let Some(arg_span) = format_arg_removal_span(args, index) - && let Some(pos_span) = pos.span - { - let replacement = match usage { - FormatParamUsage::Argument => segment.ident.name.to_string(), - FormatParamUsage::Width => format!("{}$", segment.ident.name), - FormatParamUsage::Precision => format!(".{}$", segment.ident.name), - }; - fixes.push((pos_span, replacement)); - fixes.push((arg_span, String::new())); - true // successful inlining, continue checking - } else { - // Do not continue inlining (return false) in case - // * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)` - // * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already - pos.kind != FormatArgPositionKind::Number - && (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_))) - } -} - -fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) { - let expn_data = arg.span.ctxt().outer_expn_data(); - if expn_data.call_site.from_expansion() { - return; - } - let Some(mac_id) = expn_data.macro_def_id else { return }; - if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) { - return; - } - span_lint_and_then( - cx, - FORMAT_IN_FORMAT_ARGS, - call_site, - &format!("`format!` in `{name}!` args"), - |diag| { - diag.help(format!( - "combine the `format!(..)` arguments with the outer `{name}!(..)` call" - )); - diag.help("or consider changing `format!` to `format_args!`"); - }, - ); -} - -fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) { - if !value.span.from_expansion() - && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind - && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) - && is_diag_trait_item(cx, method_def_id, sym::ToString) - && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && 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()) - && implements_trait(cx, target, display_trait_id, &[]) - && let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait() - && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) - { - 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, - to_string_span.with_lo(receiver.span.hi()), - &format!("`to_string` applied to a type that implements `Display` in `{name}!` args"), - "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 `{name}!` args"), - "use this", - format!( - "{}{:*>n_needed_derefs$}{receiver_snippet}", - if needs_ref { "&" } else { "" }, - "" - ), - Applicability::MachineApplicable, - ); + fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) { + let cx = self.cx; + if !value.span.from_expansion() + && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind + && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) + && is_diag_trait_item(cx, method_def_id, sym::ToString) + && let receiver_ty = cx.typeck_results().expr_ty(receiver) + && 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()) + && implements_trait(cx, target, display_trait_id, &[]) + && let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait() + && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) + { + 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, + to_string_span.with_lo(receiver.span.hi()), + &format!("`to_string` applied to a type that implements `Display` in `{name}!` args"), + "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 `{name}!` args"), + "use this", + format!( + "{}{:*>n_needed_derefs$}{receiver_snippet}", + if needs_ref { "&" } else { "" }, + "" + ), + Applicability::MachineApplicable, + ); + } } } -} -fn format_arg_positions( - format_args: &rustc_ast::FormatArgs, -) -> impl Iterator { - format_args.template.iter().flat_map(|piece| match piece { - FormatArgsPiece::Placeholder(placeholder) => { - let mut positions = ArrayVec::<_, 3>::new(); + fn format_arg_positions(&self) -> impl Iterator { + self.format_args.template.iter().flat_map(|piece| match piece { + FormatArgsPiece::Placeholder(placeholder) => { + let mut positions = ArrayVec::<_, 3>::new(); - positions.push((&placeholder.argument, FormatParamUsage::Argument)); - if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width { - positions.push((position, FormatParamUsage::Width)); - } - if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision { - positions.push((position, FormatParamUsage::Precision)); - } + positions.push((&placeholder.argument, FormatParamUsage::Argument)); + if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width { + positions.push((position, FormatParamUsage::Width)); + } + if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision { + positions.push((position, FormatParamUsage::Precision)); + } - positions - }, - FormatArgsPiece::Literal(_) => ArrayVec::new(), - }) -} + positions + }, + FormatArgsPiece::Literal(_) => ArrayVec::new(), + }) + } -/// Returns true if the format argument at `index` is referred to by multiple format params -fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool { - format_arg_positions(format_args) - .filter(|(position, _)| position.index == Ok(index)) - .at_most_one() - .is_err() + /// Returns true if the format argument at `index` is referred to by multiple format params + fn is_aliased(&self, index: usize) -> bool { + self.format_arg_positions() + .filter(|(position, _)| position.index == Ok(index)) + .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 9360eb1fa..93517076c 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -7,7 +7,7 @@ use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::symbol::kw; -use rustc_span::{sym, Span, Symbol}; +use rustc_span::{sym, Symbol}; declare_clippy_lint! { /// ### What it does @@ -119,123 +119,132 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl { } fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { - // Assume no nested Impl of Debug and Display within eachother + // Assume no nested Impl of Debug and Display within each other if is_format_trait_impl(cx, impl_item).is_some() { self.format_trait_impl = None; } } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(format_trait_impl) = self.format_trait_impl else { - return; - }; - - if format_trait_impl.name == sym::Display { - check_to_string_in_display(cx, expr); + if let Some(format_trait_impl) = self.format_trait_impl { + let linter = FormatImplExpr { + cx, + expr, + format_trait_impl, + }; + linter.check_to_string_in_display(); + linter.check_self_in_format_args(); + linter.check_print_in_format_impl(); } - - check_self_in_format_args(cx, expr, format_trait_impl); - check_print_in_format_impl(cx, expr, format_trait_impl); } } -fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind - // Get the hir_id of the object we are calling the method on - // Is the method to_string() ? - && path.ident.name == sym::to_string - // Is the method a part of the ToString trait? (i.e. not to_string() implemented - // separately) - && let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && is_diag_trait_item(cx, expr_def_id, sym::ToString) - // Is the method is called on self - && let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind - && let [segment] = path.segments - && segment.ident.name == kw::SelfLower - { - span_lint( - cx, - RECURSIVE_FORMAT_IMPL, - expr.span, - "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", - ); - } +struct FormatImplExpr<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + format_trait_impl: FormatTraitNames, } -fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) { - // Check each arg in format calls - do we ever use Display on self (directly or via deref)? - if let Some(outer_macro) = root_macro_call_first_node(cx, expr) - && let macro_def_id = outer_macro.def_id - && is_format_macro(cx, macro_def_id) - && let Some(format_args) = find_format_args(cx, expr, outer_macro.expn) - { - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece - && let trait_name = match placeholder.format_trait { - FormatTrait::Display => sym::Display, - FormatTrait::Debug => sym::Debug, - FormatTrait::LowerExp => sym!(LowerExp), - FormatTrait::UpperExp => sym!(UpperExp), - FormatTrait::Octal => sym!(Octal), - FormatTrait::Pointer => sym::Pointer, - FormatTrait::Binary => sym!(Binary), - FormatTrait::LowerHex => sym!(LowerHex), - FormatTrait::UpperHex => sym!(UpperHex), +impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> { + fn check_to_string_in_display(&self) { + if self.format_trait_impl.name == sym::Display + && let ExprKind::MethodCall(path, self_arg, ..) = self.expr.kind + // Get the hir_id of the object we are calling the method on + // Is the method to_string() ? + && path.ident.name == sym::to_string + // Is the method a part of the ToString trait? (i.e. not to_string() implemented + // separately) + && let Some(expr_def_id) = self.cx.typeck_results().type_dependent_def_id(self.expr.hir_id) + && is_diag_trait_item(self.cx, expr_def_id, sym::ToString) + // Is the method is called on self + && let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind + && let [segment] = path.segments + && segment.ident.name == kw::SelfLower + { + span_lint( + self.cx, + RECURSIVE_FORMAT_IMPL, + self.expr.span, + "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", + ); + } + } + + fn check_self_in_format_args(&self) { + // Check each arg in format calls - do we ever use Display on self (directly or via deref)? + if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr) + && let macro_def_id = outer_macro.def_id + && is_format_macro(self.cx, macro_def_id) + && let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn) + { + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let trait_name = match placeholder.format_trait { + FormatTrait::Display => sym::Display, + FormatTrait::Debug => sym::Debug, + FormatTrait::LowerExp => sym!(LowerExp), + FormatTrait::UpperExp => sym!(UpperExp), + FormatTrait::Octal => sym!(Octal), + FormatTrait::Pointer => sym::Pointer, + FormatTrait::Binary => sym!(Binary), + FormatTrait::LowerHex => sym!(LowerHex), + FormatTrait::UpperHex => sym!(UpperHex), + } + && trait_name == self.format_trait_impl.name + && let Ok(index) = placeholder.argument.index + && let Some(arg) = format_args.arguments.all_args().get(index) + && let Ok(arg_expr) = find_format_arg_expr(self.expr, arg) + { + self.check_format_arg_self(arg_expr); } - && trait_name == impl_trait.name - && let Ok(index) = placeholder.argument.index - && let Some(arg) = format_args.arguments.all_args().get(index) - && let Ok(arg_expr) = find_format_arg_expr(expr, arg) - { - check_format_arg_self(cx, expr.span, arg_expr, impl_trait); } } } -} -fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) { - // Handle multiple dereferencing of references e.g. &&self - // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) - // Since the argument to fmt is itself a reference: &self - let reference = peel_ref_operators(cx, arg); - let map = cx.tcx.hir(); - // Is the reference self? - if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { - let FormatTraitNames { name, .. } = impl_trait; - span_lint( - cx, - RECURSIVE_FORMAT_IMPL, - span, - &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"), - ); + fn check_format_arg_self(&self, arg: &Expr<'_>) { + // 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(self.cx, arg); + let map = self.cx.tcx.hir(); + // Is the reference self? + if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { + let FormatTraitNames { name, .. } = self.format_trait_impl; + span_lint( + self.cx, + RECURSIVE_FORMAT_IMPL, + self.expr.span, + &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"), + ); + } } -} -fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) { - if let Some(macro_call) = root_macro_call_first_node(cx, expr) - && let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id) - { - let replacement = match name { - sym::print_macro | sym::eprint_macro => "write", - sym::println_macro | sym::eprintln_macro => "writeln", - _ => return, - }; + fn check_print_in_format_impl(&self) { + if let Some(macro_call) = root_macro_call_first_node(self.cx, self.expr) + && let Some(name) = self.cx.tcx.get_diagnostic_name(macro_call.def_id) + { + let replacement = match name { + sym::print_macro | sym::eprint_macro => "write", + sym::println_macro | sym::eprintln_macro => "writeln", + _ => return, + }; - let name = name.as_str().strip_suffix("_macro").unwrap(); + let name = name.as_str().strip_suffix("_macro").unwrap(); - span_lint_and_sugg( - cx, - PRINT_IN_FORMAT_IMPL, - macro_call.span, - &format!("use of `{name}!` in `{}` impl", impl_trait.name), - "replace with", - if let Some(formatter_name) = impl_trait.formatter_name { - format!("{replacement}!({formatter_name}, ..)") - } else { - format!("{replacement}!(..)") - }, - Applicability::HasPlaceholders, - ); + span_lint_and_sugg( + self.cx, + PRINT_IN_FORMAT_IMPL, + macro_call.span, + &format!("use of `{name}!` in `{}` impl", self.format_trait_impl.name), + "replace with", + if let Some(formatter_name) = self.format_trait_impl.formatter_name { + format!("{replacement}!({formatter_name}, ..)") + } else { + format!("{replacement}!(..)") + }, + Applicability::HasPlaceholders, + ); + } } } diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index fab8ffedb..74582f7f1 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -1,11 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::{Applicability, SuggestionStyle}; -use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::intravisit::FnKind; +use rustc_hir::def_id::DefId; use rustc_hir::{ - Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem, - TraitItemKind, TyKind, + GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, TypeBinding, + WherePredicate, }; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; @@ -50,20 +49,17 @@ declare_clippy_lint! { } declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]); -#[allow(clippy::too_many_arguments)] fn emit_lint( cx: &LateContext<'_>, poly_trait: &rustc_hir::PolyTraitRef<'_>, - opaque_ty: &rustc_hir::OpaqueTy<'_>, + bounds: GenericBounds<'_>, index: usize, - // The bindings that were implied + // The bindings that were implied, used for suggestion purposes since removing a bound with associated types + // means we might need to then move it to a different bound implied_bindings: &[rustc_hir::TypeBinding<'_>], - // The original bindings that `implied_bindings` are implied from - implied_by_bindings: &[rustc_hir::TypeBinding<'_>], - implied_by_args: &[GenericArg<'_>], - implied_by_span: Span, + bound: &ImplTraitBound<'_>, ) { - let implied_by = snippet(cx, implied_by_span, ".."); + let implied_by = snippet(cx, bound.span, ".."); span_lint_and_then( cx, @@ -75,10 +71,10 @@ fn emit_lint( // to include the `+` token that is ahead or behind, // so we don't end up with something like `impl + B` or `impl A + ` - let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) { + let implied_span_extended = if let Some(next_bound) = bounds.get(index + 1) { poly_trait.span.to(next_bound.span().shrink_to_lo()) } else if index > 0 - && let Some(prev_bound) = opaque_ty.bounds.get(index - 1) + && let Some(prev_bound) = bounds.get(index - 1) { prev_bound.span().shrink_to_hi().to(poly_trait.span.shrink_to_hi()) } else { @@ -93,17 +89,17 @@ fn emit_lint( // If we're going to suggest removing `Deref<..>`, we'll need to put `` on `DerefMut` let omitted_assoc_tys: Vec<_> = implied_bindings .iter() - .filter(|binding| !implied_by_bindings.iter().any(|b| b.ident == binding.ident)) + .filter(|binding| !bound.bindings.iter().any(|b| b.ident == binding.ident)) .collect(); if !omitted_assoc_tys.is_empty() { // `<>` needs to be added if there aren't yet any generic arguments or bindings - let needs_angle_brackets = implied_by_args.is_empty() && implied_by_bindings.is_empty(); - let insert_span = match (implied_by_args, implied_by_bindings) { + let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty(); + let insert_span = match (bound.args, bound.bindings) { ([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(), ([.., arg], []) => arg.span().shrink_to_hi(), ([], [.., binding]) => binding.span.shrink_to_hi(), - ([], []) => implied_by_span.shrink_to_hi(), + ([], []) => bound.span.shrink_to_hi(), }; let mut associated_tys_sugg = if needs_angle_brackets { @@ -223,111 +219,135 @@ fn is_same_generics<'tcx>( }) } -fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) { - if let FnRetTy::Return(ty) = decl.output - &&let TyKind::OpaqueDef(item_id, ..) = ty.kind - && let item = cx.tcx.hir().item(item_id) - && let ItemKind::OpaqueTy(opaque_ty) = item.kind - // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case - // we can avoid doing a bunch of stuff unnecessarily. - && opaque_ty.bounds.len() > 1 - { - // Get all the (implied) trait predicates in the bounds. - // For `impl Deref + DerefMut` this will contain [`Deref`]. - // The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`. - // N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits. - // Example: - // `impl Deref + DerefMut` is not allowed. - // `DerefMut::Target` needs to match `Deref::Target`. - let implied_bounds: Vec<_> = opaque_ty - .bounds - .iter() - .filter_map(|bound| { - if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound - && let [.., path] = poly_trait.trait_ref.path.segments - && poly_trait.bound_generic_params.is_empty() - && let Some(trait_def_id) = path.res.opt_def_id() - && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates - && !predicates.is_empty() - // If the trait has no supertrait, there is nothing to add. - { - Some((bound.span(), path, predicates, trait_def_id)) - } else { - None - } - }) - .collect(); +struct ImplTraitBound<'tcx> { + /// The span of the bound in the `impl Trait` type + span: Span, + /// The predicates defined in the trait referenced by this bound. This also contains the actual + /// supertrait bounds + predicates: &'tcx [(ty::Clause<'tcx>, Span)], + /// The `DefId` of the trait being referenced by this bound + trait_def_id: DefId, + /// The generic arguments on the `impl Trait` bound + args: &'tcx [GenericArg<'tcx>], + /// The associated types on this bound + bindings: &'tcx [TypeBinding<'tcx>], +} - // Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. - // This involves some extra logic when generic arguments are present, since - // simply comparing trait `DefId`s won't be enough. We also need to compare the generics. - for (index, bound) in opaque_ty.bounds.iter().enumerate() { +/// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds"). +/// +/// For `impl Deref + DerefMut + Eq` this returns `[Deref, PartialEq]`. +/// The `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`, and `PartialEq` comes from +/// `Eq`. +fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) -> Vec> { + bounds + .iter() + .filter_map(|bound| { if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound && let [.., path] = poly_trait.trait_ref.path.segments - && let implied_args = path.args.map_or([].as_slice(), |a| a.args) - && let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings) - && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() - && let Some((implied_by_span, implied_by_args, implied_by_bindings)) = - implied_bounds - .iter() - .find_map(|&(span, implied_by_path, preds, implied_by_def_id)| { - let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args); - let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings); - - preds.iter().find_map(|(clause, _)| { - if let ClauseKind::Trait(tr) = clause.kind().skip_binder() - && tr.def_id() == def_id - && is_same_generics( - cx.tcx, - tr.trait_ref.args, - implied_by_args, - implied_args, - implied_by_def_id, - def_id, - ) - { - Some((span, implied_by_args, implied_by_bindings)) - } else { - None - } - }) - }) + && poly_trait.bound_generic_params.is_empty() + && let Some(trait_def_id) = path.res.opt_def_id() + && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates + // If the trait has no supertrait, there is no need to collect anything from that bound + && !predicates.is_empty() { - emit_lint( - cx, - poly_trait, - opaque_ty, - index, - implied_bindings, - implied_by_bindings, - implied_by_args, - implied_by_span, - ); + Some(ImplTraitBound { + predicates, + args: path.args.map_or([].as_slice(), |p| p.args), + bindings: path.args.map_or([].as_slice(), |p| p.bindings), + trait_def_id, + span: bound.span(), + }) + } else { + None + } + }) + .collect() +} + +/// Given a bound in an `impl Trait` type, looks for a trait in the set of supertraits (previously +/// collected in [`collect_supertrait_bounds`]) that matches (same trait and generic arguments). +fn find_bound_in_supertraits<'a, 'tcx>( + cx: &LateContext<'tcx>, + trait_def_id: DefId, + args: &'tcx [GenericArg<'tcx>], + bounds: &'a [ImplTraitBound<'tcx>], +) -> Option<&'a ImplTraitBound<'tcx>> { + bounds.iter().find(|bound| { + bound.predicates.iter().any(|(clause, _)| { + if let ClauseKind::Trait(tr) = clause.kind().skip_binder() + && tr.def_id() == trait_def_id + { + is_same_generics( + cx.tcx, + tr.trait_ref.args, + bound.args, + args, + bound.trait_def_id, + trait_def_id, + ) + } else { + false + } + }) + }) +} + +fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) { + if bounds.len() == 1 { + // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case + // we can avoid doing a bunch of stuff unnecessarily; there will trivially be + // no duplicate bounds + return; + } + + let supertraits = collect_supertrait_bounds(cx, bounds); + + // Lint all bounds in the `impl Trait` type that we've previously also seen in the set of + // supertraits of each of the bounds. + // This involves some extra logic when generic arguments are present, since + // simply comparing trait `DefId`s won't be enough. We also need to compare the generics. + for (index, bound) in bounds.iter().enumerate() { + if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound + && let [.., path] = poly_trait.trait_ref.path.segments + && let implied_args = path.args.map_or([].as_slice(), |a| a.args) + && let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings) + && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() + && let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits) + // If the implied bound has a type binding that also exists in the implied-by trait, + // then we shouldn't lint. See #11880 for an example. + && let assocs = cx.tcx.associated_items(bound.trait_def_id) + && !implied_bindings.iter().any(|binding| { + assocs + .filter_by_name_unhygienic(binding.ident.name) + .next() + .is_some_and(|assoc| assoc.kind == ty::AssocKind::Type) + }) + { + emit_lint(cx, poly_trait, bounds, index, implied_bindings, bound); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ImpliedBoundsInImpls { + fn check_generics(&mut self, cx: &LateContext<'tcx>, generics: &rustc_hir::Generics<'tcx>) { + for predicate in generics.predicates { + if let WherePredicate::BoundPredicate(predicate) = predicate + // In theory, the origin doesn't really matter, + // we *could* also lint on explicit where clauses written out by the user, + // not just impl trait desugared ones, but that contradicts with the lint name... + && let PredicateOrigin::ImplTrait = predicate.origin + { + check(cx, predicate.bounds); } } } -} -impl LateLintPass<'_> for ImpliedBoundsInImpls { - fn check_fn( - &mut self, - cx: &LateContext<'_>, - _: FnKind<'_>, - decl: &FnDecl<'_>, - _: &Body<'_>, - _: Span, - _: LocalDefId, - ) { - check(cx, decl); - } - fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { - if let TraitItemKind::Fn(sig, ..) = &item.kind { - check(cx, sig.decl); - } - } - fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { - if let ImplItemKind::Fn(sig, ..) = &item.kind { - check(cx, sig.decl); + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &rustc_hir::Ty<'_>) { + if let TyKind::OpaqueDef(item_id, ..) = ty.kind + && let item = cx.tcx.hir().item(item_id) + && let ItemKind::OpaqueTy(opaque_ty) = item.kind + { + check(cx, opaque_ty.bounds); } } } diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index f2f0e7d42..cd000fcd1 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -1,14 +1,15 @@ use clippy_config::msrvs::Msrv; use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_in_test_function; use rustc_attr::{StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_semver::RustcVersion; use rustc_session::impl_lint_pass; use rustc_span::def_id::DefId; -use rustc_span::Span; +use rustc_span::{ExpnKind, Span}; declare_clippy_lint! { /// ### What it does @@ -81,13 +82,18 @@ impl IncompatibleMsrv { version } - fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) { + fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) { if def_id.is_local() { // We don't check local items since their MSRV is supposed to always be valid. return; } let version = self.get_def_id_version(cx.tcx, def_id); - if self.msrv.meets(version) { + if self.msrv.meets(version) || is_in_test_function(cx.tcx, node) { + return; + } + if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind { + // Desugared expressions get to cheat and stability is ignored. + // Intentionally not using `.from_expansion()`, since we do still care about macro expansions return; } self.emit_lint_for(cx, span, version); @@ -117,14 +123,14 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { match expr.kind { ExprKind::MethodCall(_, _, _, span) => { if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - self.emit_lint_if_under_msrv(cx, method_did, span); + self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); } }, ExprKind::Call(call, [_]) => { if let ExprKind::Path(qpath) = call.kind && let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id() { - self.emit_lint_if_under_msrv(cx, path_def_id, call.span); + self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span); } }, _ => {}, diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 41e9d5b1c..5b5eb355f 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -255,7 +255,9 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { && let hir::Node::Expr(maybe_addrof_expr) = cx.tcx.parent_hir_node(parent_id) && let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind { - use_info.index_use.push((index_value, cx.tcx.hir().span(parent_expr.hir_id))); + use_info + .index_use + .push((index_value, cx.tcx.hir().span(parent_expr.hir_id))); return; } diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 391db0b0d..35fcd8cdd 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -174,6 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { // only `usize` index is legal in rust array index // leave other type to rustc if let Constant::Int(off) = constant + && off <= usize::MAX as u128 && let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind() && *utype == ty::UintTy::Usize && let ty::Array(_, s) = ty.kind() diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 276c1abb6..0b4c416d9 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -385,7 +385,6 @@ impl LateLintPass<'_> for ItemNameRepetitions { assert!(last.is_some()); } - #[expect(clippy::similar_names)] fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { let item_name = item.ident.name.as_str(); let item_camel = to_camel_case(item_name); diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index b9fad7265..32ae6be56 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::get_parent_node; use clippy_utils::ty::implements_trait; use rustc_hir::def_id::LocalDefId; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; @@ -56,8 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator { let name = item.ident.name.as_str(); if matches!(name, "iter" | "iter_mut") && !matches!( - get_parent_node(cx.tcx, item.hir_id()), - Some(Node::Item(Item { kind: ItemKind::Impl(i), .. })) if i.of_trait.is_some() + cx.tcx.parent_hir_node(item.hir_id()), + Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some() ) { if let ImplItemKind::Fn(fn_sig, _) = &item.kind { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 14bd82f9c..76e759683 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -14,7 +14,7 @@ clippy::missing_docs_in_private_items, clippy::must_use_candidate, rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic, + rustc::untranslatable_diagnostic )] #![warn(trivial_casts, trivial_numeric_casts)] // warn on lints, that are included in `rust-lang/rust`s bootstrap @@ -231,6 +231,7 @@ mod missing_trait_methods; mod mixed_read_write_in_expression; mod module_style; mod multi_assignments; +mod multiple_bound_locations; mod multiple_unsafe_ops_per_block; mod mut_key; mod mut_mut; @@ -1067,7 +1068,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| { Box::new(single_call_fn::SingleCallFn { avoid_breaking_exported_api, - def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(), + def_id_to_usage: rustc_data_structures::fx::FxIndexMap::default(), }) }); store.register_early_pass(move || { @@ -1116,6 +1117,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv()))); store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); + store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index 5e099f1e7..5b5bb88c1 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -1,17 +1,18 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{fn_def_id, is_lint_allowed}; +use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use hir::intravisit::{walk_expr, Visitor}; use hir::{Expr, ExprKind, FnRetTy, FnSig, Node}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; use super::INFINITE_LOOP; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &Expr<'_>, + expr: &Expr<'tcx>, loop_block: &'tcx hir::Block<'_>, label: Option