Auto merge of #127609 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`
This commit is contained in:
bors 2024-07-11 20:41:24 +00:00
commit 5315cbe15b
191 changed files with 4157 additions and 2504 deletions

View File

@ -58,7 +58,7 @@ jobs:
- name: Run lintcheck - name: Run lintcheck
if: steps.cache-json.outputs.cache-hit != 'true' if: steps.cache-json.outputs.cache-hit != 'true'
run: ./target/debug/lintcheck --format json run: ./target/debug/lintcheck --format json --warn-all
- name: Upload base JSON - name: Upload base JSON
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -86,7 +86,7 @@ jobs:
run: cargo build --manifest-path=lintcheck/Cargo.toml run: cargo build --manifest-path=lintcheck/Cargo.toml
- name: Run lintcheck - name: Run lintcheck
run: ./target/debug/lintcheck --format json run: ./target/debug/lintcheck --format json --warn-all
- name: Upload head JSON - name: Upload head JSON
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -5236,6 +5236,7 @@ Released 2018-09-13
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local [`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code [`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow [`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
[`byte_char_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len [`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
@ -5253,6 +5254,7 @@ Released 2018-09-13
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes [`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts [`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts
[`cfg_not_test`]: https://rust-lang.github.io/rust-clippy/master/index.html#cfg_not_test
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
@ -5539,6 +5541,7 @@ Released 2018-09-13
[`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns [`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid [`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain [`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
[`manual_rotate`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rotate
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
[`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation [`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
@ -5587,6 +5590,7 @@ Released 2018-09-13
[`missing_assert_message`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_assert_message [`missing_assert_message`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_assert_message
[`missing_asserts_for_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_asserts_for_indexing [`missing_asserts_for_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_asserts_for_indexing
[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn [`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
[`missing_const_for_thread_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_thread_local
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items [`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
[`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames [`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames
[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc [`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
@ -5701,6 +5705,7 @@ Released 2018-09-13
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn [`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params [`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
[`panicking_overflow_checks`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_overflow_checks
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap [`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
[`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields [`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl [`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
@ -5797,6 +5802,7 @@ Released 2018-09-13
[`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block [`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block
[`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix [`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse [`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
[`set_contains_or_insert`]: https://rust-lang.github.io/rust-clippy/master/index.html#set_contains_or_insert
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same [`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated [`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated

View File

@ -348,6 +348,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
* [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names) * [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
* [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value) * [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
* [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist) * [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
* [`needless_pass_by_ref_mut`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_ref_mut)
* [`option_option`](https://rust-lang.github.io/rust-clippy/master/index.html#option_option) * [`option_option`](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
* [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer) * [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer)
* [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) * [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
@ -454,7 +455,7 @@ default configuration of Clippy. By default, any configuration will replace the
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` **Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
--- ---
**Affected lints:** **Affected lints:**

View File

@ -1,5 +1,9 @@
avoid-breaking-exported-api = false avoid-breaking-exported-api = false
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
[[disallowed-methods]] [[disallowed-methods]]
path = "rustc_lint::context::LintContext::span_lint" path = "rustc_lint::context::LintContext::span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"

View File

@ -31,6 +31,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"OCaml", "OCaml",
"OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
"WebGL", "WebGL2", "WebGPU", "WebGL", "WebGL2", "WebGPU",
"WebP", "OpenExr", "YCbCr", "sRGB",
"TensorFlow", "TensorFlow",
"TrueType", "TrueType",
"iOS", "macOS", "FreeBSD", "iOS", "macOS", "FreeBSD",
@ -262,7 +263,7 @@ define_Conf! {
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ``` /// ```
(arithmetic_side_effects_allowed_unary: FxHashSet<String> = <_>::default()), (arithmetic_side_effects_allowed_unary: FxHashSet<String> = <_>::default()),
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN. /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN, NEEDLESS_PASS_BY_REF_MUT.
/// ///
/// Suppress lints whenever the suggested change would cause breakage for other crates. /// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true), (avoid_breaking_exported_api: bool = true),

View File

@ -25,8 +25,8 @@ msrv_aliases! {
1,68,0 { PATH_MAIN_SEPARATOR_STR } 1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
1,63,0 { CLONE_INTO } 1,63,0 { CLONE_INTO }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_FN }
1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST } 1,59,0 { THREAD_LOCAL_CONST_INIT }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
1,56,0 { CONST_FN_UNION } 1,56,0 { CONST_FN_UNION }
1,55,0 { SEEK_REWIND } 1,55,0 { SEEK_REWIND }

View File

@ -273,7 +273,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
result.push_str(&if enable_msrv { result.push_str(&if enable_msrv {
formatdoc!( formatdoc!(
r#" r#"
use clippy_utils::msrvs::{{self, Msrv}}; use clippy_config::msrvs::{{self, Msrv}};
{pass_import} {pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}}; use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
@ -399,7 +399,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
let _: fmt::Result = writedoc!( let _: fmt::Result = writedoc!(
lint_file_contents, lint_file_contents,
r#" r#"
use clippy_utils::msrvs::{{self, Msrv}}; use clippy_config::msrvs::{{self, Msrv}};
use rustc_lint::{{{context_import}, LintContext}}; use rustc_lint::{{{context_import}, LintContext}};
use super::{name_upper}; use super::{name_upper};

View File

@ -6,7 +6,6 @@ use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -41,61 +40,80 @@ impl AlmostCompleteRange {
} }
impl EarlyLintPass for AlmostCompleteRange { impl EarlyLintPass for AlmostCompleteRange {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind { if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind
let ctxt = e.span.ctxt(); && is_incomplete_range(start, end)
let sugg = if let Some(start) = walk_span_to_context(start.span, ctxt) && !in_external_macro(cx.sess(), e.span)
&& let Some(end) = walk_span_to_context(end.span, ctxt) {
&& self.msrv.meets(msrvs::RANGE_INCLUSIVE) span_lint_and_then(
{ cx,
Some((trim_span(cx.sess().source_map(), start.between(end)), "..=")) ALMOST_COMPLETE_RANGE,
} else { e.span,
None "almost complete ascii range",
}; |diag| {
check_range(cx, e.span, start, end, sugg); let ctxt = e.span.ctxt();
if let Some(start) = walk_span_to_context(start.span, ctxt)
&& let Some(end) = walk_span_to_context(end.span, ctxt)
&& self.msrv.meets(msrvs::RANGE_INCLUSIVE)
{
diag.span_suggestion(
trim_span(cx.sess().source_map(), start.between(end)),
"use an inclusive range",
"..=".to_owned(),
Applicability::MaybeIncorrect,
);
}
},
);
} }
} }
fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &Pat) { fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &Pat) {
if let PatKind::Range(Some(start), Some(end), kind) = &p.kind if let PatKind::Range(Some(start), Some(end), kind) = &p.kind
&& matches!(kind.node, RangeEnd::Excluded) && matches!(kind.node, RangeEnd::Excluded)
&& is_incomplete_range(start, end)
&& !in_external_macro(cx.sess(), p.span)
{ {
let sugg = if self.msrv.meets(msrvs::RANGE_INCLUSIVE) { span_lint_and_then(
"..=" cx,
} else { ALMOST_COMPLETE_RANGE,
"..." p.span,
}; "almost complete ascii range",
check_range(cx, p.span, start, end, Some((kind.span, sugg))); |diag| {
diag.span_suggestion(
kind.span,
"use an inclusive range",
if self.msrv.meets(msrvs::RANGE_INCLUSIVE) {
"..=".to_owned()
} else {
"...".to_owned()
},
Applicability::MaybeIncorrect,
);
},
);
} }
} }
extract_msrv_attr!(EarlyContext); extract_msrv_attr!(EarlyContext);
} }
fn check_range(cx: &EarlyContext<'_>, span: Span, start: &Expr, end: &Expr, sugg: Option<(Span, &str)>) { fn is_incomplete_range(start: &Expr, end: &Expr) -> bool {
if let ExprKind::Lit(start_token_lit) = start.peel_parens().kind match (&start.peel_parens().kind, &end.peel_parens().kind) {
&& let ExprKind::Lit(end_token_lit) = end.peel_parens().kind (&ExprKind::Lit(start_lit), &ExprKind::Lit(end_lit)) => {
&& matches!( matches!(
( (LitKind::from_token_lit(start_lit), LitKind::from_token_lit(end_lit),),
LitKind::from_token_lit(start_token_lit), (
LitKind::from_token_lit(end_token_lit), Ok(LitKind::Byte(b'a') | LitKind::Char('a')),
), Ok(LitKind::Byte(b'z') | LitKind::Char('z'))
( ) | (
Ok(LitKind::Byte(b'a') | LitKind::Char('a')), Ok(LitKind::Byte(b'A') | LitKind::Char('A')),
Ok(LitKind::Byte(b'z') | LitKind::Char('z')) Ok(LitKind::Byte(b'Z') | LitKind::Char('Z')),
) | ( ) | (
Ok(LitKind::Byte(b'A') | LitKind::Char('A')), Ok(LitKind::Byte(b'0') | LitKind::Char('0')),
Ok(LitKind::Byte(b'Z') | LitKind::Char('Z')), Ok(LitKind::Byte(b'9') | LitKind::Char('9')),
) | ( )
Ok(LitKind::Byte(b'0') | LitKind::Char('0')),
Ok(LitKind::Byte(b'9') | LitKind::Char('9')),
) )
) },
&& !in_external_macro(cx.sess(), span) _ => false,
{
span_lint_and_then(cx, ALMOST_COMPLETE_RANGE, span, "almost complete ascii range", |diag| {
if let Some((span, sugg)) = sugg {
diag.span_suggestion(span, "use an inclusive range", sugg, Applicability::MaybeIncorrect);
}
});
} }
} }

View File

@ -1,7 +1,8 @@
use clippy_utils::consts::{constant_with_source, Constant, ConstantSource}; use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_inside_always_const_context;
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
use rustc_hir::{Expr, Item, ItemKind, Node}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::sym; use rustc_span::sym;
@ -42,17 +43,16 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else {
return; return;
}; };
let Some((Constant::Bool(val), source)) = constant_with_source(cx, cx.typeck_results(), condition) else { let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else {
return; return;
}; };
if let ConstantSource::Constant = source
&& let Node::Item(Item { match condition.kind {
kind: ItemKind::Const(..), ExprKind::Path(..) | ExprKind::Lit(_) => {},
.. _ if is_inside_always_const_context(cx.tcx, e.hir_id) => return,
}) = cx.tcx.parent_hir_node(e.hir_id) _ => {},
{
return;
} }
if val { if val {
span_lint_and_help( span_lint_and_help(
cx, cx,

View File

@ -1,18 +1,16 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::HirNode;
use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap}; use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; use clippy_utils::{is_diag_trait_item, last_path_segment, local_is_initialized, path_to_local};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_hir::{self as hir, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::ty::{self, Instance, Mutability}; use rustc_middle::ty::{self, Instance, Mutability};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::{ExpnKind, Span, SyntaxContext}; use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -68,167 +66,84 @@ impl AssigningClones {
impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
impl<'tcx> LateLintPass<'tcx> for AssigningClones { impl<'tcx> LateLintPass<'tcx> for AssigningClones {
fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
// Do not fire the lint in macros if let ExprKind::Assign(lhs, rhs, _) = e.kind
let ctxt = assign_expr.span().ctxt(); && let typeck = cx.typeck_results()
let expn_data = ctxt.outer_expn_data(); && let (call_kind, fn_name, fn_id, fn_arg, fn_gen_args) = match rhs.kind {
match expn_data.kind { ExprKind::Call(f, [arg])
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return, if let ExprKind::Path(fn_path) = &f.kind
ExpnKind::Root => {}, && let Some(id) = typeck.qpath_res(fn_path, f.hir_id).opt_def_id() =>
} {
(CallKind::Ufcs, last_path_segment(fn_path).ident.name, id, arg, typeck.node_args(f.hir_id))
let ExprKind::Assign(lhs, rhs, _span) = assign_expr.kind else { },
return; ExprKind::MethodCall(name, recv, [], _) if let Some(id) = typeck.type_dependent_def_id(rhs.hir_id) => {
}; (CallKind::Method, name.ident.name, id, recv, typeck.node_args(rhs.hir_id))
},
let Some(call) = extract_call(cx, rhs) else { _ => return,
return; }
}; && let ctxt = e.span.ctxt()
// Don't lint in macros.
if is_ok_to_suggest(cx, lhs, &call, &self.msrv) { && ctxt.is_root()
suggest(cx, ctxt, assign_expr, lhs, &call); && let which_trait = match fn_name {
sym::clone if is_diag_trait_item(cx, fn_id, sym::Clone) => CloneTrait::Clone,
_ if fn_name.as_str() == "to_owned"
&& is_diag_trait_item(cx, fn_id, sym::ToOwned)
&& self.msrv.meets(msrvs::CLONE_INTO) =>
{
CloneTrait::ToOwned
},
_ => return,
}
&& let Ok(Some(resolved_fn)) = Instance::try_resolve(cx.tcx, cx.param_env, fn_id, fn_gen_args)
// TODO: This check currently bails if the local variable has no initializer.
// That is overly conservative - the lint should fire even if there was no initializer,
// but the variable has been initialized before `lhs` was evaluated.
&& path_to_local(lhs).map_or(true, |lhs| local_is_initialized(cx, lhs))
&& let Some(resolved_impl) = cx.tcx.impl_of_method(resolved_fn.def_id())
// Derived forms don't implement `clone_from`/`clone_into`.
// See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305
&& !cx.tcx.is_builtin_derived(resolved_impl)
// Don't suggest calling a function we're implementing.
&& resolved_impl.as_local().map_or(true, |block_id| {
cx.tcx.hir().parent_owner_iter(e.hir_id).all(|(id, _)| id.def_id != block_id)
})
&& let resolved_assoc_items = cx.tcx.associated_items(resolved_impl)
// Only suggest if `clone_from`/`clone_into` is explicitly implemented
&& resolved_assoc_items.in_definition_order().any(|assoc|
match which_trait {
CloneTrait::Clone => assoc.name == sym::clone_from,
CloneTrait::ToOwned => assoc.name.as_str() == "clone_into",
}
)
&& !clone_source_borrows_from_dest(cx, lhs, rhs.span)
{
span_lint_and_then(
cx,
ASSIGNING_CLONES,
e.span,
match which_trait {
CloneTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient",
CloneTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient",
},
|diag| {
let mut app = Applicability::Unspecified;
diag.span_suggestion(
e.span,
match which_trait {
CloneTrait::Clone => "use `clone_from()`",
CloneTrait::ToOwned => "use `clone_into()`",
},
build_sugg(cx, ctxt, lhs, fn_arg, which_trait, call_kind, &mut app),
app,
);
},
);
} }
} }
extract_msrv_attr!(LateContext); extract_msrv_attr!(LateContext);
} }
// Try to resolve the call to `Clone::clone` or `ToOwned::to_owned`.
fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<CallCandidate<'tcx>> {
let fn_def_id = clippy_utils::fn_def_id(cx, expr)?;
// Fast paths to only check method calls without arguments or function calls with a single argument
let (target, kind, resolved_method) = match expr.kind {
ExprKind::MethodCall(path, receiver, [], _span) => {
let args = cx.typeck_results().node_args(expr.hir_id);
// If we could not resolve the method, don't apply the lint
let Ok(Some(resolved_method)) = Instance::try_resolve(cx.tcx, cx.param_env, fn_def_id, args) else {
return None;
};
if is_trait_method(cx, expr, sym::Clone) && path.ident.name == sym::clone {
(TargetTrait::Clone, CallKind::MethodCall { receiver }, resolved_method)
} else if is_trait_method(cx, expr, sym::ToOwned) && path.ident.name.as_str() == "to_owned" {
(TargetTrait::ToOwned, CallKind::MethodCall { receiver }, resolved_method)
} else {
return None;
}
},
ExprKind::Call(function, [arg]) => {
let kind = cx.typeck_results().node_type(function.hir_id).kind();
// If we could not resolve the method, don't apply the lint
let Ok(Some(resolved_method)) = (match kind {
ty::FnDef(_, args) => Instance::try_resolve(cx.tcx, cx.param_env, fn_def_id, args),
_ => Ok(None),
}) else {
return None;
};
if cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) {
(
TargetTrait::ToOwned,
CallKind::FunctionCall { self_arg: arg },
resolved_method,
)
} else if let Some(trait_did) = cx.tcx.trait_of_item(fn_def_id)
&& cx.tcx.is_diagnostic_item(sym::Clone, trait_did)
{
(
TargetTrait::Clone,
CallKind::FunctionCall { self_arg: arg },
resolved_method,
)
} else {
return None;
}
},
_ => return None,
};
Some(CallCandidate {
span: expr.span,
target,
kind,
method_def_id: resolved_method.def_id(),
})
}
// Return true if we find that the called method has a custom implementation and isn't derived or
// provided by default by the corresponding trait.
fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>, msrv: &Msrv) -> bool {
// For calls to .to_owned we suggest using .clone_into(), which was only stablilized in 1.63.
// If the current MSRV is below that, don't suggest the lint.
if !msrv.meets(msrvs::CLONE_INTO) && matches!(call.target, TargetTrait::ToOwned) {
return false;
}
// If the left-hand side is a local variable, it might be uninitialized at this point.
// In that case we do not want to suggest the lint.
if let Some(local) = path_to_local(lhs) {
// TODO: This check currently bails if the local variable has no initializer.
// That is overly conservative - the lint should fire even if there was no initializer,
// but the variable has been initialized before `lhs` was evaluated.
if !local_is_initialized(cx, local) {
return false;
}
}
let Some(impl_block) = cx.tcx.impl_of_method(call.method_def_id) else {
return false;
};
// If the method implementation comes from #[derive(Clone)], then don't suggest the lint.
// Automatically generated Clone impls do not currently override `clone_from`.
// See e.g. https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 for more details.
if cx.tcx.is_builtin_derived(impl_block) {
return false;
}
// If the call expression is inside an impl block that contains the method invoked by the
// call expression, we bail out to avoid suggesting something that could result in endless
// recursion.
if let Some(local_block_id) = impl_block.as_local()
&& let Some(block) = cx.tcx.hir_node_by_def_id(local_block_id).as_owner()
{
let impl_block_owner = block.def_id();
if cx
.tcx
.hir()
.parent_id_iter(lhs.hir_id)
.any(|parent| parent.owner == impl_block_owner)
{
return false;
}
}
// Find the function for which we want to check that it is implemented.
let provided_fn = match call.target {
TargetTrait::Clone => cx.tcx.get_diagnostic_item(sym::Clone).and_then(|clone| {
cx.tcx
.provided_trait_methods(clone)
.find(|item| item.name == sym::clone_from)
}),
TargetTrait::ToOwned => cx.tcx.get_diagnostic_item(sym::ToOwned).and_then(|to_owned| {
cx.tcx
.provided_trait_methods(to_owned)
.find(|item| item.name.as_str() == "clone_into")
}),
};
let Some(provided_fn) = provided_fn else {
return false;
};
if clone_source_borrows_from_dest(cx, lhs, call.span) {
return false;
}
// Now take a look if the impl block defines an implementation for the method that we're interested
// in. If not, then we're using a default implementation, which is not interesting, so we will
// not suggest the lint.
let implemented_fns = cx.tcx.impl_item_implementor_ids(impl_block);
implemented_fns.contains_key(&provided_fn.def_id)
}
/// Checks if the data being cloned borrows from the place that is being assigned to: /// Checks if the data being cloned borrows from the place that is being assigned to:
/// ///
/// ``` /// ```
@ -239,16 +154,6 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
/// ///
/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows. /// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows.
fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool { fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool {
/// If this basic block only exists to drop a local as part of an assignment, returns its
/// successor. Otherwise returns the basic block that was passed in.
fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock {
if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind {
target
} else {
bb
}
}
let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else { let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else {
return false; return false;
}; };
@ -267,172 +172,123 @@ fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_spa
// //
// bb2: // bb2:
// s = s_temp // s = s_temp
for bb in mir.basic_blocks.iter() { if let Some(terminator) = mir.basic_blocks.iter()
let terminator = bb.terminator(); .map(mir::BasicBlockData::terminator)
.find(|term| term.source_info.span == call_span)
// Look for the to_owned/clone call. && let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind
if terminator.source_info.span != call_span { && let [source] = &**args
continue; && let mir::Operand::Move(source) = &source.node
&& let assign_bb = &mir.basic_blocks[assign_bb]
&& let assign_bb = match assign_bb.terminator().kind {
// Skip the drop of the assignment's destination.
mir::TerminatorKind::Drop { target, .. } => &mir.basic_blocks[target],
_ => assign_bb,
} }
// Skip any storage statements as they are just noise
if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind && let Some(assignment) = assign_bb.statements
&& let [source] = &**args .iter()
&& let mir::Operand::Move(source) = &source.node .find(|stmt| {
&& let assign_bb = skip_drop_block(mir, assign_bb) !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))
// Skip any storage statements as they are just noise })
&& let Some(assignment) = mir.basic_blocks[assign_bb].statements && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind
.iter() && let Some(borrowers) = borrow_map.get(&borrowed.local)
.find(|stmt| { {
!matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_)) borrowers.contains(source.local)
}) } else {
&& let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind false
&& let Some(borrowers) = borrow_map.get(&borrowed.local)
&& borrowers.contains(source.local)
{
return true;
}
return false;
} }
false
} }
fn suggest<'tcx>( #[derive(Clone, Copy)]
cx: &LateContext<'tcx>, enum CloneTrait {
ctxt: SyntaxContext,
assign_expr: &Expr<'tcx>,
lhs: &Expr<'tcx>,
call: &CallCandidate<'tcx>,
) {
span_lint_and_then(cx, ASSIGNING_CLONES, assign_expr.span, call.message(), |diag| {
let mut applicability = Applicability::Unspecified;
diag.span_suggestion(
assign_expr.span,
call.suggestion_msg(),
call.suggested_replacement(cx, ctxt, lhs, &mut applicability),
applicability,
);
});
}
#[derive(Copy, Clone, Debug)]
enum CallKind<'tcx> {
MethodCall { receiver: &'tcx Expr<'tcx> },
FunctionCall { self_arg: &'tcx Expr<'tcx> },
}
#[derive(Copy, Clone, Debug)]
enum TargetTrait {
Clone, Clone,
ToOwned, ToOwned,
} }
#[derive(Debug)] #[derive(Copy, Clone)]
struct CallCandidate<'tcx> { enum CallKind {
span: Span, Ufcs,
target: TargetTrait, Method,
kind: CallKind<'tcx>,
// DefId of the called method from an impl block that implements the target trait
method_def_id: DefId,
} }
impl<'tcx> CallCandidate<'tcx> { fn build_sugg<'tcx>(
#[inline] cx: &LateContext<'tcx>,
fn message(&self) -> &'static str { ctxt: SyntaxContext,
match self.target { lhs: &'tcx Expr<'_>,
TargetTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient", fn_arg: &'tcx Expr<'_>,
TargetTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient", which_trait: CloneTrait,
} call_kind: CallKind,
} app: &mut Applicability,
) -> String {
#[inline] match which_trait {
fn suggestion_msg(&self) -> &'static str { CloneTrait::Clone => {
match self.target { match call_kind {
TargetTrait::Clone => "use `clone_from()`", CallKind::Method => {
TargetTrait::ToOwned => "use `clone_into()`", let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
} // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
} Sugg::hir_with_applicability(cx, ref_expr, "_", app)
fn suggested_replacement(
&self,
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
lhs: &Expr<'tcx>,
applicability: &mut Applicability,
) -> String {
match self.target {
TargetTrait::Clone => {
match self.kind {
CallKind::MethodCall { receiver } => {
let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, ref_expr, "_", applicability)
} else {
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", applicability)
}
.maybe_par();
// Determine whether we need to reference the argument to clone_from().
let clone_receiver_type = cx.typeck_results().expr_ty(receiver);
let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(receiver);
let mut arg_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability);
if clone_receiver_type != clone_receiver_adj_type {
// The receiver may have been a value type, so we need to add an `&` to
// be sure the argument to clone_from will be a reference.
arg_sugg = arg_sugg.addr();
};
format!("{receiver_sugg}.clone_from({arg_sugg})")
},
CallKind::FunctionCall { self_arg, .. } => {
let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)`
Sugg::hir_with_applicability(cx, ref_expr, "_", applicability)
} else {
// `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", applicability).mut_addr()
};
// The RHS had to be exactly correct before the call, there is no auto-deref for function calls.
let rhs_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability);
format!("Clone::clone_from({self_sugg}, {rhs_sugg})")
},
}
},
TargetTrait::ToOwned => {
let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
// `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", applicability).maybe_par();
let inner_type = cx.typeck_results().expr_ty(ref_expr);
// If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
// deref to a mutable reference.
if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) {
sugg
} else { } else {
sugg.mut_addr() // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app)
} }
} else { .maybe_par();
// `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
// `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
Sugg::hir_with_applicability(cx, lhs, "_", applicability)
.maybe_par()
.mut_addr()
};
match self.kind { // Determine whether we need to reference the argument to clone_from().
CallKind::MethodCall { receiver } => { let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);
let receiver_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability); let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(fn_arg);
format!("{receiver_sugg}.clone_into({rhs_sugg})") let mut arg_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);
}, if clone_receiver_type != clone_receiver_adj_type {
CallKind::FunctionCall { self_arg, .. } => { // The receiver may have been a value type, so we need to add an `&` to
let self_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability); // be sure the argument to clone_from will be a reference.
format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})") arg_sugg = arg_sugg.addr();
}, };
format!("{receiver_sugg}.clone_from({arg_sugg})")
},
CallKind::Ufcs => {
let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)`
Sugg::hir_with_applicability(cx, ref_expr, "_", app)
} else {
// `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr()
};
// The RHS had to be exactly correct before the call, there is no auto-deref for function calls.
let rhs_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);
format!("Clone::clone_from({self_sugg}, {rhs_sugg})")
},
}
},
CloneTrait::ToOwned => {
let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
// `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par();
let inner_type = cx.typeck_results().expr_ty(ref_expr);
// If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
// deref to a mutable reference.
if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) {
sugg
} else {
sugg.mut_addr()
} }
}, } else {
} // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
// `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr()
};
match call_kind {
CallKind::Method => {
let receiver_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);
format!("{receiver_sugg}.clone_into({rhs_sugg})")
},
CallKind::Ufcs => {
let self_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);
format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})")
},
}
},
} }
} }

View File

@ -11,21 +11,25 @@ use rustc_span::{sym, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for calls to await while holding a non-async-aware MutexGuard. /// Checks for calls to `await` while holding a non-async-aware
/// `MutexGuard`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// The Mutex types found in std::sync and parking_lot /// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and
/// are not designed to operate in an async context across await points. /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are
/// not designed to operate in an async context across await points.
/// ///
/// There are two potential solutions. One is to use an async-aware Mutex /// There are two potential solutions. One is to use an async-aware `Mutex`
/// type. Many asynchronous foundation crates provide such a Mutex type. The /// type. Many asynchronous foundation crates provide such a `Mutex` type.
/// other solution is to ensure the mutex is unlocked before calling await, /// The other solution is to ensure the mutex is unlocked before calling
/// either by introducing a scope or an explicit call to Drop::drop. /// `await`, either by introducing a scope or an explicit call to
/// [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html).
/// ///
/// ### Known problems /// ### Known problems
/// Will report false positive for explicitly dropped guards /// Will report false positive for explicitly dropped guards
/// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A
/// to wrap the `.lock()` call in a block instead of explicitly dropping the guard. /// workaround for this is to wrap the `.lock()` call in a block instead of
/// explicitly dropping the guard.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -73,11 +77,11 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`. /// Checks for calls to `await` while holding a `RefCell`, `Ref`, or `RefMut`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// `RefCell` refs only check for exclusive mutable access /// `RefCell` refs only check for exclusive mutable access
/// at runtime. Holding onto a `RefCell` ref across an `await` suspension point /// at runtime. Holding a `RefCell` ref across an await suspension point
/// risks panics from a mutable ref shared while other refs are outstanding. /// risks panics from a mutable ref shared while other refs are outstanding.
/// ///
/// ### Known problems /// ### Known problems
@ -131,13 +135,13 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Allows users to configure types which should not be held across `await` /// Allows users to configure types which should not be held across await
/// suspension points. /// suspension points.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// There are some types which are perfectly "safe" to be used concurrently /// There are some types which are perfectly safe to use concurrently from
/// from a memory access perspective but will cause bugs at runtime if they /// a memory access perspective, but that will cause bugs at runtime if
/// are held in such a way. /// they are held in such a way.
/// ///
/// ### Example /// ### Example
/// ///
@ -228,15 +232,15 @@ impl AwaitHolding {
cx, cx,
AWAIT_HOLDING_LOCK, AWAIT_HOLDING_LOCK,
ty_cause.source_info.span, ty_cause.source_info.span,
"this `MutexGuard` is held across an `await` point", "this `MutexGuard` is held across an await point",
|diag| { |diag| {
diag.help( diag.help(
"consider using an async-aware `Mutex` type or ensuring the \ "consider using an async-aware `Mutex` type or ensuring the \
`MutexGuard` is dropped before calling await", `MutexGuard` is dropped before calling `await`",
); );
diag.span_note( diag.span_note(
await_points(), await_points(),
"these are all the `await` points this lock is held through", "these are all the await points this lock is held through",
); );
}, },
); );
@ -245,12 +249,12 @@ impl AwaitHolding {
cx, cx,
AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_REFCELL_REF,
ty_cause.source_info.span, ty_cause.source_info.span,
"this `RefCell` reference is held across an `await` point", "this `RefCell` reference is held across an await point",
|diag| { |diag| {
diag.help("ensure the reference is dropped before calling `await`"); diag.help("ensure the reference is dropped before calling `await`");
diag.span_note( diag.span_note(
await_points(), await_points(),
"these are all the `await` points this reference is held through", "these are all the await points this reference is held through",
); );
}, },
); );
@ -268,7 +272,7 @@ fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPa
AWAIT_HOLDING_INVALID_TYPE, AWAIT_HOLDING_INVALID_TYPE,
span, span,
format!( format!(
"`{}` may not be held across an `await` point per `clippy.toml`", "`{}` may not be held across an await point per `clippy.toml`",
disallowed.path() disallowed.path()
), ),
|diag| { |diag| {

View File

@ -1,13 +1,11 @@
use clippy_utils::higher::If;
use rustc_ast::LitKind;
use rustc_hir::{Block, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{in_constant, is_else_clause, is_integer_literal}; use clippy_utils::{in_constant, is_else_clause};
use rustc_ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -47,80 +45,64 @@ declare_clippy_lint! {
declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { if let ExprKind::If(cond, then, Some(else_)) = expr.kind
check_if_else(cx, expr); && matches!(cond.kind, ExprKind::DropTemps(_))
&& let Some(then_lit) = as_int_bool_lit(then)
&& let Some(else_lit) = as_int_bool_lit(else_)
&& then_lit != else_lit
&& !expr.span.from_expansion()
&& !in_constant(cx, expr.hir_id)
{
let ty = cx.typeck_results().expr_ty(then);
let mut applicability = Applicability::MachineApplicable;
let snippet = {
let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
if !then_lit {
sugg = !sugg;
}
sugg
};
let suggestion = {
let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into());
// when used in else clause if statement should be wrapped in curly braces
if is_else_clause(cx.tcx, expr) {
s = s.blockify();
}
s
};
let into_snippet = snippet.clone().maybe_par();
let as_snippet = snippet.as_ty(ty);
span_lint_and_then(
cx,
BOOL_TO_INT_WITH_IF,
expr.span,
"boolean to int conversion using if",
|diag| {
diag.span_suggestion(expr.span, "replace with from", suggestion, applicability);
diag.note(format!(
"`{as_snippet}` or `{into_snippet}.into()` can also be valid options"
));
},
);
} }
} }
} }
fn check_if_else<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> {
if let Some(If { if let ExprKind::Block(b, _) = e.kind
cond, && b.stmts.is_empty()
then, && let Some(e) = b.expr
r#else: Some(r#else), && let ExprKind::Lit(lit) = e.kind
}) = If::hir(expr) && let LitKind::Int(x, _) = lit.node
&& let Some(then_lit) = int_literal(then)
&& let Some(else_lit) = int_literal(r#else)
{ {
let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) { match x.get() {
false 0 => Some(false),
} else if is_integer_literal(then_lit, 0) && is_integer_literal(else_lit, 1) { 1 => Some(true),
true _ => None,
} else { }
// Expression isn't boolean, exit
return;
};
let mut applicability = Applicability::MachineApplicable;
let snippet = {
let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
if inverted {
sugg = !sugg;
}
sugg
};
let ty = cx.typeck_results().expr_ty(then_lit); // then and else must be of same type
let suggestion = {
let wrap_in_curly = is_else_clause(cx.tcx, expr);
let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into());
if wrap_in_curly {
s = s.blockify();
}
s
}; // when used in else clause if statement should be wrapped in curly braces
let into_snippet = snippet.clone().maybe_par();
let as_snippet = snippet.as_ty(ty);
span_lint_and_then(
cx,
BOOL_TO_INT_WITH_IF,
expr.span,
"boolean to int conversion using if",
|diag| {
diag.span_suggestion(expr.span, "replace with from", suggestion, applicability);
diag.note(format!(
"`{as_snippet}` or `{into_snippet}.into()` can also be valid options"
));
},
);
};
}
// If block contains only a int literal expression, return literal expression
fn int_literal<'tcx>(expr: &'tcx rustc_hir::Expr<'tcx>) -> Option<&'tcx rustc_hir::Expr<'tcx>> {
if let ExprKind::Block(block, _) = expr.kind
&& let Block {
stmts: [], // Shouldn't lint if statements with side effects
expr: Some(expr),
..
} = block
&& let ExprKind::Lit(lit) = &expr.kind
&& let LitKind::Int(_, _) = lit.node
{
Some(expr)
} else { } else {
None None
} }

View File

@ -0,0 +1,80 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_ast::token::{Lit, LitKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for hard to read slices of byte characters, that could be more easily expressed as a
/// byte string.
///
/// ### Why is this bad?
///
/// Potentially makes the string harder to read.
///
/// ### Example
/// ```ignore
/// &[b'H', b'e', b'l', b'l', b'o'];
/// ```
/// Use instead:
/// ```ignore
/// b"Hello"
/// ```
#[clippy::version = "1.68.0"]
pub BYTE_CHAR_SLICES,
style,
"hard to read byte char slice"
}
declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]);
impl EarlyLintPass for ByteCharSlice {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let Some(slice) = is_byte_char_slices(expr)
&& !expr.span.from_expansion()
{
span_lint_and_sugg(
cx,
BYTE_CHAR_SLICES,
expr.span,
"can be more succinctly written as a byte str",
"try",
format!("b\"{slice}\""),
Applicability::MaybeIncorrect,
);
}
}
}
fn is_byte_char_slices(expr: &Expr) -> Option<String> {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind {
match &expr.kind {
ExprKind::Array(members) => {
if members.is_empty() {
return None;
}
members
.iter()
.map(|member| match &member.kind {
ExprKind::Lit(Lit {
kind: LitKind::Byte,
symbol,
..
}) => Some(symbol.as_str()),
_ => None,
})
.map(|maybe_quote| match maybe_quote {
Some("\"") => Some("\\\""),
Some("\\'") => Some("'"),
other => other,
})
.collect::<Option<String>>()
},
_ => None,
}
} else {
None
}
}

View File

@ -32,7 +32,7 @@ use rustc_session::impl_lint_pass;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts from any numerical to a float type where /// Checks for casts from any numeric type to a float type where
/// the receiving type cannot store all values from the original type without /// the receiving type cannot store all values from the original type without
/// rounding errors. This possible rounding is to be expected, so this lint is /// rounding errors. This possible rounding is to be expected, so this lint is
/// `Allow` by default. /// `Allow` by default.
@ -58,14 +58,14 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts from a signed to an unsigned numerical /// Checks for casts from a signed to an unsigned numeric
/// type. In this case, negative values wrap around to large positive values, /// type. In this case, negative values wrap around to large positive values,
/// which can be quite surprising in practice. However, as the cast works as /// which can be quite surprising in practice. However, since the cast works as
/// defined, this lint is `Allow` by default. /// defined, this lint is `Allow` by default.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Possibly surprising results. You can activate this lint /// Possibly surprising results. You can activate this lint
/// as a one-time check to see where numerical wrapping can arise. /// as a one-time check to see where numeric wrapping can arise.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -80,7 +80,7 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts between numerical types that may /// Checks for casts between numeric types that may
/// truncate large values. This is expected behavior, so the cast is `Allow` by /// truncate large values. This is expected behavior, so the cast is `Allow` by
/// default. It suggests user either explicitly ignore the lint, /// default. It suggests user either explicitly ignore the lint,
/// or use `try_from()` and handle the truncation, default, or panic explicitly. /// or use `try_from()` and handle the truncation, default, or panic explicitly.
@ -120,17 +120,16 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts from an unsigned type to a signed type of /// Checks for casts from an unsigned type to a signed type of
/// the same size, or possibly smaller due to target dependent integers. /// the same size, or possibly smaller due to target-dependent integers.
/// Performing such a cast is a 'no-op' for the compiler, i.e., nothing is /// Performing such a cast is a no-op for the compiler (that is, nothing is
/// changed at the bit level, and the binary representation of the value is /// changed at the bit level), and the binary representation of the value is
/// reinterpreted. This can cause wrapping if the value is too big /// reinterpreted. This can cause wrapping if the value is too big
/// for the target signed type. However, the cast works as defined, so this lint /// for the target signed type. However, the cast works as defined, so this lint
/// is `Allow` by default. /// is `Allow` by default.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// While such a cast is not bad in itself, the results can /// While such a cast is not bad in itself, the results can
/// be surprising when this is not the intended behavior, as demonstrated by the /// be surprising when this is not the intended behavior:
/// example below.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -144,16 +143,16 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts between numerical types that may /// Checks for casts between numeric types that can be replaced by safe
/// be replaced by safe conversion functions. /// conversion functions.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Rust's `as` keyword will perform many kinds of /// Rust's `as` keyword will perform many kinds of conversions, including
/// conversions, including silently lossy conversions. Conversion functions such /// silently lossy conversions. Conversion functions such as `i32::from`
/// as `i32::from` will only perform lossless conversions. Using the conversion /// will only perform lossless conversions. Using the conversion functions
/// functions prevents conversions from turning into silent lossy conversions if /// prevents conversions from becoming silently lossy if the input types
/// the types of the input expressions ever change, and make it easier for /// ever change, and makes it clear for people reading the code that the
/// people reading the code to know that the conversion is lossless. /// conversion is lossless.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -177,19 +176,21 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts to the same type, casts of int literals to integer types, casts of float /// Checks for casts to the same type, casts of int literals to integer
/// literals to float types and casts between raw pointers without changing type or constness. /// types, casts of float literals to float types, and casts between raw
/// pointers that don't change type or constness.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// It's just unnecessary. /// It's just unnecessary.
/// ///
/// ### Known problems /// ### Known problems
/// When the expression on the left is a function call, the lint considers the return type to be /// When the expression on the left is a function call, the lint considers
/// a type alias if it's aliased through a `use` statement /// the return type to be a type alias if it's aliased through a `use`
/// (like `use std::io::Result as IoResult`). It will not lint such cases. /// statement (like `use std::io::Result as IoResult`). It will not lint
/// such cases.
/// ///
/// This check is also rather primitive. It will only work on primitive types without any /// This check will only work on primitive types without any intermediate
/// intermediate references, raw pointers and trait objects may or may not work. /// references: raw pointers and trait objects may or may not work.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -211,17 +212,17 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts, using `as` or `pointer::cast`, /// Checks for casts, using `as` or `pointer::cast`, from a
/// from a less-strictly-aligned pointer to a more-strictly-aligned pointer /// less strictly aligned pointer to a more strictly aligned pointer.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Dereferencing the resulting pointer may be undefined /// Dereferencing the resulting pointer may be undefined behavior.
/// behavior.
/// ///
/// ### Known problems /// ### Known problems
/// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar /// Using [`std::ptr::read_unaligned`](https://doc.rust-lang.org/std/ptr/fn.read_unaligned.html) and [`std::ptr::write_unaligned`](https://doc.rust-lang.org/std/ptr/fn.write_unaligned.html) or
/// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like /// similar on the resulting pointer is fine. Is over-zealous: casts with
/// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. /// manual alignment checks or casts like `u64` -> `u8` -> `u16` can be
/// fine. Miri is able to do a more in-depth analysis.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -234,20 +235,21 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub CAST_PTR_ALIGNMENT, pub CAST_PTR_ALIGNMENT,
pedantic, pedantic,
"cast from a pointer to a more-strictly-aligned pointer" "cast from a pointer to a more strictly aligned pointer"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts of function pointers to something other than usize /// Checks for casts of function pointers to something other than `usize`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Casting a function pointer to anything other than usize/isize is not portable across /// Casting a function pointer to anything other than `usize`/`isize` is
/// architectures, because you end up losing bits if the target type is too small or end up with a /// not portable across architectures. If the target type is too small the
/// bunch of extra bits that waste space and add more instructions to the final binary than /// address would be truncated, and target types larger than `usize` are
/// strictly necessary for the problem /// unnecessary.
/// ///
/// Casting to isize also doesn't make sense since there are no signed addresses. /// Casting to `isize` also doesn't make sense, since addresses are never
/// signed.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -263,17 +265,17 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub FN_TO_NUMERIC_CAST, pub FN_TO_NUMERIC_CAST,
style, style,
"casting a function pointer to a numeric type other than usize" "casting a function pointer to a numeric type other than `usize`"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts of a function pointer to a numeric type not wide enough to /// Checks for casts of a function pointer to a numeric type not wide enough to
/// store address. /// store an address.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Such a cast discards some bits of the function's address. If this is intended, it would be more /// Such a cast discards some bits of the function's address. If this is intended, it would be more
/// clearly expressed by casting to usize first, then casting the usize to the intended type (with /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with
/// a comment) to perform the truncation. /// a comment) to perform the truncation.
/// ///
/// ### Example /// ### Example
@ -306,7 +308,7 @@ declare_clippy_lint! {
/// ### Why restrict this? /// ### Why restrict this?
/// Casting a function pointer to an integer can have surprising results and can occur /// Casting a function pointer to an integer can have surprising results and can occur
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything /// accidentally if parentheses are omitted from a function call. If you aren't doing anything
/// low-level with function pointers then you can opt-out of casting functions to integers in /// low-level with function pointers then you can opt out of casting functions to integers in
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
/// pointer casts in your code. /// pointer casts in your code.
/// ///
@ -349,8 +351,8 @@ declare_clippy_lint! {
/// ### Why is this bad? /// ### Why is this bad?
/// In general, casting values to smaller types is /// In general, casting values to smaller types is
/// error-prone and should be avoided where possible. In the particular case of /// error-prone and should be avoided where possible. In the particular case of
/// converting a character literal to u8, it is easy to avoid by just using a /// converting a character literal to `u8`, it is easy to avoid by just using a
/// byte literal instead. As an added bonus, `b'a'` is even slightly shorter /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter
/// than `'a' as u8`. /// than `'a' as u8`.
/// ///
/// ### Example /// ### Example
@ -371,12 +373,13 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for `as` casts between raw pointers without changing its mutability, /// Checks for `as` casts between raw pointers that don't change their
/// namely `*const T` to `*const U` and `*mut T` to `*mut U`. /// constness, namely `*const T` to `*const U` and `*mut T` to `*mut U`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Though `as` casts between raw pointers are not terrible, `pointer::cast` is safer because /// Though `as` casts between raw pointers are not terrible,
/// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`. /// `pointer::cast` is safer because it cannot accidentally change the
/// pointer's mutability, nor cast the pointer to other types like `usize`.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -395,12 +398,12 @@ declare_clippy_lint! {
#[clippy::version = "1.51.0"] #[clippy::version = "1.51.0"]
pub PTR_AS_PTR, pub PTR_AS_PTR,
pedantic, pedantic,
"casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" "casting using `as` between raw pointers that doesn't change their constness, where `pointer::cast` could take the place of `as`"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for `as` casts between raw pointers which change its constness, namely `*const T` to /// Checks for `as` casts between raw pointers that change their constness, namely `*const T` to
/// `*mut T` and `*mut T` to `*const T`. /// `*mut T` and `*mut T` to `*const T`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
@ -423,12 +426,12 @@ declare_clippy_lint! {
#[clippy::version = "1.72.0"] #[clippy::version = "1.72.0"]
pub PTR_CAST_CONSTNESS, pub PTR_CAST_CONSTNESS,
pedantic, pedantic,
"casting using `as` from and to raw pointers to change constness when specialized methods apply" "casting using `as` on raw pointers to change constness when specialized methods apply"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for casts from an enum type to an integral type which will definitely truncate the /// Checks for casts from an enum type to an integral type that will definitely truncate the
/// value. /// value.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
@ -442,7 +445,7 @@ declare_clippy_lint! {
#[clippy::version = "1.61.0"] #[clippy::version = "1.61.0"]
pub CAST_ENUM_TRUNCATION, pub CAST_ENUM_TRUNCATION,
suspicious, suspicious,
"casts from an enum type to an integral type which will truncate the value" "casts from an enum type to an integral type that will truncate the value"
} }
declare_clippy_lint! { declare_clippy_lint! {
@ -621,7 +624,7 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior

View File

@ -92,7 +92,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) {
cx, cx,
PTR_AS_PTR, PTR_AS_PTR,
expr.span, expr.span,
"`as` casting between raw pointers without changing its mutability", "`as` casting between raw pointers without changing their constness",
help, help,
final_suggestion, final_suggestion,
app, app,

View File

@ -0,0 +1,60 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::NestedMetaItem;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `cfg` that excludes code from `test` builds. (i.e., `#{cfg(not(test))]`)
///
/// ### Why is this bad?
/// This may give the false impression that a codebase has 100% coverage, yet actually has untested code.
/// Enabling this also guards against excessive mockery as well, which is an anti-pattern.
///
/// ### Example
/// ```rust
/// # fn important_check() {}
/// #[cfg(not(test))]
/// important_check(); // I'm not actually tested, but not including me will falsely increase coverage!
/// ```
/// Use instead:
/// ```rust
/// # fn important_check() {}
/// important_check();
/// ```
#[clippy::version = "1.73.0"]
pub CFG_NOT_TEST,
restriction,
"enforce against excluding code from test builds"
}
declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]);
impl EarlyLintPass for CfgNotTest {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) {
if attr.has_name(rustc_span::sym::cfg) && contains_not_test(attr.meta_item_list().as_deref(), false) {
span_lint_and_then(
cx,
CFG_NOT_TEST,
attr.span,
"code is excluded from test builds",
|diag| {
diag.help("consider not excluding any code from test builds");
diag.note_once("this could increase code coverage despite not actually being tested");
},
);
}
}
}
fn contains_not_test(list: Option<&[NestedMetaItem]>, not: bool) -> bool {
list.is_some_and(|list| {
list.iter().any(|item| {
item.ident().is_some_and(|ident| match ident.name {
rustc_span::sym::not => contains_not_test(item.meta_item_list(), !not),
rustc_span::sym::test => not,
_ => contains_not_test(item.meta_item_list(), not),
})
})
})
}

View File

@ -8,8 +8,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO, crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO, crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO, crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
@ -73,6 +71,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO,
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
crate::box_default::BOX_DEFAULT_INFO, crate::box_default::BOX_DEFAULT_INFO,
crate::byte_char_slices::BYTE_CHAR_SLICES_INFO,
crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO,
crate::cargo::LINT_GROUPS_PRIORITY_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO,
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO, crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
@ -103,6 +102,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::casts::REF_AS_PTR_INFO, crate::casts::REF_AS_PTR_INFO,
crate::casts::UNNECESSARY_CAST_INFO, crate::casts::UNNECESSARY_CAST_INFO,
crate::casts::ZERO_PTR_INFO, crate::casts::ZERO_PTR_INFO,
crate::cfg_not_test::CFG_NOT_TEST_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
@ -313,6 +313,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO, crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO,
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
crate::manual_retain::MANUAL_RETAIN_INFO, crate::manual_retain::MANUAL_RETAIN_INFO,
crate::manual_rotate::MANUAL_ROTATE_INFO,
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO, crate::manual_strip::MANUAL_STRIP_INFO,
@ -503,6 +504,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO, crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO,
crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO, crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO,
crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO, crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO,
crate::missing_const_for_thread_local::MISSING_CONST_FOR_THREAD_LOCAL_INFO,
crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO, crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO,
crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO, crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
crate::missing_fields_in_debug::MISSING_FIELDS_IN_DEBUG_INFO, crate::missing_fields_in_debug::MISSING_FIELDS_IN_DEBUG_INFO,
@ -585,12 +587,12 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::operators::VERBOSE_BIT_MASK_INFO, crate::operators::VERBOSE_BIT_MASK_INFO,
crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO, crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO,
crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO, crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO,
crate::overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL_INFO,
crate::panic_in_result_fn::PANIC_IN_RESULT_FN_INFO, crate::panic_in_result_fn::PANIC_IN_RESULT_FN_INFO,
crate::panic_unimplemented::PANIC_INFO, crate::panic_unimplemented::PANIC_INFO,
crate::panic_unimplemented::TODO_INFO, crate::panic_unimplemented::TODO_INFO,
crate::panic_unimplemented::UNIMPLEMENTED_INFO, crate::panic_unimplemented::UNIMPLEMENTED_INFO,
crate::panic_unimplemented::UNREACHABLE_INFO, crate::panic_unimplemented::UNREACHABLE_INFO,
crate::panicking_overflow_checks::PANICKING_OVERFLOW_CHECKS_INFO,
crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO, crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO,
crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_INFO, crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_INFO,
crate::partialeq_to_none::PARTIALEQ_TO_NONE_INFO, crate::partialeq_to_none::PARTIALEQ_TO_NONE_INFO,
@ -644,6 +646,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::semicolon_block::SEMICOLON_OUTSIDE_BLOCK_INFO, crate::semicolon_block::SEMICOLON_OUTSIDE_BLOCK_INFO,
crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO, crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO,
crate::serde_api::SERDE_API_MISUSE_INFO, crate::serde_api::SERDE_API_MISUSE_INFO,
crate::set_contains_or_insert::SET_CONTAINS_OR_INSERT_INFO,
crate::shadow::SHADOW_REUSE_INFO, crate::shadow::SHADOW_REUSE_INFO,
crate::shadow::SHADOW_SAME_INFO, crate::shadow::SHADOW_SAME_INFO,
crate::shadow::SHADOW_UNRELATED_INFO, crate::shadow::SHADOW_UNRELATED_INFO,
@ -679,7 +682,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO, crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy}; use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro}; use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro};
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// Avoid cases already linted by `field_reassign_with_default` // Avoid cases already linted by `field_reassign_with_default`
&& !self.reassigned_linted.contains(&expr.span) && !self.reassigned_linted.contains(&expr.span)
&& let ExprKind::Call(path, ..) = expr.kind && let ExprKind::Call(path, ..) = expr.kind
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id) && !in_automatically_derived(cx.tcx, expr.hir_id)
&& let ExprKind::Path(ref qpath) = path.kind && let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id) && cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
@ -113,9 +113,9 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// start from the `let mut _ = _::default();` and look at all the following // start from the `let mut _ = _::default();` and look at all the following
// statements, see if they re-assign the fields of the binding // statements, see if they re-assign the fields of the binding
let stmts_head = match block.stmts { let stmts_head = match block.stmts {
[] | [_] => return,
// Skip the last statement since there cannot possibly be any following statements that re-assign fields. // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
[head @ .., _] if !head.is_empty() => head, [head @ .., _] => head,
_ => return,
}; };
for (stmt_idx, stmt) in stmts_head.iter().enumerate() { for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
let (local, variant, binding_name, binding_type, span) = if let StmtKind::Let(local) = stmt.kind let (local, variant, binding_name, binding_type, span) = if let StmtKind::Let(local) = stmt.kind
// only take `let ...` statements // only take `let ...` statements
&& let Some(expr) = local.init && let Some(expr) = local.init
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id) && !in_automatically_derived(cx.tcx, expr.hir_id)
&& !expr.span.from_expansion() && !expr.span.from_expansion()
// only take bindings to identifiers // only take bindings to identifiers
&& let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind && let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind

View File

@ -50,6 +50,8 @@ declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback { impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) {
let hir = cx.tcx.hir(); let hir = cx.tcx.hir();
// NOTE: this is different from `clippy_utils::is_inside_always_const_context`.
// Inline const supports type inference.
let is_parent_const = matches!( let is_parent_const = matches!(
hir.body_const_context(hir.body_owner_def_id(body.id())), hir.body_const_context(hir.body_owner_def_id(body.id())),
Some(ConstContext::Const { inline: false } | ConstContext::Static(_)) Some(ConstContext::Const { inline: false } | ConstContext::Static(_))

View File

@ -3,7 +3,8 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
use clippy_utils::{ use clippy_utils::{
expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy,
ExprUseNode,
}; };
use core::mem; use core::mem;
use rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS}; use rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS};
@ -175,6 +176,7 @@ struct StateData<'tcx> {
adjusted_ty: Ty<'tcx>, adjusted_ty: Ty<'tcx>,
} }
#[derive(Debug)]
struct DerefedBorrow { struct DerefedBorrow {
count: usize, count: usize,
msg: &'static str, msg: &'static str,
@ -182,6 +184,7 @@ struct DerefedBorrow {
for_field_access: Option<Symbol>, for_field_access: Option<Symbol>,
} }
#[derive(Debug)]
enum State { enum State {
// Any number of deref method calls. // Any number of deref method calls.
DerefMethod { DerefMethod {
@ -744,7 +747,7 @@ fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> boo
} }
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
enum TyCoercionStability { enum TyCoercionStability {
Deref, Deref,
Reborrow, Reborrow,
@ -1042,16 +1045,28 @@ fn report<'tcx>(
return; return;
} }
let (prefix, precedence) = if let Some(mutability) = mutability let ty = typeck.expr_ty(expr);
&& !typeck.expr_ty(expr).is_ref()
// `&&[T; N]`, or `&&..&[T; N]` (src) cannot coerce to `&[T]` (dst).
if let ty::Ref(_, dst, _) = data.adjusted_ty.kind()
&& dst.is_slice()
{ {
let prefix = match mutability { let (src, n_src_refs) = peel_middle_ty_refs(ty);
Mutability::Not => "&", if n_src_refs >= 2 && src.is_array() {
Mutability::Mut => "&mut ", return;
}; }
(prefix, PREC_PREFIX) }
} else {
("", 0) let (prefix, precedence) = match mutability {
Some(mutability) if !ty.is_ref() => {
let prefix = match mutability {
Mutability::Not => "&",
Mutability::Mut => "&mut ",
};
(prefix, PREC_PREFIX)
},
None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", 0),
_ => ("", 0),
}; };
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,

View File

@ -1,6 +1,6 @@
use clippy_config::types::DisallowedPath; use clippy_config::types::DisallowedPath;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, get_parent_expr, path_def_id}; use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefIdMap; use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -83,26 +83,26 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr) let (id, span) = match &expr.kind {
&& let ExprKind::Call(receiver, _) = parent.kind ExprKind::Path(path)
&& receiver.hir_id == expr.hir_id if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) =
{ cx.qpath_res(path, expr.hir_id) =>
None {
} else { (id, expr.span)
path_def_id(cx, expr) },
ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {
(id, name.ident.span)
},
_ => return,
}; };
let Some(def_id) = uncalled_path.or_else(|| fn_def_id(cx, expr)) else { if let Some(&index) = self.disallowed.get(&id) {
return; let conf = &self.conf_disallowed[index];
}; let msg = format!("use of a disallowed method `{}`", conf.path());
let conf = match self.disallowed.get(&def_id) { span_lint_and_then(cx, DISALLOWED_METHODS, span, msg, |diag| {
Some(&index) => &self.conf_disallowed[index], if let Some(reason) = conf.reason() {
None => return, diag.note(reason);
}; }
let msg = format!("use of a disallowed method `{}`", conf.path()); });
span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, msg, |diag| { }
if let Some(reason) = conf.reason() {
diag.note(reason);
}
});
} }
} }

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_test_module_or_function; use clippy_utils::is_in_test;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{Item, Pat, PatKind}; use rustc_hir::{Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
@ -27,52 +27,30 @@ declare_clippy_lint! {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DisallowedNames { pub struct DisallowedNames {
disallow: FxHashSet<String>, disallow: FxHashSet<String>,
test_modules_deep: u32,
} }
impl DisallowedNames { impl DisallowedNames {
pub fn new(disallowed_names: &[String]) -> Self { pub fn new(disallowed_names: &[String]) -> Self {
Self { Self {
disallow: disallowed_names.iter().cloned().collect(), disallow: disallowed_names.iter().cloned().collect(),
test_modules_deep: 0,
} }
} }
fn in_test_module(&self) -> bool {
self.test_modules_deep != 0
}
} }
impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]);
impl<'tcx> LateLintPass<'tcx> for DisallowedNames { impl<'tcx> LateLintPass<'tcx> for DisallowedNames {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if is_test_module_or_function(cx.tcx, item) {
self.test_modules_deep = self.test_modules_deep.saturating_add(1);
}
}
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
// Check whether we are under the `test` attribute. if let PatKind::Binding(.., ident, _) = pat.kind
if self.in_test_module() { && self.disallow.contains(&ident.name.to_string())
return; && !is_in_test(cx.tcx, pat.hir_id)
} {
span_lint(
if let PatKind::Binding(.., ident, _) = pat.kind { cx,
if self.disallow.contains(&ident.name.to_string()) { DISALLOWED_NAMES,
span_lint( ident.span,
cx, format!("use of a disallowed/placeholder name `{}`", ident.name),
DISALLOWED_NAMES, );
ident.span,
format!("use of a disallowed/placeholder name `{}`", ident.name),
);
}
}
}
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if is_test_module_or_function(cx.tcx, item) {
self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
} }
} }
} }

View File

@ -82,30 +82,25 @@ impl EarlyLintPass for DisallowedScriptIdents {
// Note: `symbol.as_str()` is an expensive operation, thus should not be called // Note: `symbol.as_str()` is an expensive operation, thus should not be called
// more than once for a single symbol. // more than once for a single symbol.
let symbol_str = symbol.as_str(); let symbol_str = symbol.as_str();
if symbol_str.is_ascii() {
continue;
}
for c in symbol_str.chars() { // Check if any character in the symbol is not part of any allowed script.
// We want to iterate through all the scripts associated with this character // Fast path for ascii-only idents.
// and check whether at least of one scripts is in the whitelist. if !symbol_str.is_ascii()
let forbidden_script = c && let Some(script) = symbol_str.chars().find_map(|c| {
.script_extension() c.script_extension()
.iter() .iter()
.find(|script| !self.whitelist.contains(script)); .find(|script| !self.whitelist.contains(script))
if let Some(script) = forbidden_script { })
span_lint( {
cx, span_lint(
DISALLOWED_SCRIPT_IDENTS, cx,
span, DISALLOWED_SCRIPT_IDENTS,
format!( span,
"identifier `{symbol_str}` has a Unicode script that is not allowed by configuration: {}", format!(
script.full_name() "identifier `{symbol_str}` has a Unicode script that is not allowed by configuration: {}",
), script.full_name()
); ),
// We don't want to spawn warning multiple times over a single identifier. );
break;
}
} }
} }
} }

View File

@ -22,6 +22,7 @@ pub(super) fn check(
range: Range<usize>, range: Range<usize>,
mut span: Span, mut span: Span,
containers: &[super::Container], containers: &[super::Container],
line_break_span: Span,
) { ) {
if doc[range.clone()].contains('\t') { if doc[range.clone()].contains('\t') {
// We don't do tab stops correctly. // We don't do tab stops correctly.
@ -46,11 +47,35 @@ pub(super) fn check(
.sum(); .sum();
if ccount < blockquote_level || lcount < list_indentation { if ccount < blockquote_level || lcount < list_indentation {
let msg = if ccount < blockquote_level { let msg = if ccount < blockquote_level {
"doc quote missing `>` marker" "doc quote line without `>` marker"
} else { } else {
"doc list item missing indentation" "doc list item without indentation"
}; };
span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
let snippet = clippy_utils::source::snippet(cx, line_break_span, "");
if snippet.chars().filter(|&c| c == '\n').count() > 1
&& let Some(doc_comment_start) = snippet.rfind('\n')
&& let doc_comment = snippet[doc_comment_start..].trim()
&& (doc_comment == "///" || doc_comment == "//!")
{
// suggest filling in a blank line
diag.span_suggestion_with_style(
line_break_span.shrink_to_lo(),
"if this should be its own paragraph, add a blank doc comment line",
format!("\n{doc_comment}"),
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
if ccount > 0 || blockquote_level > 0 {
diag.help("if this not intended to be a quote at all, escape it with `\\>`");
} else {
let indent = list_indentation - lcount;
diag.help(format!(
"if this is intended to be part of the list, indent {indent} spaces"
));
}
return;
}
if ccount == 0 && blockquote_level == 0 { if ccount == 0 && blockquote_level == 0 {
// simpler suggestion style for indentation // simpler suggestion style for indentation
let indent = list_indentation - lcount; let indent = list_indentation - lcount;

View File

@ -6,7 +6,8 @@ use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable; use clippy_utils::visitors::Visitable;
use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args};
use pulldown_cmark::Event::{ use pulldown_cmark::Event::{
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, TaskListMarker, Text, Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
TaskListMarker, Text,
}; };
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph}; use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd}; use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
@ -747,7 +748,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
}, },
Start(FootnoteDefinition(..)) => in_footnote_definition = true, Start(FootnoteDefinition(..)) => in_footnote_definition = true,
End(TagEnd::FootnoteDefinition) => in_footnote_definition = false, End(TagEnd::FootnoteDefinition) => in_footnote_definition = false,
Start(_) | End(_) => (), // We don't care about other tags Start(_) | End(_) // We don't care about other tags
| TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (),
SoftBreak | HardBreak => { SoftBreak | HardBreak => {
if !containers.is_empty() if !containers.is_empty()
&& let Some((next_event, next_range)) = events.peek() && let Some((next_event, next_range)) = events.peek()
@ -762,13 +764,24 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
range.end..next_range.start, range.end..next_range.start,
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()), Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
&containers[..], &containers[..],
span,
); );
} }
}, },
TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (),
FootnoteReference(text) | Text(text) => { FootnoteReference(text) | Text(text) => {
paragraph_range.end = range.end; paragraph_range.end = range.end;
ticks_unbalanced |= text.contains('`') && !in_code; let range_ = range.clone();
ticks_unbalanced |= text.contains('`')
&& !in_code
&& doc[range.clone()].bytes().enumerate().any(|(i, c)| {
// scan the markdown source code bytes for backquotes that aren't preceded by backslashes
// - use bytes, instead of chars, to avoid utf8 decoding overhead (special chars are ascii)
// - relevant backquotes are within doc[range], but backslashes are not, because they're not
// actually part of the rendered text (pulldown-cmark doesn't emit any events for escapes)
// - if `range_.start + i == 0`, then `range_.start + i - 1 == -1`, and since we're working in
// usize, that would underflow and maybe panic
c == b'`' && (range_.start + i == 0 || doc.as_bytes().get(range_.start + i - 1) != Some(&b'\\'))
});
if Some(&text) == in_link.as_ref() || ticks_unbalanced { if Some(&text) == in_link.as_ref() || ticks_unbalanced {
// Probably a link of the form `<http://example.com>` // Probably a link of the form `<http://example.com>`
// Which are represented as a link to "http://example.com" with // Which are represented as a link to "http://example.com" with

View File

@ -33,7 +33,7 @@ declare_clippy_lint! {
/// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`. /// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`.
/// ///
/// ### Why restrict this? /// ### Why restrict this?
/// To ensure use of big endian or the targets endianness rather than little endian. /// To ensure use of big-endian or the targets endianness rather than little-endian.
/// ///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore
@ -51,7 +51,7 @@ declare_clippy_lint! {
/// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`. /// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`.
/// ///
/// ### Why restrict this? /// ### Why restrict this?
/// To ensure use of little endian or the targets endianness rather than big endian. /// To ensure use of little-endian or the targets endianness rather than big-endian.
/// ///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_in_test_function; use clippy_utils::is_in_test;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
@ -41,7 +41,7 @@ fn report(cx: &LateContext<'_>, param: &GenericParam<'_>, generics: &Generics<'_
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) { pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
if let FnKind::ItemFn(_, generics, _) = kind if let FnKind::ItemFn(_, generics, _) = kind
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, hir_id) && !is_in_test(cx.tcx, hir_id)
{ {
for param in generics.params { for param in generics.params {
if param.is_impl_trait() { if param.is_impl_trait() {
@ -59,7 +59,7 @@ pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
&& of_trait.is_none() && of_trait.is_none()
&& let body = cx.tcx.hir().body(body_id) && let body = cx.tcx.hir().body(body_id)
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, impl_item.hir_id()) && !is_in_test(cx.tcx, impl_item.hir_id())
{ {
for param in impl_item.generics.params { for param in impl_item.generics.params {
if param.is_impl_trait() { if param.is_impl_trait() {
@ -75,7 +75,7 @@ pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>,
&& let hir::Node::Item(item) = cx.tcx.parent_hir_node(trait_item.hir_id()) && let hir::Node::Item(item) = cx.tcx.parent_hir_node(trait_item.hir_id())
// ^^ (Will always be a trait) // ^^ (Will always be a trait)
&& !item.vis_span.is_empty() // Is public && !item.vis_span.is_empty() // Is public
&& !is_in_test_function(cx.tcx, trait_item.hir_id()) && !is_in_test(cx.tcx, trait_item.hir_id())
{ {
for param in trait_item.generics.params { for param in trait_item.generics.params {
if param.is_impl_trait() { if param.is_impl_trait() {

View File

@ -1,6 +1,6 @@
use clippy_config::msrvs::Msrv; use clippy_config::msrvs::Msrv;
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_in_test_function; use clippy_utils::is_in_test;
use rustc_attr::{StabilityLevel, StableSince}; use rustc_attr::{StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind, HirId}; use rustc_hir::{Expr, ExprKind, HirId};
@ -88,7 +88,7 @@ impl IncompatibleMsrv {
return; return;
} }
let version = self.get_def_id_version(cx.tcx, def_id); let version = self.get_def_id_version(cx.tcx, def_id);
if self.msrv.meets(version) || is_in_test_function(cx.tcx, node) { if self.msrv.meets(version) || is_in_test(cx.tcx, node) {
return; return;
} }
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind { if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {

View File

@ -91,10 +91,6 @@ declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_S
impl<'tcx> LateLintPass<'tcx> for InherentToString { impl<'tcx> LateLintPass<'tcx> for InherentToString {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
if impl_item.span.from_expansion() {
return;
}
// Check if item is a method called `to_string` and has a parameter 'self' // Check if item is a method called `to_string` and has a parameter 'self'
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// #11201 // #11201
@ -106,6 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
&& decl.implicit_self.has_implicit_self() && decl.implicit_self.has_implicit_self()
&& decl.inputs.len() == 1 && decl.inputs.len() == 1
&& impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }))
&& !impl_item.span.from_expansion()
// Check if return type is String // Check if return type is String
&& is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String) && is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String)
// Filters instances of to_string which are required by a trait // Filters instances of to_string which are required by a trait

View File

@ -1,13 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -44,38 +43,56 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]);
impl<'tcx> LateLintPass<'tcx> for NumberedFields { impl<'tcx> LateLintPass<'tcx> for NumberedFields {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Struct(path, fields, None) = e.kind { if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind
if !fields.is_empty() // If the first character of any field is a digit it has to be a tuple.
&& !e.span.from_expansion() && field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit)
&& fields // Type aliases can't be used as functions.
.iter() && !matches!(
.all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit)) cx.qpath_res(path, e.hir_id),
&& !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..)) Res::Def(DefKind::TyAlias | DefKind::AssocTy, _)
{ )
let expr_spans = fields // This is the only syntax macros can use that works for all struct types.
.iter() && !e.span.from_expansion()
.map(|f| (Reverse(f.ident.as_str().parse::<usize>().unwrap()), f.expr.span)) && let mut has_side_effects = false
.collect::<BinaryHeap<_>>(); && let Ok(mut expr_spans) = fields
let mut appl = Applicability::MachineApplicable; .iter()
let snippet = format!( .map(|f| {
"{}({})", has_side_effects |= f.expr.can_have_side_effects();
snippet_with_applicability(cx, path.span(), "..", &mut appl), f.ident.as_str().parse::<usize>().map(|x| (x, f.expr.span))
expr_spans })
.into_iter_sorted() .collect::<Result<Vec<_>, _>>()
.map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) // We can only reorder the expressions if there are no side effects.
.intersperse(Cow::Borrowed(", ")) && (!has_side_effects || expr_spans.is_sorted_by_key(|&(idx, _)| idx))
.collect::<String>() {
); span_lint_and_then(
span_lint_and_sugg( cx,
cx, INIT_NUMBERED_FIELDS,
INIT_NUMBERED_FIELDS, e.span,
e.span, "used a field initializer for a tuple struct",
"used a field initializer for a tuple struct", |diag| {
"try", if !has_side_effects {
snippet, // We already checked the order if there are side effects.
appl, expr_spans.sort_by_key(|&(idx, _)| idx);
); }
} let mut app = Applicability::MachineApplicable;
diag.span_suggestion(
e.span,
"use tuple initialization",
format!(
"{}({})",
snippet_with_applicability(cx, path.span(), "..", &mut app),
expr_spans
.into_iter()
.map(
|(_, span)| snippet_with_context(cx, span, SyntaxContext::root(), "..", &mut app).0
)
.intersperse(Cow::Borrowed(", "))
.collect::<String>()
),
app,
);
},
);
} }
} }
} }

View File

@ -2,12 +2,11 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::DiagExt; use clippy_utils::sugg::DiagExt;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::{sym, Symbol}; use rustc_span::sym;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -34,27 +33,23 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody { impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind { if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind
let attrs = cx.tcx.hir().attrs(item.hir_id()); && let Some(attr) = cx
check_attrs(cx, item.ident.name, attrs); .tcx
.hir()
.attrs(item.hir_id())
.iter()
.find(|a| a.has_name(sym::inline))
{
span_lint_and_then(
cx,
INLINE_FN_WITHOUT_BODY,
attr.span,
format!("use of `#[inline]` on trait method `{}` which has no body", item.ident),
|diag| {
diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable);
},
);
} }
} }
} }
fn check_attrs(cx: &LateContext<'_>, name: Symbol, attrs: &[Attribute]) {
for attr in attrs {
if !attr.has_name(sym::inline) {
continue;
}
span_lint_and_then(
cx,
INLINE_FN_WITHOUT_BODY,
attr.span,
format!("use of `#[inline]` on trait method `{name}` which has no body"),
|diag| {
diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable);
},
);
}
}

View File

@ -85,16 +85,19 @@ impl LateLintPass<'_> for InstantSubtraction {
lhs, lhs,
rhs, rhs,
) = expr.kind ) = expr.kind
&& let typeck = cx.typeck_results()
&& ty::is_type_diagnostic_item(cx, typeck.expr_ty(lhs), sym::Instant)
{ {
let rhs_ty = typeck.expr_ty(rhs);
if is_instant_now_call(cx, lhs) if is_instant_now_call(cx, lhs)
&& is_an_instant(cx, rhs) && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant)
&& let Some(sugg) = Sugg::hir_opt(cx, rhs) && let Some(sugg) = Sugg::hir_opt(cx, rhs)
{ {
print_manual_instant_elapsed_sugg(cx, expr, sugg); print_manual_instant_elapsed_sugg(cx, expr, sugg);
} else if !expr.span.from_expansion() } else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration)
&& !expr.span.from_expansion()
&& self.msrv.meets(msrvs::TRY_FROM) && self.msrv.meets(msrvs::TRY_FROM)
&& is_an_instant(cx, lhs)
&& is_a_duration(cx, rhs)
{ {
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
} }
@ -115,16 +118,6 @@ fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
} }
} }
fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr);
ty::is_type_diagnostic_item(cx, expr_ty, sym::Instant)
}
fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr);
ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration)
}
fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,

View File

@ -54,36 +54,35 @@ declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
impl LateLintPass<'_> for ItemsAfterStatements { impl LateLintPass<'_> for ItemsAfterStatements {
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
if in_external_macro(cx.sess(), block.span) { if block.stmts.len() > 1 {
return; let ctxt = block.span.ctxt();
} let mut in_external = None;
block
// skip initial items .stmts
let stmts = block .iter()
.stmts .skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)))
.iter() .filter_map(|stmt| match stmt.kind {
.skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..))); StmtKind::Item(id) => Some(cx.tcx.hir().item(id)),
_ => None,
// lint on all further items })
for stmt in stmts { // Ignore macros since they can only see previously defined locals.
if let StmtKind::Item(item_id) = stmt.kind { .filter(|item| !matches!(item.kind, ItemKind::Macro(..)))
let item = cx.tcx.hir().item(item_id); // Stop linting if macros define items.
if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) { .take_while(|item| item.span.ctxt() == ctxt)
return; // Don't use `next` due to the complex filter chain.
} .for_each(|item| {
if let ItemKind::Macro(..) = item.kind { // Only do the macro check once, but delay it until it's needed.
// do not lint `macro_rules`, but continue processing further statements if !*in_external.get_or_insert_with(|| in_external_macro(cx.sess(), block.span)) {
continue; span_lint_hir(
} cx,
span_lint_hir( ITEMS_AFTER_STATEMENTS,
cx, item.hir_id(),
ITEMS_AFTER_STATEMENTS, item.span,
item.hir_id(), "adding items after statements is confusing, since items exist from the \
item.span, start of the scope",
"adding items after statements is confusing, since items exist from the \ );
start of the scope", }
); });
}
} }
} }
} }

View File

@ -4,7 +4,7 @@ use rustc_hir::def_id::LocalDefId;
use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym; use rustc_span::{sym, Symbol};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -43,30 +43,27 @@ declare_lint_pass!(IterNotReturningIterator => [ITER_NOT_RETURNING_ITERATOR]);
impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator { impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
let name = item.ident.name.as_str(); if let TraitItemKind::Fn(fn_sig, _) = &item.kind
if matches!(name, "iter" | "iter_mut") { && matches!(item.ident.name, sym::iter | sym::iter_mut)
if let TraitItemKind::Fn(fn_sig, _) = &item.kind { {
check_sig(cx, name, fn_sig, item.owner_id.def_id); check_sig(cx, item.ident.name, fn_sig, item.owner_id.def_id);
}
} }
} }
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) {
let name = item.ident.name.as_str(); if let ImplItemKind::Fn(fn_sig, _) = &item.kind
if matches!(name, "iter" | "iter_mut") && matches!(item.ident.name, sym::iter | sym::iter_mut)
&& !matches!( && !matches!(
cx.tcx.parent_hir_node(item.hir_id()), cx.tcx.parent_hir_node(item.hir_id()),
Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some() Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some()
) )
{ {
if let ImplItemKind::Fn(fn_sig, _) = &item.kind { check_sig(cx, item.ident.name, fn_sig, item.owner_id.def_id);
check_sig(cx, name, fn_sig, item.owner_id.def_id);
}
} }
} }
} }
fn check_sig(cx: &LateContext<'_>, name: &str, sig: &FnSig<'_>, fn_id: LocalDefId) { fn check_sig(cx: &LateContext<'_>, name: Symbol, sig: &FnSig<'_>, fn_id: LocalDefId) {
if sig.decl.implicit_self.has_implicit_self() { if sig.decl.implicit_self.has_implicit_self() {
let ret_ty = cx let ret_ty = cx
.tcx .tcx

View File

@ -125,13 +125,13 @@ fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
impl LateLintPass<'_> for IterWithoutIntoIter { impl LateLintPass<'_> for IterWithoutIntoIter {
fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
if !in_external_macro(cx.sess(), item.span) if let ItemKind::Impl(imp) = item.kind
&& let ItemKind::Impl(imp) = item.kind
&& let TyKind::Ref(_, self_ty_without_ref) = &imp.self_ty.kind && let TyKind::Ref(_, self_ty_without_ref) = &imp.self_ty.kind
&& let Some(trait_ref) = imp.of_trait && let Some(trait_ref) = imp.of_trait
&& trait_ref && trait_ref
.trait_def_id() .trait_def_id()
.is_some_and(|did| cx.tcx.is_diagnostic_item(sym::IntoIterator, did)) .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::IntoIterator, did))
&& !in_external_macro(cx.sess(), item.span)
&& let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind() && let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
&& let expected_method_name = match mtbl { && let expected_method_name = match mtbl {
Mutability::Mut => sym::iter_mut, Mutability::Mut => sym::iter_mut,

View File

@ -46,12 +46,12 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !item.span.from_expansion() if let ItemKind::Const(_, generics, _) = &item.kind
&& let ItemKind::Const(_, generics, _) = &item.kind
// Since static items may not have generics, skip generic const items. // Since static items may not have generics, skip generic const items.
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it // FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
// doesn't account for empty where-clauses that only consist of keyword `where` IINM. // doesn't account for empty where-clauses that only consist of keyword `where` IINM.
&& generics.params.is_empty() && !generics.has_where_clause_predicates && generics.params.is_empty() && !generics.has_where_clause_predicates
&& !item.span.from_expansion()
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Array(element_type, cst) = ty.kind() && let ty::Array(element_type, cst) = ty.kind()
&& let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind()

View File

@ -77,17 +77,12 @@ impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
if in_external_macro(cx.tcx.sess, item.span) { if let ItemKind::Enum(ref def, _) = item.kind
return; && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
} && let ty::Adt(adt, subst) = ty.kind()
if let ItemKind::Enum(ref def, _) = item.kind { && adt.variants().len() > 1
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); && !in_external_macro(cx.tcx.sess, item.span)
let ty::Adt(adt, subst) = ty.kind() else { {
panic!("already checked whether this is an enum")
};
if adt.variants().len() <= 1 {
return;
}
let variants_size = AdtVariantInfo::new(cx, *adt, subst); let variants_size = AdtVariantInfo::new(cx, *adt, subst);
let mut difference = variants_size[0].size - variants_size[1].size; let mut difference = variants_size[0].size - variants_size[1].size;

View File

@ -54,29 +54,26 @@ impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
impl<'tcx> LateLintPass<'tcx> for LargeFuture { impl<'tcx> LateLintPass<'tcx> for LargeFuture {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) { if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind
return; && let ExprKind::Call(func, [arg, ..]) = scrutinee.kind
} && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind { && !expr.span.from_expansion()
if let ExprKind::Call(func, [expr, ..]) = expr.kind && let ty = cx.typeck_results().expr_ty(arg)
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& let ty = cx.typeck_results().expr_ty(expr) && implements_trait(cx, ty, future_trait_def_id, &[])
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
&& implements_trait(cx, ty, future_trait_def_id, &[]) && let size = layout.layout.size()
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) && size >= Size::from_bytes(self.future_size_threshold)
&& let size = layout.layout.size() {
&& size >= Size::from_bytes(self.future_size_threshold) span_lint_and_sugg(
{ cx,
span_lint_and_sugg( LARGE_FUTURES,
cx, arg.span,
LARGE_FUTURES, format!("large future with a size of {} bytes", size.bytes()),
expr.span, "consider `Box::pin` on it",
format!("large future with a size of {} bytes", size.bytes()), format!("Box::pin({})", snippet(cx, arg.span, "..")),
"consider `Box::pin` on it", Applicability::Unspecified,
format!("Box::pin({})", snippet(cx, expr.span, "..")), );
Applicability::Unspecified,
);
}
} }
} }
} }

View File

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_lint_allowed;
use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::macros::root_macro_call_first_node;
use rustc_ast::LitKind; use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -52,24 +51,19 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
impl LateLintPass<'_> for LargeIncludeFile { impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) if let ExprKind::Lit(lit) = &expr.kind
&& !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id) && let len = match &lit.node {
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
&& let ExprKind::Lit(lit) = &expr.kind
{
let len = match &lit.node {
// include_bytes // include_bytes
LitKind::ByteStr(bstr, _) => bstr.len(), LitKind::ByteStr(bstr, _) => bstr.len(),
// include_str // include_str
LitKind::Str(sym, _) => sym.as_str().len(), LitKind::Str(sym, _) => sym.as_str().len(),
_ => return, _ => return,
};
if len as u64 <= self.max_file_size {
return;
} }
&& len as u64 > self.max_file_size
&& let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
{
span_lint_and_note( span_lint_and_note(
cx, cx,
LARGE_INCLUDE_FILE, LARGE_INCLUDE_FILE,

View File

@ -48,15 +48,11 @@ impl_lint_pass!(LegacyNumericConstants => [LEGACY_NUMERIC_CONSTANTS]);
impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
let Self { msrv } = self;
if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), item.span) {
return;
}
// Integer modules are "TBD" deprecated, and the contents are too, // Integer modules are "TBD" deprecated, and the contents are too,
// so lint on the `use` statement directly. // so lint on the `use` statement directly.
if let ItemKind::Use(path, kind @ (UseKind::Single | UseKind::Glob)) = item.kind if let ItemKind::Use(path, kind @ (UseKind::Single | UseKind::Glob)) = item.kind
&& self.msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS)
&& !in_external_macro(cx.sess(), item.span)
&& let Some(def_id) = path.res[0].opt_def_id() && let Some(def_id) = path.res[0].opt_def_id()
{ {
let module = if is_integer_module(cx, def_id) { let module = if is_integer_module(cx, def_id) {
@ -103,12 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
let Self { msrv } = self; let ExprKind::Path(qpath) = &expr.kind else {
if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), expr.span) {
return;
}
let ExprKind::Path(qpath) = expr.kind else {
return; return;
}; };
@ -129,10 +120,10 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
) )
// `<integer>::xxx_value` check // `<integer>::xxx_value` check
} else if let QPath::TypeRelative(_, last_segment) = qpath } else if let QPath::TypeRelative(_, last_segment) = qpath
&& let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() && let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id()
&& is_integer_method(cx, def_id)
&& let Some(par_expr) = get_parent_expr(cx, expr) && let Some(par_expr) = get_parent_expr(cx, expr)
&& let ExprKind::Call(_, _) = par_expr.kind && let ExprKind::Call(_, []) = par_expr.kind
&& is_integer_method(cx, def_id)
{ {
let name = last_segment.ident.name.as_str(); let name = last_segment.ident.name.as_str();
@ -145,19 +136,20 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
return; return;
}; };
if is_from_proc_macro(cx, expr) { if self.msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS)
return; && !in_external_macro(cx.sess(), expr.span)
&& !is_from_proc_macro(cx, expr)
{
span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| {
diag.span_suggestion_with_style(
span,
"use the associated constant instead",
sugg,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
});
} }
span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| {
diag.span_suggestion_with_style(
span,
"use the associated constant instead",
sugg,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
});
} }
extract_msrv_attr!(LateContext); extract_msrv_attr!(LateContext);

View File

@ -121,11 +121,9 @@ declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMP
impl<'tcx> LateLintPass<'tcx> for LenZero { impl<'tcx> LateLintPass<'tcx> for LenZero {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if item.span.from_expansion() { if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind
return; && !item.span.from_expansion()
} {
if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind {
check_trait_items(cx, item, trait_items); check_trait_items(cx, item, trait_items);
} }
} }
@ -162,17 +160,14 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Let(lt) = expr.kind if let ExprKind::Let(lt) = expr.kind
&& has_is_empty(cx, lt.init)
&& match lt.pat.kind { && match lt.pat.kind {
PatKind::Slice([], None, []) => true, PatKind::Slice([], None, []) => true,
PatKind::Lit(lit) if is_empty_string(lit) => true, PatKind::Lit(lit) if is_empty_string(lit) => true,
_ => false, _ => false,
} }
&& !expr.span.from_expansion()
&& has_is_empty(cx, lt.init)
{ {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
@ -190,7 +185,9 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
); );
} }
if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind { if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind
&& !expr.span.from_expansion()
{
// expr.span might contains parenthesis, see issue #10529 // expr.span might contains parenthesis, see issue #10529
let actual_span = span_without_enclosing_paren(cx, expr.span); let actual_span = span_without_enclosing_paren(cx, expr.span);
match cmp { match cmp {

View File

@ -58,12 +58,10 @@ declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]);
impl<'tcx> LateLintPass<'tcx> for LetIfSeq { impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let mut it = block.stmts.iter().peekable(); for [stmt, next] in block.stmts.array_windows::<2>() {
while let Some(stmt) = it.next() { if let hir::StmtKind::Let(local) = stmt.kind
if let Some(expr) = it.peek()
&& let hir::StmtKind::Let(local) = stmt.kind
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
&& let hir::StmtKind::Expr(if_) = expr.kind && let hir::StmtKind::Expr(if_) = next.kind
&& let hir::ExprKind::If( && let hir::ExprKind::If(
hir::Expr { hir::Expr {
kind: hir::ExprKind::DropTemps(cond), kind: hir::ExprKind::DropTemps(cond),

View File

@ -139,9 +139,9 @@ const SYNC_GUARD_PATHS: [&[&str]; 3] = [
impl<'tcx> LateLintPass<'tcx> for LetUnderscore { impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) {
if matches!(local.source, LocalSource::Normal) if matches!(local.source, LocalSource::Normal)
&& !in_external_macro(cx.tcx.sess, local.span)
&& let PatKind::Wild = local.pat.kind && let PatKind::Wild = local.pat.kind
&& let Some(init) = local.init && let Some(init) = local.init
&& !in_external_macro(cx.tcx.sess, local.span)
{ {
let init_ty = cx.typeck_results().expr_ty(init); let init_ty = cx.typeck_results().expr_ty(init);
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet; use clippy_utils::is_from_proc_macro;
use rustc_hir::{LetStmt, TyKind}; use rustc_hir::{LetStmt, TyKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -25,19 +25,14 @@ declare_clippy_lint! {
} }
declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]); declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped { impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'_>, local: &LetStmt<'_>) { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
if !in_external_macro(cx.tcx.sess, local.span) if let Some(ty) = local.ty // Ensure that it has a type defined
&& let Some(ty) = local.ty // Ensure that it has a type defined
&& let TyKind::Infer = &ty.kind // that type is '_' && let TyKind::Infer = &ty.kind // that type is '_'
&& local.span.eq_ctxt(ty.span) && local.span.eq_ctxt(ty.span)
&& !in_external_macro(cx.tcx.sess, local.span)
&& !is_from_proc_macro(cx, ty)
{ {
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help( span_lint_and_help(
cx, cx,
LET_WITH_TYPE_UNDERSCORE, LET_WITH_TYPE_UNDERSCORE,

View File

@ -4,7 +4,9 @@
#![feature(f128)] #![feature(f128)]
#![feature(f16)] #![feature(f16)]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(is_sorted)]
#![feature(iter_intersperse)] #![feature(iter_intersperse)]
#![feature(iter_partition_in_place)]
#![feature(let_chains)] #![feature(let_chains)]
#![cfg_attr(bootstrap, feature(lint_reasons))] #![cfg_attr(bootstrap, feature(lint_reasons))]
#![feature(never_type)] #![feature(never_type)]
@ -90,8 +92,10 @@ mod bool_to_int_with_if;
mod booleans; mod booleans;
mod borrow_deref_ref; mod borrow_deref_ref;
mod box_default; mod box_default;
mod byte_char_slices;
mod cargo; mod cargo;
mod casts; mod casts;
mod cfg_not_test;
mod checked_conversions; mod checked_conversions;
mod cognitive_complexity; mod cognitive_complexity;
mod collapsible_if; mod collapsible_if;
@ -212,6 +216,7 @@ mod manual_non_exhaustive;
mod manual_range_patterns; mod manual_range_patterns;
mod manual_rem_euclid; mod manual_rem_euclid;
mod manual_retain; mod manual_retain;
mod manual_rotate;
mod manual_slice_size_calculation; mod manual_slice_size_calculation;
mod manual_string_new; mod manual_string_new;
mod manual_strip; mod manual_strip;
@ -229,6 +234,7 @@ mod mismatching_type_param_order;
mod missing_assert_message; mod missing_assert_message;
mod missing_asserts_for_indexing; mod missing_asserts_for_indexing;
mod missing_const_for_fn; mod missing_const_for_fn;
mod missing_const_for_thread_local;
mod missing_doc; mod missing_doc;
mod missing_enforced_import_rename; mod missing_enforced_import_rename;
mod missing_fields_in_debug; mod missing_fields_in_debug;
@ -275,9 +281,9 @@ mod only_used_in_recursion;
mod operators; mod operators;
mod option_env_unwrap; mod option_env_unwrap;
mod option_if_let_else; mod option_if_let_else;
mod overflow_check_conditional;
mod panic_in_result_fn; mod panic_in_result_fn;
mod panic_unimplemented; mod panic_unimplemented;
mod panicking_overflow_checks;
mod partial_pub_fields; mod partial_pub_fields;
mod partialeq_ne_impl; mod partialeq_ne_impl;
mod partialeq_to_none; mod partialeq_to_none;
@ -318,6 +324,7 @@ mod self_named_constructors;
mod semicolon_block; mod semicolon_block;
mod semicolon_if_nothing_returned; mod semicolon_if_nothing_returned;
mod serde_api; mod serde_api;
mod set_contains_or_insert;
mod shadow; mod shadow;
mod significant_drop_tightening; mod significant_drop_tightening;
mod single_call_fn; mod single_call_fn;
@ -339,7 +346,6 @@ mod swap_ptr_to_ref;
mod tabs_in_doc_comments; mod tabs_in_doc_comments;
mod temporary_assignment; mod temporary_assignment;
mod tests_outside_test_module; mod tests_outside_test_module;
mod thread_local_initializer_can_be_made_const;
mod to_digit_is_some; mod to_digit_is_some;
mod to_string_trait_impl; mod to_string_trait_impl;
mod trailing_empty_array; mod trailing_empty_array;
@ -639,9 +645,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
}); });
store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce)); store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls)); store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| {
Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new())
});
store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths)); store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths));
store.register_late_pass(|_| { store.register_late_pass(|_| {
Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default() Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default()
@ -788,7 +791,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
let format_args = format_args_storage.clone(); let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone())));
store.register_late_pass(|_| Box::new(swap::Swap)); store.register_late_pass(|_| Box::new(swap::Swap));
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional)); store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default()); store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names))); store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names)));
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
@ -1024,6 +1027,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate));
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(operators::Operators::new( Box::new(operators::Operators::new(
verbose_bit_mask_threshold, verbose_bit_mask_threshold,
@ -1153,9 +1157,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
behavior: pub_underscore_fields_behavior, behavior: pub_underscore_fields_behavior,
}) })
}); });
store.register_late_pass(move |_| { store
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv())) .register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(msrv())));
});
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv()))); 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_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
@ -1171,6 +1174,9 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
}); });
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv())));
store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains));
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
// add lints here, do not remove this comment, it's used in `new_lint` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View File

@ -233,11 +233,9 @@ impl_lint_pass!(LiteralDigitGrouping => [
impl EarlyLintPass for LiteralDigitGrouping { impl EarlyLintPass for LiteralDigitGrouping {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) { if let ExprKind::Lit(lit) = expr.kind
return; && !in_external_macro(cx.sess(), expr.span)
} {
if let ExprKind::Lit(lit) = expr.kind {
self.check_lit(cx, lit, expr.span); self.check_lit(cx, lit, expr.span);
} }
} }
@ -448,11 +446,9 @@ impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]
impl EarlyLintPass for DecimalLiteralRepresentation { impl EarlyLintPass for DecimalLiteralRepresentation {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) { if let ExprKind::Lit(lit) = expr.kind
return; && !in_external_macro(cx.sess(), expr.span)
} {
if let ExprKind::Lit(lit) = expr.kind {
self.check_lit(cx, lit, expr.span); self.check_lit(cx, lit, expr.span);
} }
} }

View File

@ -50,13 +50,10 @@ impl_lint_pass!(ManualBits => [MANUAL_BITS]);
impl<'tcx> LateLintPass<'tcx> for ManualBits { impl<'tcx> LateLintPass<'tcx> for ManualBits {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !self.msrv.meets(msrvs::MANUAL_BITS) {
return;
}
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
&& let BinOpKind::Mul = &bin_op.node && let BinOpKind::Mul = &bin_op.node
&& !in_external_macro(cx.sess(), expr.span) && !in_external_macro(cx.sess(), expr.span)
&& self.msrv.meets(msrvs::MANUAL_BITS)
&& let ctxt = expr.span.ctxt() && let ctxt = expr.span.ctxt()
&& left_expr.span.ctxt() == ctxt && left_expr.span.ctxt() == ctxt
&& right_expr.span.ctxt() == ctxt && right_expr.span.ctxt() == ctxt

View File

@ -82,29 +82,26 @@ impl Variant {
impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !in_external_macro(cx.sess(), expr.span) if let ExprKind::Binary(kind, lhs, rhs) = expr.kind
&& (
matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
|| cx.tcx.features().declared(sym!(const_float_classify))
) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind
&& let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind
&& let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind
// Checking all possible scenarios using a function would be a hopeless task, as we have // Checking all possible scenarios using a function would be a hopeless task, as we have
// 16 possible alignments of constants/operands. For now, let's use `partition`. // 16 possible alignments of constants/operands. For now, let's use `partition`.
&& let (operands, constants) = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs] && let mut exprs = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs]
.into_iter() && exprs.iter_mut().partition_in_place(|i| path_to_local(i).is_some()) == 2
.partition::<Vec<&Expr<'_>>, _>(|i| path_to_local(i).is_some()) && !in_external_macro(cx.sess(), expr.span)
&& let [first, second] = &*operands && (
&& let Some([const_1, const_2]) = constants matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
.into_iter() || cx.tcx.features().declared(sym!(const_float_classify))
.map(|i| constant(cx, cx.typeck_results(), i)) )
.collect::<Option<Vec<_>>>() && let [first, second, const_1, const_2] = exprs
.as_deref() && let Some(const_1) = constant(cx, cx.typeck_results(), const_1)
&& let Some(const_2) = constant(cx, cx.typeck_results(), const_2)
&& path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s))
// The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in
// case somebody does that for some reason // case somebody does that for some reason
&& (is_infinity(const_1) && is_neg_infinity(const_2) && (is_infinity(&const_1) && is_neg_infinity(&const_2)
|| is_neg_infinity(const_1) && is_infinity(const_2)) || is_neg_infinity(&const_1) && is_infinity(&const_2))
&& let Some(local_snippet) = snippet_opt(cx, first.span) && let Some(local_snippet) = snippet_opt(cx, first.span)
{ {
let variant = match (kind.node, lhs_kind.node, rhs_kind.node) { let variant = match (kind.node, lhs_kind.node, rhs_kind.node) {

View File

@ -49,16 +49,14 @@ declare_clippy_lint! {
impl<'tcx> QuestionMark { impl<'tcx> QuestionMark {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) { pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
return;
}
if let StmtKind::Let(local) = stmt.kind if let StmtKind::Let(local) = stmt.kind
&& let Some(init) = local.init && let Some(init) = local.init
&& local.els.is_none() && local.els.is_none()
&& local.ty.is_none() && local.ty.is_none()
&& init.span.eq_ctxt(stmt.span) && init.span.eq_ctxt(stmt.span)
&& let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init) && let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init)
&& self.msrv.meets(msrvs::LET_ELSE)
&& !in_external_macro(cx.sess(), stmt.span)
{ {
match if_let_or_match { match if_let_or_match {
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else, ..) => { IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else, ..) => {

View File

@ -47,13 +47,13 @@ impl_lint_pass!(ManualMainSeparatorStr => [MANUAL_MAIN_SEPARATOR_STR]);
impl LateLintPass<'_> for ManualMainSeparatorStr { impl LateLintPass<'_> for ManualMainSeparatorStr {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) let (target, _) = peel_hir_expr_refs(expr);
&& let (target, _) = peel_hir_expr_refs(expr) if let ExprKind::MethodCall(path, receiver, &[], _) = target.kind
&& is_trait_method(cx, target, sym::ToString)
&& let ExprKind::MethodCall(path, receiver, &[], _) = target.kind
&& path.ident.name == sym::to_string && path.ident.name == sym::to_string
&& let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind && let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind
&& let Res::Def(DefKind::Const, receiver_def_id) = path.res && let Res::Def(DefKind::Const, receiver_def_id) = path.res
&& is_trait_method(cx, target, sym::ToString)
&& self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR)
&& match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR) && match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR)
&& let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() && let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind()
&& ty.is_str() && ty.is_str()

View File

@ -97,19 +97,15 @@ impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
impl EarlyLintPass for ManualNonExhaustiveStruct { impl EarlyLintPass for ManualNonExhaustiveStruct {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) { if let ast::ItemKind::Struct(variant_data, _) = &item.kind
return; && let (fields, delimiter) = match variant_data {
}
if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
let (fields, delimiter) = match variant_data {
ast::VariantData::Struct { fields, .. } => (&**fields, '{'), ast::VariantData::Struct { fields, .. } => (&**fields, '{'),
ast::VariantData::Tuple(fields, _) => (&**fields, '('), ast::VariantData::Tuple(fields, _) => (&**fields, '('),
ast::VariantData::Unit(_) => return, ast::VariantData::Unit(_) => return,
};
if fields.len() <= 1 {
return;
} }
&& fields.len() > 1
&& self.msrv.meets(msrvs::NON_EXHAUSTIVE)
{
let mut iter = fields.iter().filter_map(|f| match f.vis.kind { let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
VisibilityKind::Public => None, VisibilityKind::Public => None,
VisibilityKind::Inherited => Some(Ok(f)), VisibilityKind::Inherited => Some(Ok(f)),

View File

@ -76,14 +76,11 @@ impl Num {
impl LateLintPass<'_> for ManualRangePatterns { impl LateLintPass<'_> for ManualRangePatterns {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) { fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
if in_external_macro(cx.sess(), pat.span) {
return;
}
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives // a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
// or at least one range // or at least one range
if let PatKind::Or(pats) = pat.kind if let PatKind::Or(pats) = pat.kind
&& (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..)))) && (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..))))
&& !in_external_macro(cx.sess(), pat.span)
{ {
let mut min = Num::dummy(i128::MAX); let mut min = Num::dummy(i128::MAX);
let mut max = Num::dummy(i128::MIN); let mut max = Num::dummy(i128::MIN);

View File

@ -48,35 +48,30 @@ impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !self.msrv.meets(msrvs::REM_EUCLID) {
return;
}
if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::REM_EUCLID_CONST) {
return;
}
if in_external_macro(cx.sess(), expr.span) {
return;
}
// (x % c + c) % c // (x % c + c) % c
if let ExprKind::Binary(op1, expr1, right) = expr.kind if let ExprKind::Binary(rem_op, rem_lhs, rem_rhs) = expr.kind
&& op1.node == BinOpKind::Rem && rem_op.node == BinOpKind::Rem
&& let ExprKind::Binary(add_op, add_lhs, add_rhs) = rem_lhs.kind
&& add_op.node == BinOpKind::Add
&& let ctxt = expr.span.ctxt() && let ctxt = expr.span.ctxt()
&& expr1.span.ctxt() == ctxt && rem_lhs.span.ctxt() == ctxt
&& let Some(const1) = check_for_unsigned_int_constant(cx, right) && rem_rhs.span.ctxt() == ctxt
&& let ExprKind::Binary(op2, left, right) = expr1.kind && add_lhs.span.ctxt() == ctxt
&& op2.node == BinOpKind::Add && add_rhs.span.ctxt() == ctxt
&& let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right) && !in_external_macro(cx.sess(), expr.span)
&& expr2.span.ctxt() == ctxt && self.msrv.meets(msrvs::REM_EUCLID)
&& let ExprKind::Binary(op3, expr3, right) = expr2.kind && (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !in_constant(cx, expr.hir_id))
&& op3.node == BinOpKind::Rem && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
&& let Some(const3) = check_for_unsigned_int_constant(cx, right) && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs)
&& let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind
&& rem2_op.node == BinOpKind::Rem
&& const1 == const2
&& let Some(hir_id) = path_to_local(rem2_lhs)
&& let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs)
// Also ensures the const is nonzero since zero can't be a divisor // Also ensures the const is nonzero since zero can't be a divisor
&& const1 == const2 && const2 == const3 && const2 == const3
&& let Some(hir_id) = path_to_local(expr3) && rem2_lhs.span.ctxt() == ctxt
&& let Node::Pat(_) = cx.tcx.hir_node(hir_id) && rem2_rhs.span.ctxt() == ctxt
{ {
// Apply only to params or locals with annotated types // Apply only to params or locals with annotated types
match cx.tcx.parent_hir_node(hir_id) { match cx.tcx.parent_hir_node(hir_id) {
@ -91,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
}; };
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let rem_of = snippet_with_context(cx, expr3.span, ctxt, "_", &mut app).0; let rem_of = snippet_with_context(cx, rem2_lhs.span, ctxt, "_", &mut app).0;
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
MANUAL_REM_EUCLID, MANUAL_REM_EUCLID,

View File

@ -70,9 +70,8 @@ impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
impl<'tcx> LateLintPass<'tcx> for ManualRetain { impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Assign(left_expr, collect_expr, _) = &expr.kind if let Assign(left_expr, collect_expr, _) = &expr.kind
&& let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind && let hir::ExprKind::MethodCall(seg, target_expr, [], _) = &collect_expr.kind
&& seg.args.is_none() && seg.args.is_none()
&& let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id) && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id) && cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
{ {

View File

@ -0,0 +1,117 @@
use std::fmt::Display;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
///
/// It detects manual bit rotations that could be rewritten using standard
/// functions `rotate_left` or `rotate_right`.
///
/// ### Why is this bad?
///
/// Calling the function better conveys the intent.
///
/// ### Known issues
///
/// Currently, the lint only catches shifts by constant amount.
///
/// ### Example
/// ```no_run
/// let x = 12345678_u32;
/// let _ = (x >> 8) | (x << 24);
/// ```
/// Use instead:
/// ```no_run
/// let x = 12345678_u32;
/// let _ = x.rotate_right(8);
/// ```
#[clippy::version = "1.81.0"]
pub MANUAL_ROTATE,
style,
"using bit shifts to rotate integers"
}
declare_lint_pass!(ManualRotate => [MANUAL_ROTATE]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ShiftDirection {
Left,
Right,
}
impl Display for ShiftDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Left => "rotate_left",
Self::Right => "rotate_right",
})
}
}
fn parse_shift<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> Option<(ShiftDirection, u128, &'tcx Expr<'tcx>)> {
if let ExprKind::Binary(op, l, r) = expr.kind {
let dir = match op.node {
BinOpKind::Shl => ShiftDirection::Left,
BinOpKind::Shr => ShiftDirection::Right,
_ => return None,
};
let const_expr = constant(cx, cx.typeck_results(), r)?;
if let Constant::Int(shift) = const_expr {
return Some((dir, shift, l));
}
}
None
}
impl LateLintPass<'_> for ManualRotate {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Binary(op, l, r) = expr.kind
&& let BinOpKind::Add | BinOpKind::BitOr = op.node
&& let Some((l_shift_dir, l_amount, l_expr)) = parse_shift(cx, l)
&& let Some((r_shift_dir, r_amount, r_expr)) = parse_shift(cx, r)
{
if l_shift_dir == r_shift_dir {
return;
}
if !clippy_utils::eq_expr_value(cx, l_expr, r_expr) {
return;
}
let Some(bit_width) = (match cx.typeck_results().expr_ty(expr).kind() {
ty::Int(itype) => itype.bit_width(),
ty::Uint(itype) => itype.bit_width(),
_ => return,
}) else {
return;
};
if l_amount + r_amount == u128::from(bit_width) {
let (shift_function, amount) = if l_amount < r_amount {
(l_shift_dir, l_amount)
} else {
(r_shift_dir, r_amount)
};
let mut applicability = Applicability::MachineApplicable;
let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_par();
span_lint_and_sugg(
cx,
MANUAL_ROTATE,
expr.span,
"there is no need to manually implement bit rotation",
"this expression can be rewritten as",
format!("{expr_sugg}.{shift_function}({amount})"),
Applicability::MachineApplicable,
);
}
}
}
}

View File

@ -40,11 +40,11 @@ declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]
impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation { impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// Does not apply inside const because size_of_val is not cost in stable. if let ExprKind::Binary(ref op, left, right) = expr.kind
if !in_constant(cx, expr.hir_id)
&& let ExprKind::Binary(ref op, left, right) = expr.kind
&& BinOpKind::Mul == op.node && BinOpKind::Mul == op.node
&& !expr.span.from_expansion() && !expr.span.from_expansion()
// Does not apply inside const because size_of_val is not cost in stable.
&& !in_constant(cx, expr.hir_id)
&& let Some(receiver) = simplify(cx, left, right) && let Some(receiver) = simplify(cx, left, right)
{ {
let ctxt = expr.span.ctxt(); let ctxt = expr.span.ctxt();

View File

@ -66,14 +66,11 @@ enum StripKind {
impl<'tcx> LateLintPass<'tcx> for ManualStrip { impl<'tcx> LateLintPass<'tcx> for ManualStrip {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
return;
}
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
&& let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind && let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
&& let ExprKind::Path(target_path) = &target_arg.kind && let ExprKind::Path(target_path) = &target_arg.kind
&& self.msrv.meets(msrvs::STR_STRIP_PREFIX)
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
{ {
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) { let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
StripKind::Prefix StripKind::Prefix

View File

@ -172,11 +172,10 @@ fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, exp
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault { impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() || in_constant(cx, expr.hir_id) { if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr)
return; && !expr.span.from_expansion()
} && !in_constant(cx, expr.hir_id)
// Call handle only if the expression is `if let` or `match` {
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr) {
handle(cx, if_let_or_match, expr); handle(cx, if_let_or_match, expr);
} }
} }

View File

@ -253,14 +253,11 @@ fn lint_map_unit_fn(
impl<'tcx> LateLintPass<'tcx> for MapUnit { impl<'tcx> LateLintPass<'tcx> for MapUnit {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
if stmt.span.from_expansion() { if let hir::StmtKind::Semi(expr) = stmt.kind
return; && !stmt.span.from_expansion()
} && let Some(arglists) = method_chain_args(expr, &["map"])
{
if let hir::StmtKind::Semi(expr) = stmt.kind { lint_map_unit_fn(cx, stmt, expr, arglists[0]);
if let Some(arglists) = method_chain_args(expr, &["map"]) {
lint_map_unit_fn(cx, stmt, expr, arglists[0]);
}
} }
} }
} }

View File

@ -1019,6 +1019,7 @@ impl_lint_pass!(Matches => [
]); ]);
impl<'tcx> LateLintPass<'tcx> for Matches { impl<'tcx> LateLintPass<'tcx> for Matches {
#[expect(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if is_direct_expn_of(expr.span, "matches").is_none() && in_external_macro(cx.sess(), expr.span) { if is_direct_expn_of(expr.span, "matches").is_none() && in_external_macro(cx.sess(), expr.span) {
return; return;
@ -1037,7 +1038,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
return; return;
} }
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
significant_drop_in_scrutinee::check(cx, expr, ex, arms, source); significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source);
} }
collapsible_match::check_match(cx, arms, &self.msrv); collapsible_match::check_match(cx, arms, &self.msrv);
@ -1084,6 +1085,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
} }
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) { } else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, &self.msrv); collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, &self.msrv);
significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else);
if !from_expansion { if !from_expansion {
if let Some(else_expr) = if_let.if_else { if let Some(else_expr) = if_let.if_else {
if self.msrv.meets(msrvs::MATCHES_MACRO) { if self.msrv.meets(msrvs::MATCHES_MACRO) {
@ -1126,8 +1128,13 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
); );
needless_match::check_if_let(cx, expr, &if_let); needless_match::check_if_let(cx, expr, &if_let);
} }
} else if !from_expansion { } else {
redundant_pattern_match::check(cx, expr); if let Some(while_let) = higher::WhileLet::hir(expr) {
significant_drop_in_scrutinee::check_while_let(cx, expr, while_let.let_expr, while_let.if_then);
}
if !from_expansion {
redundant_pattern_match::check(cx, expr);
}
} }
} }

View File

@ -16,7 +16,7 @@ use rustc_span::Span;
use super::SIGNIFICANT_DROP_IN_SCRUTINEE; use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
pub(super) fn check<'tcx>( pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>, expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>, scrutinee: &'tcx Expr<'_>,
@ -27,10 +27,89 @@ pub(super) fn check<'tcx>(
return; return;
} }
let (suggestions, message) = has_significant_drop_in_scrutinee(cx, scrutinee, source); let scrutinee = match (source, &scrutinee.kind) {
(MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
_ => scrutinee,
};
let message = if source == MatchSource::Normal {
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
} else {
"temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
};
let arms = arms.iter().map(|arm| arm.body).collect::<Vec<_>>();
check(cx, expr, scrutinee, &arms, message, Suggestion::Emit);
}
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
if_then: &'tcx Expr<'_>,
if_else: Option<&'tcx Expr<'_>>,
) {
if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
return;
}
let message =
"temporary with significant `Drop` in `if let` scrutinee will live until the end of the `if let` expression";
if let Some(if_else) = if_else {
check(cx, expr, scrutinee, &[if_then, if_else], message, Suggestion::Emit);
} else {
check(cx, expr, scrutinee, &[if_then], message, Suggestion::Emit);
}
}
pub(super) fn check_while_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
body: &'tcx Expr<'_>,
) {
if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
return;
}
check(
cx,
expr,
scrutinee,
&[body],
"temporary with significant `Drop` in `while let` scrutinee will live until the end of the `while let` expression",
// Don't emit wrong suggestions: We cannot fix the significant drop in the `while let` scrutinee by simply
// moving it out. We need to change the `while` to a `loop` instead.
Suggestion::DontEmit,
);
}
#[derive(Copy, Clone, Debug)]
enum Suggestion {
Emit,
DontEmit,
}
fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
arms: &[&'tcx Expr<'_>],
message: &'static str,
sugg: Suggestion,
) {
let mut helper = SigDropHelper::new(cx);
let suggestions = helper.find_sig_drop(scrutinee);
for found in suggestions { for found in suggestions {
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
set_diagnostic(diag, cx, expr, found); match sugg {
Suggestion::Emit => set_suggestion(diag, cx, expr, found),
Suggestion::DontEmit => (),
}
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
diag.span_label(s, "temporary lives until here"); diag.span_label(s, "temporary lives until here");
for span in has_significant_drop_in_arms(cx, arms) { for span in has_significant_drop_in_arms(cx, arms) {
@ -41,7 +120,7 @@ pub(super) fn check<'tcx>(
} }
} }
fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { fn set_suggestion<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
let original = snippet(cx, found.found_span, ".."); let original = snippet(cx, found.found_span, "..");
let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0));
@ -79,26 +158,6 @@ fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &
); );
} }
/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
/// may have a surprising lifetime.
fn has_significant_drop_in_scrutinee<'tcx>(
cx: &LateContext<'tcx>,
scrutinee: &'tcx Expr<'tcx>,
source: MatchSource,
) -> (Vec<FoundSigDrop>, &'static str) {
let mut helper = SigDropHelper::new(cx);
let scrutinee = match (source, &scrutinee.kind) {
(MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
_ => scrutinee,
};
let message = if source == MatchSource::Normal {
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
} else {
"temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
};
(helper.find_sig_drop(scrutinee), message)
}
struct SigDropChecker<'a, 'tcx> { struct SigDropChecker<'a, 'tcx> {
seen_types: FxHashSet<Ty<'tcx>>, seen_types: FxHashSet<Ty<'tcx>>,
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
@ -428,10 +487,10 @@ impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
} }
} }
fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> { fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxHashSet<Span> {
let mut helper = ArmSigDropHelper::new(cx); let mut helper = ArmSigDropHelper::new(cx);
for arm in arms { for arm in arms {
helper.visit_expr(arm.body); helper.visit_expr(arm);
} }
helper.found_sig_drop_spans helper.found_sig_drop_spans
} }

View File

@ -167,14 +167,12 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
} else { } else {
edits.extend(addr_of_edits); edits.extend(addr_of_edits);
} }
edits.push(( let edit = match name {
name_span, "map" => "inspect",
String::from(match name { "map_err" => "inspect_err",
"map" => "inspect", _ => return,
"map_err" => "inspect_err", };
_ => return, edits.push((name_span, edit.to_string()));
}),
));
edits.push(( edits.push((
final_expr final_expr
.span .span
@ -187,9 +185,15 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
} else { } else {
Applicability::MachineApplicable Applicability::MachineApplicable
}; };
span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| { span_lint_and_then(
diag.multipart_suggestion("try", edits, app); cx,
}); MANUAL_INSPECT,
name_span,
format!("using `{name}` over `{edit}`"),
|diag| {
diag.multipart_suggestion("try", edits, app);
},
);
} }
} }
} }

View File

@ -628,12 +628,11 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))`
/// `_.or_else(|x| Err(y))`. /// or `_.or_else(|x| Err(y))`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Readability, this can be written more concisely as /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`.
/// `_.map(|x| y)` or `_.map_err(|x| y)`.
/// ///
/// ### Example /// ### Example
/// ```no_run /// ```no_run
@ -4121,7 +4120,7 @@ declare_clippy_lint! {
/// ```no_run /// ```no_run
/// let x = Some(0).inspect(|x| println!("{x}")); /// let x = Some(0).inspect(|x| println!("{x}"));
/// ``` /// ```
#[clippy::version = "1.78.0"] #[clippy::version = "1.81.0"]
pub MANUAL_INSPECT, pub MANUAL_INSPECT,
complexity, complexity,
"use of `map` returning the original item" "use of `map` returning the original item"

View File

@ -1,13 +1,15 @@
use super::implicit_clone::is_clone_like; use super::implicit_clone::is_clone_like;
use super::unnecessary_iter_cloned::{self, is_into_iter}; use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt; use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{ use clippy_utils::ty::{
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs, get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
}; };
use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; use clippy_utils::{
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, match_def_path, paths, return_ty,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
@ -52,6 +54,9 @@ pub fn check<'tcx>(
if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) { if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
return; return;
} }
if check_string_from_utf8(cx, expr, receiver) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver); check_other_call_arg(cx, expr, method_name, receiver);
} }
} else { } else {
@ -240,6 +245,65 @@ fn check_into_iter_call_arg(
false false
} }
/// Checks for `&String::from_utf8(bytes.{to_vec,to_owned,...}()).unwrap()` coercing to `&str`,
/// which can be written as just `std::str::from_utf8(bytes).unwrap()`.
fn check_string_from_utf8<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, receiver: &'tcx Expr<'tcx>) -> bool {
if let Some((call, arg)) = skip_addr_of_ancestors(cx, expr)
&& !arg.span.from_expansion()
&& let ExprKind::Call(callee, _) = call.kind
&& fn_def_id(cx, call).is_some_and(|did| match_def_path(cx, did, &paths::STRING_FROM_UTF8))
&& let Some(unwrap_call) = get_parent_expr(cx, call)
&& let ExprKind::MethodCall(unwrap_method_name, ..) = unwrap_call.kind
&& matches!(unwrap_method_name.ident.name, sym::unwrap | sym::expect)
&& let Some(ref_string) = get_parent_expr(cx, unwrap_call)
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = ref_string.kind
&& let adjusted_ty = cx.typeck_results().expr_ty_adjusted(ref_string)
// `&...` creates a `&String`, so only actually lint if this coerces to a `&str`
&& matches!(adjusted_ty.kind(), ty::Ref(_, ty, _) if ty.is_str())
{
span_lint_and_then(
cx,
UNNECESSARY_TO_OWNED,
ref_string.span,
"allocating a new `String` only to create a temporary `&str` from it",
|diag| {
let arg_suggestion = format!(
"{borrow}{recv_snippet}",
recv_snippet = snippet(cx, receiver.span.source_callsite(), ".."),
borrow = if cx.typeck_results().expr_ty(receiver).is_ref() {
""
} else {
// If not already a reference, prefix with a borrow so that it can coerce to one
"&"
}
);
diag.multipart_suggestion(
"convert from `&[u8]` to `&str` directly",
vec![
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^^^^^^^^^^^^^^^^^
(callee.span, "core::str::from_utf8".into()),
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^
(
ref_string.span.shrink_to_lo().to(unwrap_call.span.shrink_to_lo()),
String::new(),
),
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^^^^^^^^^^^^^^
(arg.span, arg_suggestion),
],
Applicability::MachineApplicable,
);
},
);
true
} else {
false
}
}
/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
/// call of a `to_owned`-like function is unnecessary. /// call of a `to_owned`-like function is unnecessary.
fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; use clippy_utils::ty::{is_never_like, is_type_diagnostic_item};
use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; use clippy_utils::{is_in_test, is_lint_allowed};
use rustc_hir::Expr; use rustc_hir::Expr;
use rustc_lint::{LateContext, Lint}; use rustc_lint::{LateContext, Lint};
use rustc_middle::ty; use rustc_middle::ty;
@ -61,7 +61,7 @@ pub(super) fn check(
let method_suffix = if is_err { "_err" } else { "" }; let method_suffix = if is_err { "_err" } else { "" };
if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { if allow_unwrap_in_tests && is_in_test(cx.tcx, expr.hir_id) {
return; return;
} }

View File

@ -5,7 +5,7 @@ use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::sym; use rustc_span::sym;
use std::cmp::Ordering; use std::cmp::Ordering::{Equal, Greater, Less};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -36,26 +36,21 @@ declare_lint_pass!(MinMaxPass => [MIN_MAX]);
impl<'tcx> LateLintPass<'tcx> for MinMaxPass { impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) { if let Some((outer_max, outer_c, oe)) = min_max(cx, expr)
if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) { && let Some((inner_max, inner_c, ie)) = min_max(cx, oe)
if outer_max == inner_max { && outer_max != inner_max
return; && let Some(ord) = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c)
} && matches!(
match ( (outer_max, ord),
outer_max, (MinMax::Max, Equal | Greater) | (MinMax::Min, Equal | Less)
Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c), )
) { {
(_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (), span_lint(
_ => { cx,
span_lint( MIN_MAX,
cx, expr.span,
MIN_MAX, "this `min`/`max` combination leads to constant result",
expr.span, );
"this `min`/`max` combination leads to constant result",
);
},
}
}
} }
} }
} }

View File

@ -2,8 +2,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then, span_lint_hir_and
use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{ use clippy_utils::{
any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, is_lint_allowed, iter_input_pats, fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats, last_path_segment,
last_path_segment, SpanlessEq, SpanlessEq,
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) if in_external_macro(cx.sess(), expr.span)
|| expr.span.desugaring_kind().is_some() || expr.span.desugaring_kind().is_some()
|| any_parent_is_automatically_derived(cx.tcx, expr.hir_id) || in_automatically_derived(cx.tcx, expr.hir_id)
{ {
return; return;
} }

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_in_test;
use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, PanicExpn};
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_hir::Expr; use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingAssertMessage {
}; };
// This lint would be very noisy in tests, so just ignore if we're in test context // This lint would be very noisy in tests, so just ignore if we're in test context
if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) { if is_in_test(cx.tcx, expr.hir_id) {
return; return;
} }

View File

@ -8,9 +8,11 @@ use rustc_hir::intravisit::FnKind;
use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind}; use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::Span; use rustc_span::Span;
use rustc_target::spec::abi::Abi;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -115,7 +117,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
.iter() .iter()
.any(|param| matches!(param.kind, GenericParamKind::Const { .. })); .any(|param| matches!(param.kind, GenericParamKind::Const { .. }));
if already_const(header) || has_const_generic_params { if already_const(header)
|| has_const_generic_params
|| !could_be_const_with_abi(cx, &self.msrv, header.abi)
{
return; return;
} }
}, },
@ -127,6 +132,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
FnKind::Closure => return, FnKind::Closure => return,
} }
if fn_inputs_has_impl_trait_ty(cx, def_id) {
return;
}
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
// Const fns are not allowed as methods in a trait. // Const fns are not allowed as methods in a trait.
@ -171,3 +180,25 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
fn already_const(header: hir::FnHeader) -> bool { fn already_const(header: hir::FnHeader) -> bool {
header.constness == Constness::Const header.constness == Constness::Const
} }
fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: &Msrv, abi: Abi) -> bool {
match abi {
Abi::Rust => true,
// `const extern "C"` was stablized after 1.62.0
Abi::C { unwind: false } => msrv.meets(msrvs::CONST_EXTERN_FN),
// Rest ABIs are still unstable and need the `const_extern_fn` feature enabled.
_ => cx.tcx.features().const_extern_fn,
}
}
/// Return `true` when the given `def_id` is a function that has `impl Trait` ty as one of
/// its parameter types.
fn fn_inputs_has_impl_trait_ty(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().inputs().skip_binder();
inputs.iter().any(|input| {
matches!(
input.kind(),
ty::Alias(ty::AliasTyKind::Weak, alias_ty) if cx.tcx.type_of(alias_ty.def_id).skip_binder().is_impl_trait()
)
})
}

View File

@ -39,23 +39,23 @@ declare_clippy_lint! {
/// } /// }
/// ``` /// ```
#[clippy::version = "1.77.0"] #[clippy::version = "1.77.0"]
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, pub MISSING_CONST_FOR_THREAD_LOCAL,
perf, perf,
"suggest using `const` in `thread_local!` macro" "suggest using `const` in `thread_local!` macro"
} }
pub struct ThreadLocalInitializerCanBeMadeConst { pub struct MissingConstForThreadLocal {
msrv: Msrv, msrv: Msrv,
} }
impl ThreadLocalInitializerCanBeMadeConst { impl MissingConstForThreadLocal {
#[must_use] #[must_use]
pub fn new(msrv: Msrv) -> Self { pub fn new(msrv: Msrv) -> Self {
Self { msrv } Self { msrv }
} }
} }
impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]); impl_lint_pass!(MissingConstForThreadLocal => [MISSING_CONST_FOR_THREAD_LOCAL]);
#[inline] #[inline]
fn is_thread_local_initializer( fn is_thread_local_initializer(
@ -102,7 +102,7 @@ fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id
false false
} }
impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal {
fn check_fn( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
local_defid: rustc_span::def_id::LocalDefId, local_defid: rustc_span::def_id::LocalDefId,
) { ) {
let defid = local_defid.to_def_id(); let defid = local_defid.to_def_id();
if self.msrv.meets(msrvs::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST) if self.msrv.meets(msrvs::THREAD_LOCAL_CONST_INIT)
&& is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false) && is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false)
// Some implementations of `thread_local!` include an initializer fn. // Some implementations of `thread_local!` include an initializer fn.
// In the case of a const initializer, the init fn is also const, // In the case of a const initializer, the init fn is also const,
@ -139,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
{ {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, MISSING_CONST_FOR_THREAD_LOCAL,
unpeeled.span, unpeeled.span,
"initializer for `thread_local` value can be made `const`", "initializer for `thread_local` value can be made `const`",
"replace with", "replace with",

View File

@ -27,7 +27,7 @@ declare_clippy_lint! {
/// Check if a `&mut` function argument is actually used mutably. /// Check if a `&mut` function argument is actually used mutably.
/// ///
/// Be careful if the function is publicly reexported as it would break compatibility with /// Be careful if the function is publicly reexported as it would break compatibility with
/// users of this function. /// users of this function, when the users pass this function as an argument.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Less `mut` means less fights with the borrow checker. It can also lead to more /// Less `mut` means less fights with the borrow checker. It can also lead to more
@ -138,6 +138,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
return; return;
} }
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id) {
return;
}
let hir_id = cx.tcx.local_def_id_to_hir_id(fn_def_id); let hir_id = cx.tcx.local_def_id_to_hir_id(fn_def_id);
let is_async = match kind { let is_async = match kind {
FnKind::ItemFn(.., header) => { FnKind::ItemFn(.., header) => {
@ -262,9 +266,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
.iter() .iter()
.filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id))
{ {
let show_semver_warning =
self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id);
let mut is_cfged = None; let mut is_cfged = None;
for input in unused { for input in unused {
// If the argument is never used mutably, we emit the warning. // If the argument is never used mutably, we emit the warning.
@ -284,7 +285,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),),
Applicability::Unspecified, Applicability::Unspecified,
); );
if show_semver_warning { if cx.effective_visibilities.is_exported(*fn_def_id) {
diag.warn("changing this function will impact semver compatibility"); diag.warn("changing this function will impact semver compatibility");
} }
if *is_cfged { if *is_cfged {

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::has_drop; use clippy_utils::ty::has_drop;
use clippy_utils::{any_parent_is_automatically_derived, is_lint_allowed, path_to_local, peel_blocks}; use clippy_utils::{
in_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{ use rustc_hir::{
@ -185,7 +187,7 @@ impl NoEffect {
&& has_no_effect(cx, init) && has_no_effect(cx, init)
&& let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind && let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind
&& ident.name.to_ident_string().starts_with('_') && ident.name.to_ident_string().starts_with('_')
&& !any_parent_is_automatically_derived(cx.tcx, local.hir_id) && !in_automatically_derived(cx.tcx, local.hir_id)
{ {
if let Some(l) = self.local_bindings.last_mut() { if let Some(l) = self.local_bindings.last_mut() {
l.push(hir_id); l.push(hir_id);
@ -258,13 +260,16 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
if let StmtKind::Semi(expr) = stmt.kind if let StmtKind::Semi(expr) = stmt.kind
&& !in_external_macro(cx.sess(), stmt.span)
&& let ctxt = stmt.span.ctxt() && let ctxt = stmt.span.ctxt()
&& expr.span.ctxt() == ctxt && expr.span.ctxt() == ctxt
&& let Some(reduced) = reduce_expression(cx, expr) && let Some(reduced) = reduce_expression(cx, expr)
&& !in_external_macro(cx.sess(), stmt.span)
&& reduced.iter().all(|e| e.span.ctxt() == ctxt) && reduced.iter().all(|e| e.span.ctxt() == ctxt)
{ {
if let ExprKind::Index(..) = &expr.kind { if let ExprKind::Index(..) = &expr.kind {
if is_inside_always_const_context(cx.tcx, expr.hir_id) {
return;
}
let snippet = let snippet =
if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) { if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
format!("assert!({}.len() > {});", &arr, &func) format!("assert!({}.len() > {});", &arr, &func)

View File

@ -57,7 +57,6 @@ pub(crate) fn check<'tcx>(
Applicability::HasPlaceholders, // snippet Applicability::HasPlaceholders, // snippet
); );
} }
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
}); });
} }
} }

View File

@ -242,6 +242,13 @@ declare_clippy_lint! {
/// # let x = 1; /// # let x = 1;
/// if (x | 1 > 3) { } /// if (x | 1 > 3) { }
/// ``` /// ```
///
/// Use instead:
///
/// ```no_run
/// # let x = 1;
/// if (x >= 2) { }
/// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub INEFFECTIVE_BIT_MASK, pub INEFFECTIVE_BIT_MASK,
correctness, correctness,
@ -265,6 +272,13 @@ declare_clippy_lint! {
/// # let x = 1; /// # let x = 1;
/// if x & 0b1111 == 0 { } /// if x & 0b1111 == 0 { }
/// ``` /// ```
///
/// Use instead:
///
/// ```no_run
/// # let x: i32 = 1;
/// if x.trailing_zeros() > 4 { }
/// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub VERBOSE_BIT_MASK, pub VERBOSE_BIT_MASK,
pedantic, pedantic,
@ -560,74 +574,128 @@ declare_clippy_lint! {
/// implement equality for a type involving floats). /// implement equality for a type involving floats).
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Floating point calculations are usually imprecise, so /// Floating point calculations are usually imprecise, so asking if two values are *exactly*
/// asking if two values are *exactly* equal is asking for trouble. For a good /// equal is asking for trouble because arriving at the same logical result via different
/// guide on what to do, see [the floating point /// routes (e.g. calculation versus constant) may yield different values.
/// guide](http://www.floating-point-gui.de/errors/comparison).
/// ///
/// ### Example /// ### Example
/// ```no_run
/// let x = 1.2331f64;
/// let y = 1.2332f64;
/// ///
/// if y == 1.23f64 { } /// ```no_run
/// if y != x {} // where both are floats /// let a: f64 = 1000.1;
/// let b: f64 = 0.2;
/// let x = a + b;
/// let y = 1000.3; // Expected value.
///
/// // Actual value: 1000.3000000000001
/// println!("{x}");
///
/// let are_equal = x == y;
/// println!("{are_equal}"); // false
/// ``` /// ```
/// ///
/// Use instead: /// The correct way to compare floating point numbers is to define an allowed error margin. This
/// may be challenging if there is no "natural" error margin to permit. Broadly speaking, there
/// are two cases:
///
/// 1. If your values are in a known range and you can define a threshold for "close enough to
/// be equal", it may be appropriate to define an absolute error margin. For example, if your
/// data is "length of vehicle in centimeters", you may consider 0.1 cm to be "close enough".
/// 1. If your code is more general and you do not know the range of values, you should use a
/// relative error margin, accepting e.g. 0.1% of error regardless of specific values.
///
/// For the scenario where you can define a meaningful absolute error margin, consider using:
///
/// ```no_run /// ```no_run
/// # let x = 1.2331f64; /// let a: f64 = 1000.1;
/// # let y = 1.2332f64; /// let b: f64 = 0.2;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison /// let x = a + b;
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. /// let y = 1000.3; // Expected value.
/// // let error_margin = std::f64::EPSILON; ///
/// if (y - 1.23f64).abs() < error_margin { } /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1;
/// if (y - x).abs() > error_margin { } /// let within_tolerance = (x - y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM;
/// println!("{within_tolerance}"); // true
/// ``` /// ```
///
/// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
/// a different use of the term that is not suitable for floating point equality comparison.
/// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`.
///
/// For the scenario where no meaningful absolute error can be defined, refer to
/// [the floating point guide](https://www.floating-point-gui.de/errors/comparison)
/// for a reference implementation of relative error based comparison of floating point values.
/// `MIN_NORMAL` in the reference implementation is equivalent to `MIN_POSITIVE` in Rust.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP, pub FLOAT_CMP,
pedantic, pedantic,
"using `==` or `!=` on float values instead of comparing difference with an epsilon" "using `==` or `!=` on float values instead of comparing difference with an allowed error"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for (in-)equality comparisons on floating-point /// Checks for (in-)equality comparisons on constant floating-point
/// value and constant, except in functions called `*eq*` (which probably /// values (apart from zero), except in functions called `*eq*` (which probably
/// implement equality for a type involving floats). /// implement equality for a type involving floats).
/// ///
/// ### Why restrict this? /// ### Why restrict this?
/// Floating point calculations are usually imprecise, so /// Floating point calculations are usually imprecise, so asking if two values are *exactly*
/// asking if two values are *exactly* equal is asking for trouble. For a good /// equal is asking for trouble because arriving at the same logical result via different
/// guide on what to do, see [the floating point /// routes (e.g. calculation versus constant) may yield different values.
/// guide](http://www.floating-point-gui.de/errors/comparison).
/// ///
/// ### Example /// ### Example
/// ```no_run
/// let x: f64 = 1.0;
/// const ONE: f64 = 1.00;
/// ///
/// if x == ONE { } // where both are floats /// ```no_run
/// let a: f64 = 1000.1;
/// let b: f64 = 0.2;
/// let x = a + b;
/// const Y: f64 = 1000.3; // Expected value.
///
/// // Actual value: 1000.3000000000001
/// println!("{x}");
///
/// let are_equal = x == Y;
/// println!("{are_equal}"); // false
/// ``` /// ```
/// ///
/// Use instead: /// The correct way to compare floating point numbers is to define an allowed error margin. This
/// may be challenging if there is no "natural" error margin to permit. Broadly speaking, there
/// are two cases:
///
/// 1. If your values are in a known range and you can define a threshold for "close enough to
/// be equal", it may be appropriate to define an absolute error margin. For example, if your
/// data is "length of vehicle in centimeters", you may consider 0.1 cm to be "close enough".
/// 1. If your code is more general and you do not know the range of values, you should use a
/// relative error margin, accepting e.g. 0.1% of error regardless of specific values.
///
/// For the scenario where you can define a meaningful absolute error margin, consider using:
///
/// ```no_run /// ```no_run
/// # let x: f64 = 1.0; /// let a: f64 = 1000.1;
/// # const ONE: f64 = 1.00; /// let b: f64 = 0.2;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison /// let x = a + b;
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. /// const Y: f64 = 1000.3; // Expected value.
/// // let error_margin = std::f64::EPSILON; ///
/// if (x - ONE).abs() < error_margin { } /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1;
/// let within_tolerance = (x - Y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM;
/// println!("{within_tolerance}"); // true
/// ``` /// ```
///
/// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
/// a different use of the term that is not suitable for floating point equality comparison.
/// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`.
///
/// For the scenario where no meaningful absolute error can be defined, refer to
/// [the floating point guide](https://www.floating-point-gui.de/errors/comparison)
/// for a reference implementation of relative error based comparison of floating point values.
/// `MIN_NORMAL` in the reference implementation is equivalent to `MIN_POSITIVE` in Rust.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP_CONST, pub FLOAT_CMP_CONST,
restriction, restriction,
"using `==` or `!=` on float constants instead of comparing difference with an epsilon" "using `==` or `!=` on float constants instead of comparing difference with an allowed error"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for getting the remainder of a division by one or minus /// Checks for getting the remainder of integer division by one or minus
/// one. /// one.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
@ -646,7 +714,7 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub MODULO_ONE, pub MODULO_ONE,
correctness, correctness,
"taking a number modulo +/-1, which can either panic/overflow or always returns 0" "taking an integer modulo +/-1, which can either panic/overflow or always returns 0"
} }
declare_clippy_lint! { declare_clippy_lint! {

View File

@ -1,70 +0,0 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::SpanlessEq;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Detects classic underflow/overflow checks.
///
/// ### Why is this bad?
/// Most classic C underflow/overflow checks will fail in
/// Rust. Users can use functions like `overflowing_*` and `wrapping_*` instead.
///
/// ### Example
/// ```no_run
/// # let a = 1;
/// # let b = 2;
/// a + b < a;
/// ```
#[clippy::version = "pre 1.29.0"]
pub OVERFLOW_CHECK_CONDITIONAL,
complexity,
"overflow checks inspired by C which are likely to panic"
}
declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]);
const OVERFLOW_MSG: &str = "you are trying to use classic C overflow conditions that will fail in Rust";
const UNDERFLOW_MSG: &str = "you are trying to use classic C underflow conditions that will fail in Rust";
impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional {
// a + b < a, a > a + b, a < a - b, a - b > a
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let eq = |l, r| SpanlessEq::new(cx).eq_path_segment(l, r);
if let ExprKind::Binary(ref op, first, second) = expr.kind
&& let ExprKind::Binary(ref op2, ident1, ident2) = first.kind
&& let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind
&& let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind
&& let ExprKind::Path(QPath::Resolved(_, path3)) = second.kind
&& (eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]))
&& cx.typeck_results().expr_ty(ident1).is_integral()
&& cx.typeck_results().expr_ty(ident2).is_integral()
{
if op.node == BinOpKind::Lt && op2.node == BinOpKind::Add {
span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG);
}
if op.node == BinOpKind::Gt && op2.node == BinOpKind::Sub {
span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG);
}
}
if let ExprKind::Binary(ref op, first, second) = expr.kind
&& let ExprKind::Binary(ref op2, ident1, ident2) = second.kind
&& let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind
&& let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind
&& let ExprKind::Path(QPath::Resolved(_, path3)) = first.kind
&& (eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]))
&& cx.typeck_results().expr_ty(ident1).is_integral()
&& cx.typeck_results().expr_ty(ident2).is_integral()
{
if op.node == BinOpKind::Gt && op2.node == BinOpKind::Add {
span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG);
}
if op.node == BinOpKind::Lt && op2.node == BinOpKind::Sub {
span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG);
}
}
}
}

View File

@ -0,0 +1,86 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::eq_expr_value;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Detects C-style underflow/overflow checks.
///
/// ### Why is this bad?
/// These checks will, by default, panic in debug builds rather than check
/// whether the operation caused an overflow.
///
/// ### Example
/// ```no_run
/// # let a = 1i32;
/// # let b = 2i32;
/// if a + b < a {
/// // handle overflow
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// # let a = 1i32;
/// # let b = 2i32;
/// if a.checked_add(b).is_none() {
/// // handle overflow
/// }
/// ```
///
/// Or:
/// ```no_run
/// # let a = 1i32;
/// # let b = 2i32;
/// if a.overflowing_add(b).1 {
/// // handle overflow
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub PANICKING_OVERFLOW_CHECKS,
correctness,
"overflow checks which will panic in debug mode"
}
declare_lint_pass!(PanickingOverflowChecks => [PANICKING_OVERFLOW_CHECKS]);
impl<'tcx> LateLintPass<'tcx> for PanickingOverflowChecks {
// a + b < a, a > a + b, a < a - b, a - b > a
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(op, lhs, rhs) = expr.kind
&& let (lt, gt) = match op.node {
BinOpKind::Lt => (lhs, rhs),
BinOpKind::Gt => (rhs, lhs),
_ => return,
}
&& let ctxt = expr.span.ctxt()
&& let (op_lhs, op_rhs, other, commutative) = match (&lt.kind, &gt.kind) {
(&ExprKind::Binary(op, lhs, rhs), _) if op.node == BinOpKind::Add && ctxt == lt.span.ctxt() => {
(lhs, rhs, gt, true)
},
(_, &ExprKind::Binary(op, lhs, rhs)) if op.node == BinOpKind::Sub && ctxt == gt.span.ctxt() => {
(lhs, rhs, lt, false)
},
_ => return,
}
&& let typeck = cx.typeck_results()
&& let ty = typeck.expr_ty(op_lhs)
&& matches!(ty.kind(), ty::Uint(_))
&& ty == typeck.expr_ty(op_rhs)
&& ty == typeck.expr_ty(other)
&& !in_external_macro(cx.tcx.sess, expr.span)
&& (eq_expr_value(cx, op_lhs, other) || (commutative && eq_expr_value(cx, op_rhs, other)))
{
span_lint(
cx,
PANICKING_OVERFLOW_CHECKS,
expr.span,
"you are trying to use classic C overflow conditions that will fail in Rust",
);
}
}
}

View File

@ -26,12 +26,14 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"), ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"),
("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"), ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"),
("clippy::option_unwrap_used", "clippy::unwrap_used"), ("clippy::option_unwrap_used", "clippy::unwrap_used"),
("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"),
("clippy::ref_in_deref", "clippy::needless_borrow"), ("clippy::ref_in_deref", "clippy::needless_borrow"),
("clippy::result_expect_used", "clippy::expect_used"), ("clippy::result_expect_used", "clippy::expect_used"),
("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"), ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
("clippy::result_unwrap_used", "clippy::unwrap_used"), ("clippy::result_unwrap_used", "clippy::unwrap_used"),
("clippy::single_char_push_str", "clippy::single_char_add_str"), ("clippy::single_char_push_str", "clippy::single_char_add_str"),
("clippy::stutter", "clippy::module_name_repetitions"), ("clippy::stutter", "clippy::module_name_repetitions"),
("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"),
("clippy::to_string_in_display", "clippy::recursive_format_impl"), ("clippy::to_string_in_display", "clippy::recursive_format_impl"),
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
("clippy::zero_width_space", "clippy::invisible_characters"), ("clippy::zero_width_space", "clippy::invisible_characters"),

View File

@ -7,6 +7,7 @@ use clippy_utils::{
path_to_local_id, span_contains_cfg, span_find_starting_semi, path_to_local_id, span_contains_cfg, span_find_starting_semi,
}; };
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_ast::NestedMetaItem;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::LangItem::ResultErr; use rustc_hir::LangItem::ResultErr;
@ -14,13 +15,13 @@ use rustc_hir::{
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt, Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
StmtKind, StmtKind,
}; };
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::{BytePos, Pos, Span}; use rustc_span::{sym, BytePos, Pos, Span};
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Display; use std::fmt::Display;
@ -80,6 +81,9 @@ declare_clippy_lint! {
/// ``` /// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub NEEDLESS_RETURN, pub NEEDLESS_RETURN,
// This lint requires some special handling in `check_final_expr` for `#[expect]`.
// This handling needs to be updated if the group gets changed. This should also
// be caught by tests.
style, style,
"using a return statement like `return expr;` where an expression would suffice" "using a return statement like `return expr;` where an expression would suffice"
} }
@ -91,6 +95,9 @@ declare_clippy_lint! {
/// ### Why is this bad? /// ### Why is this bad?
/// The `return` is unnecessary. /// The `return` is unnecessary.
/// ///
/// Returns may be used to add attributes to the return expression. Return
/// statements with attributes are therefore be accepted by this lint.
///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> { /// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
@ -377,13 +384,39 @@ fn check_final_expr<'tcx>(
} }
}; };
if !cx.tcx.hir().attrs(expr.hir_id).is_empty() {
return;
}
let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
if borrows { if borrows {
return; return;
} }
if ret_span.from_expansion() {
return;
}
// Returns may be used to turn an expression into a statement in rustc's AST.
// This allows the addition of attributes, like `#[allow]` (See: clippy#9361)
// `#[expect(clippy::needless_return)]` needs to be handled separatly to
// actually fullfil the expectation (clippy::#12998)
match cx.tcx.hir().attrs(expr.hir_id) {
[] => {},
[attr] => {
if matches!(Level::from_attr(attr), Some(Level::Expect(_)))
&& let metas = attr.meta_item_list()
&& let Some(lst) = metas
&& let [NestedMetaItem::MetaItem(meta_item)] = lst.as_slice()
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
&& tool.ident.name == sym::clippy
&& matches!(
lint_name.ident.name.as_str(),
"needless_return" | "style" | "all" | "warnings"
)
{
// This is an expectation of the `needless_return` lint
} else {
return;
}
},
_ => return,
}
emit_return_lint(cx, ret_span, semi_spans, &replacement, expr.hir_id); emit_return_lint(cx, ret_span, semi_spans, &replacement, expr.hir_id);
}, },
@ -415,10 +448,6 @@ fn emit_return_lint(
replacement: &RetReplacement<'_>, replacement: &RetReplacement<'_>,
at: HirId, at: HirId,
) { ) {
if ret_span.from_expansion() {
return;
}
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
NEEDLESS_RETURN, NEEDLESS_RETURN,

View File

@ -12,7 +12,7 @@ use std::collections::{BTreeMap, BTreeSet};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// It lints if a struct has two methods with the same name: /// It lints if a struct has two methods with the same name:
/// one from a trait, another not from trait. /// one from a trait, another not from a trait.
/// ///
/// ### Why restrict this? /// ### Why restrict this?
/// Confusing. /// Confusing.

View File

@ -0,0 +1,125 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{higher, peel_hir_expr_while, SpanlessEq};
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::Symbol;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `contains` to see if a value is not
/// present on `HashSet` followed by a `insert`.
///
/// ### Why is this bad?
/// Using just `insert` and checking the returned `bool` is more efficient.
///
/// ### Known problems
/// In case the value that wants to be inserted is borrowed and also expensive or impossible
/// to clone. In such a scenario, the developer might want to check with `contains` before inserting,
/// to avoid the clone. In this case, it will report a false positive.
///
/// ### Example
/// ```rust
/// use std::collections::HashSet;
/// let mut set = HashSet::new();
/// let value = 5;
/// if !set.contains(&value) {
/// set.insert(value);
/// println!("inserted {value:?}");
/// }
/// ```
/// Use instead:
/// ```rust
/// use std::collections::HashSet;
/// let mut set = HashSet::new();
/// let value = 5;
/// if set.insert(&value) {
/// println!("inserted {value:?}");
/// }
/// ```
#[clippy::version = "1.80.0"]
pub SET_CONTAINS_OR_INSERT,
nursery,
"call to `HashSet::contains` followed by `HashSet::insert`"
}
declare_lint_pass!(HashsetInsertAfterContains => [SET_CONTAINS_OR_INSERT]);
impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !expr.span.from_expansion()
&& let Some(higher::If {
cond: cond_expr,
then: then_expr,
..
}) = higher::If::hir(expr)
&& let Some(contains_expr) = try_parse_op_call(cx, cond_expr, sym!(contains))//try_parse_contains(cx, cond_expr)
&& let Some(insert_expr) = find_insert_calls(cx, &contains_expr, then_expr)
{
span_lint(
cx,
SET_CONTAINS_OR_INSERT,
vec![contains_expr.span, insert_expr.span],
"usage of `HashSet::insert` after `HashSet::contains`",
);
}
}
}
struct OpExpr<'tcx> {
receiver: &'tcx Expr<'tcx>,
value: &'tcx Expr<'tcx>,
span: Span,
}
fn try_parse_op_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, symbol: Symbol) -> Option<OpExpr<'tcx>> {
let expr = peel_hir_expr_while(expr, |e| {
if let ExprKind::Unary(UnOp::Not, e) = e.kind {
Some(e)
} else {
None
}
});
if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind {
let value = value.peel_borrows();
let value = peel_hir_expr_while(value, |e| {
if let ExprKind::Unary(UnOp::Deref, e) = e.kind {
Some(e)
} else {
None
}
});
let receiver = receiver.peel_borrows();
let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
if value.span.eq_ctxt(expr.span)
&& is_type_diagnostic_item(cx, receiver_ty, sym::HashSet)
&& path.ident.name == symbol
{
return Some(OpExpr { receiver, value, span });
}
}
None
}
fn find_insert_calls<'tcx>(
cx: &LateContext<'tcx>,
contains_expr: &OpExpr<'tcx>,
expr: &'tcx Expr<'_>,
) -> Option<OpExpr<'tcx>> {
for_each_expr(cx, expr, |e| {
if let Some(insert_expr) = try_parse_op_call(cx, e, sym!(insert))
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
{
ControlFlow::Break(insert_expr)
} else {
ControlFlow::Continue(())
}
})
}

View File

@ -1,6 +1,5 @@
pub mod almost_standard_lint_formulation; pub mod almost_standard_lint_formulation;
pub mod collapsible_calls; pub mod collapsible_calls;
pub mod compiler_lint_functions;
pub mod interning_defined_symbol; pub mod interning_defined_symbol;
pub mod invalid_paths; pub mod invalid_paths;
pub mod lint_without_lint_pass; pub mod lint_without_lint_pass;

View File

@ -1,73 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::match_type;
use clippy_utils::{is_lint_allowed, paths};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `cx.span_lint*` and suggests to use the `utils::*`
/// variant of the function.
///
/// ### Why is this bad?
/// The `utils::*` variants also add a link to the Clippy documentation to the
/// warning/error messages.
///
/// ### Example
/// ```rust,ignore
/// cx.span_lint(LINT_NAME, "message");
/// ```
///
/// Use instead:
/// ```rust,ignore
/// utils::span_lint(cx, LINT_NAME, "message");
/// ```
pub COMPILER_LINT_FUNCTIONS,
internal,
"usage of the lint functions of the compiler instead of the utils::* variant"
}
impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]);
#[derive(Clone, Default)]
pub struct CompilerLintFunctions {
map: FxHashMap<&'static str, &'static str>,
}
impl CompilerLintFunctions {
#[must_use]
pub fn new() -> Self {
let mut map = FxHashMap::default();
map.insert("span_lint", "utils::span_lint");
map.insert("lint", "utils::span_lint");
map.insert("span_lint_note", "utils::span_lint_and_note");
map.insert("span_lint_help", "utils::span_lint_and_help");
Self { map }
}
}
impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if is_lint_allowed(cx, COMPILER_LINT_FUNCTIONS, expr.hir_id) {
return;
}
if let ExprKind::MethodCall(path, self_arg, _, _) = &expr.kind
&& let fn_name = path.ident
&& let Some(sugg) = self.map.get(fn_name.as_str())
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& (match_type(cx, ty, &paths::EARLY_CONTEXT) || match_type(cx, ty, &paths::LATE_CONTEXT))
{
span_lint_and_help(
cx,
COMPILER_LINT_FUNCTIONS,
path.ident.span,
"usage of a compiler lint function",
None,
format!("please use the Clippy variant of this function: `{sugg}`"),
);
}
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_test_module_or_function; use clippy_utils::is_in_test;
use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::source::{snippet, snippet_with_applicability};
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -100,7 +100,6 @@ declare_clippy_lint! {
#[derive(Default)] #[derive(Default)]
pub struct WildcardImports { pub struct WildcardImports {
warn_on_all: bool, warn_on_all: bool,
test_modules_deep: u32,
allowed_segments: FxHashSet<String>, allowed_segments: FxHashSet<String>,
} }
@ -108,7 +107,6 @@ impl WildcardImports {
pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self { pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
Self { Self {
warn_on_all, warn_on_all,
test_modules_deep: 0,
allowed_segments: allowed_wildcard_imports, allowed_segments: allowed_wildcard_imports,
} }
} }
@ -122,15 +120,12 @@ impl LateLintPass<'_> for WildcardImports {
return; return;
} }
if is_test_module_or_function(cx.tcx, item) {
self.test_modules_deep = self.test_modules_deep.saturating_add(1);
}
let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id); let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id);
if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) { if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) {
return; return;
} }
if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind
&& (self.warn_on_all || !self.check_exceptions(item, use_path.segments)) && (self.warn_on_all || !self.check_exceptions(cx, item, use_path.segments))
&& let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id) && let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id)
&& !used_imports.is_empty() // Already handled by `unused_imports` && !used_imports.is_empty() // Already handled by `unused_imports`
&& !used_imports.contains(&kw::Underscore) && !used_imports.contains(&kw::Underscore)
@ -180,20 +175,14 @@ impl LateLintPass<'_> for WildcardImports {
span_lint_and_sugg(cx, lint, span, message, "try", sugg, applicability); span_lint_and_sugg(cx, lint, span, message, "try", sugg, applicability);
} }
} }
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if is_test_module_or_function(cx.tcx, item) {
self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
}
}
} }
impl WildcardImports { impl WildcardImports {
fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
item.span.from_expansion() item.span.from_expansion()
|| is_prelude_import(segments) || is_prelude_import(segments)
|| (is_super_only_import(segments) && self.test_modules_deep > 0)
|| is_allowed_via_config(segments, &self.allowed_segments) || is_allowed_via_config(segments, &self.allowed_segments)
|| (is_super_only_import(segments) && is_in_test(cx.tcx, item.hir_id()))
} }
} }

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::is_in_test;
use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall}; use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall};
use clippy_utils::source::{expand_past_previous_comma, snippet_opt}; use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_ast::token::LitKind; use rustc_ast::token::LitKind;
use rustc_ast::{ use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
@ -297,8 +297,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
.as_ref() .as_ref()
.map_or(false, |crate_name| crate_name == "build_script_build"); .map_or(false, |crate_name| crate_name == "build_script_build");
let allowed_in_tests = self.allow_print_in_tests let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id);
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id));
match diag_name { match diag_name {
sym::print_macro | sym::println_macro if !allowed_in_tests => { sym::print_macro | sym::println_macro if !allowed_in_tests => {
if !is_build_script { if !is_build_script {

View File

@ -102,10 +102,11 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, Destination, Expr, self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstContext,
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef,
TyKind, UnOp,
}; };
use rustc_lexer::{tokenize, TokenKind}; use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_lint::{LateContext, Level, Lint, LintContext};
@ -210,7 +211,10 @@ pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
false false
} }
/// Returns `true` if the given `NodeId` is inside a constant context /// Returns `true` if the given `HirId` is inside a constant context.
///
/// This is the same as `is_inside_always_const_context`, but also includes
/// `const fn`.
/// ///
/// # Example /// # Example
/// ///
@ -223,6 +227,24 @@ pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx.hir().is_inside_const_context(id) cx.tcx.hir().is_inside_const_context(id)
} }
/// Returns `true` if the given `HirId` is inside an always constant context.
///
/// This context includes:
/// * const/static items
/// * const blocks (or inline consts)
/// * associated constants
pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
use ConstContext::{Const, ConstFn, Static};
let hir = tcx.hir();
let Some(ctx) = hir.body_const_context(hir.enclosing_body_owner(hir_id)) else {
return false;
};
match ctx {
ConstFn => false,
Static(_) | Const { inline: _ } => true,
}
}
/// Checks if a `Res` refers to a constructor of a `LangItem` /// Checks if a `Res` refers to a constructor of a `LangItem`
/// For example, use this to check whether a function call or a pattern is `Some(..)`. /// For example, use this to check whether a function call or a pattern is `Some(..)`.
pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool { pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool {
@ -1904,8 +1926,18 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool
false false
} }
pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { /// Checks if the given HIR node is inside an `impl` block with the `automatically_derived`
any_parent_has_attr(tcx, node, sym::automatically_derived) /// attribute.
pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir()
.parent_owner_iter(id)
.filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
.any(|(id, _)| {
has_attr(
tcx.hir().attrs(tcx.local_def_id_to_hir_id(id.def_id)),
sym::automatically_derived,
)
})
} }
/// Matches a function call with the given path and returns the arguments. /// Matches a function call with the given path and returns the arguments.
@ -2472,6 +2504,17 @@ pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize)
} }
} }
/// Peels off all references on the type. Returns the underlying type and the number of references
/// removed.
pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) {
let mut count = 0;
while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() {
ty = *dest_ty;
count += 1;
}
(ty, count)
}
/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
@ -2594,16 +2637,6 @@ pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
.any(|attr| attr.has_name(sym::cfg)) .any(|attr| attr.has_name(sym::cfg))
} }
/// Checks whether item either has `test` attribute applied, or
/// is a module with `test` in its name.
///
/// Note: Add `//@compile-flags: --test` to UI tests with a `#[test]` function
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
is_in_test_function(tcx, item.hir_id())
|| matches!(item.kind, ItemKind::Mod(..))
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
}
/// Walks up the HIR tree from the given expression in an attempt to find where the value is /// Walks up the HIR tree from the given expression in an attempt to find where the value is
/// consumed. /// consumed.
/// ///

View File

@ -88,6 +88,7 @@ pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"
pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"]; pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"]; pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]; pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
pub const STRING_FROM_UTF8: [&str; 4] = ["alloc", "string", "String", "from_utf8"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"]; pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates

View File

@ -96,11 +96,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'
return false; return false;
} }
for (predicate, _span) in cx for (predicate, _span) in cx.tcx.explicit_item_super_predicates(def_id).iter_identity_copied() {
.tcx
.explicit_item_super_predicates(def_id)
.iter_identity_copied()
{
match predicate.kind().skip_binder() { match predicate.kind().skip_binder() {
// For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through // For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through
// and check substitutions to find `U`. // and check substitutions to find `U`.
@ -1332,19 +1328,13 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`. /// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> { pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> {
if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) { if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) {
cx.tcx cx.tcx.inherent_impls(ty_did).into_iter().flatten().find_map(|&did| {
.inherent_impls(ty_did) cx.tcx
.into_iter() .associated_items(did)
.flatten() .filter_by_name_unhygienic(method_name)
.map(|&did| { .next()
cx.tcx .filter(|item| item.kind == AssocKind::Fn)
.associated_items(did) })
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
.next()
.flatten()
} else { } else {
None None
} }

View File

@ -16,6 +16,7 @@ clap = { version = "4.4", features = ["derive", "env"] }
crossbeam-channel = "0.5.6" crossbeam-channel = "0.5.6"
diff = "0.1.13" diff = "0.1.13"
flate2 = "1.0" flate2 = "1.0"
itertools = "0.12"
rayon = "1.5.1" rayon = "1.5.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"

View File

@ -7,13 +7,13 @@ repo. We can then check the diff and spot new or disappearing warnings.
From the repo root, run: From the repo root, run:
``` ```
cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml cargo lintcheck
``` ```
or or
``` ```
cargo lintcheck cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml
``` ```
By default, the logs will be saved into By default, the logs will be saved into
@ -33,6 +33,8 @@ the 200 recently most downloaded crates:
cargo lintcheck popular -n 200 custom.toml cargo lintcheck popular -n 200 custom.toml
``` ```
> Note: Lintcheck isn't sandboxed. Only use it to check crates that you trust or
> sandbox it manually.
### Configuring the Crate Sources ### Configuring the Crate Sources
@ -65,17 +67,11 @@ sources.
#### Command Line Options (optional) #### Command Line Options (optional)
```toml ```toml
bitflags = {name = "bitflags", versions = ['1.2.1'], options = ['-Wclippy::pedantic', '-Wclippy::cargo']} clap = {name = "clap", versions = ['4.5.8'], options = ['-Fderive']}
``` ```
It is possible to specify command line options for each crate. This makes it It is possible to specify command line options for each crate. This makes it
possible to only check a crate for certain lint groups. If no options are possible to enable or disable features.
specified, the lint groups `clippy::all`, `clippy::pedantic`, and
`clippy::cargo` are checked. If an empty array is specified only `clippy::all`
is checked.
**Note:** `-Wclippy::all` is always enabled by default, unless `-Aclippy::all`
is explicitly specified in the options.
### Fix mode ### Fix mode
You can run `cargo lintcheck --fix` which will run Clippy with `--fix` and You can run `cargo lintcheck --fix` which will run Clippy with `--fix` and

View File

@ -1,38 +1,38 @@
[crates] [crates]
# some of these are from cargotest # some of these are from cargotest
cargo = {name = "cargo", versions = ['0.64.0']} cargo = {name = "cargo", version = '0.64.0'}
iron = {name = "iron", versions = ['0.6.1']} iron = {name = "iron", version = '0.6.1'}
ripgrep = {name = "ripgrep", versions = ['12.1.1']} ripgrep = {name = "ripgrep", version = '12.1.1'}
xsv = {name = "xsv", versions = ['0.13.0']} xsv = {name = "xsv", version = '0.13.0'}
# commented out because of 173K clippy::match_same_arms msgs in language_type.rs # commented out because of 173K clippy::match_same_arms msgs in language_type.rs
#tokei = { name = "tokei", versions = ['12.0.4']} #tokei = { name = "tokei", version = '12.0.4'}
rayon = {name = "rayon", versions = ['1.5.0']} rayon = {name = "rayon", version = '1.5.0'}
serde = {name = "serde", versions = ['1.0.118']} serde = {name = "serde", version = '1.0.118'}
# top 10 crates.io dls # top 10 crates.io dls
bitflags = {name = "bitflags", versions = ['1.2.1']} bitflags = {name = "bitflags", version = '1.2.1'}
# crash = {name = "clippy_crash", path = "/tmp/clippy_crash"} # crash = {name = "clippy_crash", path = "/tmp/clippy_crash"}
libc = {name = "libc", versions = ['0.2.81']} libc = {name = "libc", version = '0.2.81'}
log = {name = "log", versions = ['0.4.11']} log = {name = "log", version = '0.4.11'}
proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']} proc-macro2 = {name = "proc-macro2", version = '1.0.24'}
quote = {name = "quote", versions = ['1.0.7']} quote = {name = "quote", version = '1.0.7'}
rand = {name = "rand", versions = ['0.7.3']} rand = {name = "rand", version = '0.7.3'}
rand_core = {name = "rand_core", versions = ['0.6.0']} rand_core = {name = "rand_core", version = '0.6.0'}
regex = {name = "regex", versions = ['1.3.2']} regex = {name = "regex", version = '1.3.2'}
syn = {name = "syn", versions = ['1.0.54']} syn = {name = "syn", version = '1.0.54'}
unicode-xid = {name = "unicode-xid", versions = ['0.2.1']} unicode-xid = {name = "unicode-xid", version = '0.2.1'}
# some more of dtolnays crates # some more of dtolnays crates
anyhow = {name = "anyhow", versions = ['1.0.38']} anyhow = {name = "anyhow", version = '1.0.38'}
async-trait = {name = "async-trait", versions = ['0.1.42']} async-trait = {name = "async-trait", version = '0.1.42'}
cxx = {name = "cxx", versions = ['1.0.32']} cxx = {name = "cxx", version = '1.0.32'}
ryu = {name = "ryu", versions = ['1.0.5']} ryu = {name = "ryu", version = '1.0.5'}
serde_yaml = {name = "serde_yaml", versions = ['0.8.17']} serde_yaml = {name = "serde_yaml", version = '0.8.17'}
thiserror = {name = "thiserror", versions = ['1.0.24']} thiserror = {name = "thiserror", version = '1.0.24'}
# some embark crates, there are other interesting crates but # some embark crates, there are other interesting crates but
# unfortunately adding them increases lintcheck runtime drastically # unfortunately adding them increases lintcheck runtime drastically
cfg-expr = {name = "cfg-expr", versions = ['0.7.1']} cfg-expr = {name = "cfg-expr", version = '0.7.1'}
puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"} puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
rpmalloc = {name = "rpmalloc", versions = ['0.2.0']} rpmalloc = {name = "rpmalloc", version = '0.2.0'}
tame-oidc = {name = "tame-oidc", versions = ['0.1.0']} tame-oidc = {name = "tame-oidc", version = '0.1.0'}
[recursive] [recursive]
ignore = [ ignore = [

View File

@ -36,6 +36,10 @@ pub(crate) struct LintcheckConfig {
/// Apply a filter to only collect specified lints, this also overrides `allow` attributes /// Apply a filter to only collect specified lints, this also overrides `allow` attributes
#[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)] #[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)]
pub lint_filter: Vec<String>, pub lint_filter: Vec<String>,
/// Set all lints to the "warn" lint level, even resitriction ones. Usually,
/// it's better to use `--filter` instead
#[clap(long, conflicts_with("lint_filter"))]
pub warn_all: bool,
/// Set the output format of the log file /// Set the output format of the log file
#[clap(long, short, default_value = "text")] #[clap(long, short, default_value = "text")]
pub format: OutputFormat, pub format: OutputFormat,

View File

@ -11,8 +11,6 @@ use std::{env, mem};
fn run_clippy(addr: &str) -> Option<i32> { fn run_clippy(addr: &str) -> Option<i32> {
let driver_info = DriverInfo { let driver_info = DriverInfo {
package_name: env::var("CARGO_PKG_NAME").ok()?, package_name: env::var("CARGO_PKG_NAME").ok()?,
crate_name: env::var("CARGO_CRATE_NAME").ok()?,
version: env::var("CARGO_PKG_VERSION").ok()?,
}; };
let mut stream = BufReader::new(TcpStream::connect(addr).unwrap()); let mut stream = BufReader::new(TcpStream::connect(addr).unwrap());

View File

@ -0,0 +1,288 @@
use std::collections::{HashMap, HashSet};
use std::fs::{self};
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use serde::Deserialize;
use walkdir::{DirEntry, WalkDir};
use crate::{Crate, LINTCHECK_DOWNLOADS, LINTCHECK_SOURCES};
/// List of sources to check, loaded from a .toml file
#[derive(Debug, Deserialize)]
pub struct SourceList {
crates: HashMap<String, TomlCrate>,
#[serde(default)]
recursive: RecursiveOptions,
}
#[derive(Debug, Deserialize, Default)]
pub struct RecursiveOptions {
pub ignore: HashSet<String>,
}
/// A crate source stored inside the .toml
/// will be translated into on one of the `CrateSource` variants
#[derive(Debug, Deserialize)]
struct TomlCrate {
name: String,
version: Option<String>,
git_url: Option<String>,
git_hash: Option<String>,
path: Option<String>,
options: Option<Vec<String>>,
}
/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub enum CrateSource {
CratesIo {
name: String,
version: String,
options: Option<Vec<String>>,
},
Git {
name: String,
url: String,
commit: String,
options: Option<Vec<String>>,
},
Path {
name: String,
path: PathBuf,
options: Option<Vec<String>>,
},
}
/// Read a `lintcheck_crates.toml` file
pub fn read_crates(toml_path: &Path) -> (Vec<CrateSource>, RecursiveOptions) {
let toml_content: String =
fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
let crate_list: SourceList =
toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{e}", toml_path.display()));
// parse the hashmap of the toml file into a list of crates
let tomlcrates: Vec<TomlCrate> = crate_list.crates.into_values().collect();
// flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
// multiple Cratesources)
let mut crate_sources = Vec::new();
for tk in tomlcrates {
if let Some(ref path) = tk.path {
crate_sources.push(CrateSource::Path {
name: tk.name.clone(),
path: PathBuf::from(path),
options: tk.options.clone(),
});
} else if let Some(ref version) = tk.version {
crate_sources.push(CrateSource::CratesIo {
name: tk.name.clone(),
version: version.to_string(),
options: tk.options.clone(),
});
} else if tk.git_url.is_some() && tk.git_hash.is_some() {
// otherwise, we should have a git source
crate_sources.push(CrateSource::Git {
name: tk.name.clone(),
url: tk.git_url.clone().unwrap(),
commit: tk.git_hash.clone().unwrap(),
options: tk.options.clone(),
});
} else {
panic!("Invalid crate source: {tk:?}");
}
// if we have a version as well as a git data OR only one git data, something is funky
if tk.version.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some())
|| tk.git_hash.is_some() != tk.git_url.is_some()
{
eprintln!("tomlkrate: {tk:?}");
assert_eq!(
tk.git_hash.is_some(),
tk.git_url.is_some(),
"Error: Encountered TomlCrate with only one of git_hash and git_url!"
);
assert!(
tk.path.is_none() || (tk.git_hash.is_none() && tk.version.is_none()),
"Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields"
);
unreachable!("Failed to translate TomlCrate into CrateSource!");
}
}
// sort the crates
crate_sources.sort();
(crate_sources, crate_list.recursive)
}
impl CrateSource {
/// Makes the sources available on the disk for clippy to check.
/// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
/// copies a local folder
#[expect(clippy::too_many_lines)]
pub fn download_and_extract(&self) -> Crate {
#[allow(clippy::result_large_err)]
fn get(path: &str) -> Result<ureq::Response, ureq::Error> {
const MAX_RETRIES: u8 = 4;
let mut retries = 0;
loop {
match ureq::get(path).call() {
Ok(res) => return Ok(res),
Err(e) if retries >= MAX_RETRIES => return Err(e),
Err(ureq::Error::Transport(e)) => eprintln!("Error: {e}"),
Err(e) => return Err(e),
}
eprintln!("retrying in {retries} seconds...");
std::thread::sleep(Duration::from_secs(u64::from(retries)));
retries += 1;
}
}
match self {
CrateSource::CratesIo { name, version, options } => {
let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
// url to download the crate from crates.io
let url = format!("https://crates.io/api/v1/crates/{name}/{version}/download");
println!("Downloading and extracting {name} {version} from {url}");
create_dirs(&krate_download_dir, &extract_dir);
let krate_file_path = krate_download_dir.join(format!("{name}-{version}.crate.tar.gz"));
// don't download/extract if we already have done so
if !krate_file_path.is_file() {
// create a file path to download and write the crate data into
let mut krate_dest = fs::File::create(&krate_file_path).unwrap();
let mut krate_req = get(&url).unwrap().into_reader();
// copy the crate into the file
io::copy(&mut krate_req, &mut krate_dest).unwrap();
// unzip the tarball
let ungz_tar = flate2::read::GzDecoder::new(fs::File::open(&krate_file_path).unwrap());
// extract the tar archive
let mut archive = tar::Archive::new(ungz_tar);
archive.unpack(&extract_dir).expect("Failed to extract!");
}
// crate is extracted, return a new Krate object which contains the path to the extracted
// sources that clippy can check
Crate {
version: version.clone(),
name: name.clone(),
path: extract_dir.join(format!("{name}-{version}/")),
options: options.clone(),
}
},
CrateSource::Git {
name,
url,
commit,
options,
} => {
let repo_path = {
let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
// add a -git suffix in case we have the same crate from crates.io and a git repo
repo_path.push(format!("{name}-git"));
repo_path
};
// clone the repo if we have not done so
if !repo_path.is_dir() {
println!("Cloning {url} and checking out {commit}");
if !Command::new("git")
.arg("clone")
.arg(url)
.arg(&repo_path)
.status()
.expect("Failed to clone git repo!")
.success()
{
eprintln!("Failed to clone {url} into {}", repo_path.display());
}
}
// check out the commit/branch/whatever
if !Command::new("git")
.args(["-c", "advice.detachedHead=false"])
.arg("checkout")
.arg(commit)
.current_dir(&repo_path)
.status()
.expect("Failed to check out commit")
.success()
{
eprintln!("Failed to checkout {commit} of repo at {}", repo_path.display());
}
Crate {
version: commit.clone(),
name: name.clone(),
path: repo_path,
options: options.clone(),
}
},
CrateSource::Path { name, path, options } => {
fn is_cache_dir(entry: &DirEntry) -> bool {
fs::read(entry.path().join("CACHEDIR.TAG"))
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
.unwrap_or(false)
}
// copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
// The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
// as a result of this filter.
let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
if dest_crate_root.exists() {
println!("Deleting existing directory at {dest_crate_root:?}");
fs::remove_dir_all(&dest_crate_root).unwrap();
}
println!("Copying {path:?} to {dest_crate_root:?}");
for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
let entry = entry.unwrap();
let entry_path = entry.path();
let relative_entry_path = entry_path.strip_prefix(path).unwrap();
let dest_path = dest_crate_root.join(relative_entry_path);
let metadata = entry_path.symlink_metadata().unwrap();
if metadata.is_dir() {
fs::create_dir(dest_path).unwrap();
} else if metadata.is_file() {
fs::copy(entry_path, dest_path).unwrap();
}
}
Crate {
version: String::from("local"),
name: name.clone(),
path: dest_crate_root,
options: options.clone(),
}
},
}
}
}
/// Create necessary directories to run the lintcheck tool.
///
/// # Panics
///
/// This function panics if creating one of the dirs fails.
fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
fs::create_dir("target/lintcheck/").unwrap_or_else(|err| {
assert_eq!(
err.kind(),
ErrorKind::AlreadyExists,
"cannot create lintcheck target dir"
);
});
fs::create_dir(krate_download_dir).unwrap_or_else(|err| {
assert_eq!(err.kind(), ErrorKind::AlreadyExists, "cannot create crate download dir");
});
fs::create_dir(extract_dir).unwrap_or_else(|err| {
assert_eq!(
err.kind(),
ErrorKind::AlreadyExists,
"cannot create crate extraction dir"
);
});
}

View File

@ -1,37 +1,50 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::fs; use std::fs;
use std::hash::Hash;
use std::path::Path; use std::path::Path;
use itertools::EitherOrBoth;
use serde::{Deserialize, Serialize};
use crate::ClippyWarning; use crate::ClippyWarning;
/// Creates the log file output for [`crate::config::OutputFormat::Json`] #[derive(Deserialize, Serialize)]
pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String { struct LintJson {
serde_json::to_string(&clippy_warnings).unwrap() lint: String,
file_name: String,
byte_pos: (u32, u32),
rendered: String,
} }
fn load_warnings(path: &Path) -> Vec<ClippyWarning> { impl LintJson {
fn key(&self) -> impl Ord + '_ {
(self.file_name.as_str(), self.byte_pos, self.lint.as_str())
}
}
/// Creates the log file output for [`crate::config::OutputFormat::Json`]
pub(crate) fn output(clippy_warnings: Vec<ClippyWarning>) -> String {
let mut lints: Vec<LintJson> = clippy_warnings
.into_iter()
.map(|warning| {
let span = warning.span();
LintJson {
file_name: span.file_name.clone(),
byte_pos: (span.byte_start, span.byte_end),
lint: warning.lint,
rendered: warning.diag.rendered.unwrap(),
}
})
.collect();
lints.sort_by(|a, b| a.key().cmp(&b.key()));
serde_json::to_string(&lints).unwrap()
}
fn load_warnings(path: &Path) -> Vec<LintJson> {
let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display())) serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display()))
} }
/// Group warnings by their primary span location + lint name fn print_warnings(title: &str, warnings: &[LintJson]) {
fn create_map(warnings: &[ClippyWarning]) -> HashMap<impl Eq + Hash + '_, Vec<&ClippyWarning>> {
let mut map = HashMap::<_, Vec<_>>::with_capacity(warnings.len());
for warning in warnings {
let span = warning.span();
let key = (&warning.lint_type, &span.file_name, span.byte_start, span.byte_end);
map.entry(key).or_default().push(warning);
}
map
}
fn print_warnings(title: &str, warnings: &[&ClippyWarning]) {
if warnings.is_empty() { if warnings.is_empty() {
return; return;
} }
@ -39,31 +52,20 @@ fn print_warnings(title: &str, warnings: &[&ClippyWarning]) {
println!("### {title}"); println!("### {title}");
println!("```"); println!("```");
for warning in warnings { for warning in warnings {
print!("{}", warning.diag); print!("{}", warning.rendered);
} }
println!("```"); println!("```");
} }
fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) { fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
fn render(warnings: &[&ClippyWarning]) -> String {
let mut rendered = String::new();
for warning in warnings {
write!(&mut rendered, "{}", warning.diag).unwrap();
}
rendered
}
if changed.is_empty() { if changed.is_empty() {
return; return;
} }
println!("### Changed"); println!("### Changed");
println!("```diff"); println!("```diff");
for &(old, new) in changed { for (old, new) in changed {
let old_rendered = render(old); for change in diff::lines(&old.rendered, &new.rendered) {
let new_rendered = render(new);
for change in diff::lines(&old_rendered, &new_rendered) {
use diff::Result::{Both, Left, Right}; use diff::Result::{Both, Left, Right};
match change { match change {
@ -86,26 +88,19 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) {
let old_warnings = load_warnings(old_path); let old_warnings = load_warnings(old_path);
let new_warnings = load_warnings(new_path); let new_warnings = load_warnings(new_path);
let old_map = create_map(&old_warnings);
let new_map = create_map(&new_warnings);
let mut added = Vec::new(); let mut added = Vec::new();
let mut removed = Vec::new(); let mut removed = Vec::new();
let mut changed = Vec::new(); let mut changed = Vec::new();
for (key, new) in &new_map { for change in itertools::merge_join_by(old_warnings, new_warnings, |old, new| old.key().cmp(&new.key())) {
if let Some(old) = old_map.get(key) { match change {
if old != new { EitherOrBoth::Both(old, new) => {
changed.push((old.as_slice(), new.as_slice())); if old.rendered != new.rendered {
} changed.push((old, new));
} else { }
added.extend(new); },
} EitherOrBoth::Left(old) => removed.push(old),
} EitherOrBoth::Right(new) => added.push(new),
for (key, old) in &old_map {
if !new_map.contains_key(key) {
removed.extend(old);
} }
} }

View File

@ -5,6 +5,7 @@
// When a new lint is introduced, we can search the results for new warnings and check for false // When a new lint is introduced, we can search the results for new warnings and check for false
// positives. // positives.
#![feature(iter_collect_into)]
#![warn( #![warn(
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
@ -12,84 +13,38 @@
unused_lifetimes, unused_lifetimes,
unused_qualifications unused_qualifications
)] )]
#![allow(clippy::collapsible_else_if, clippy::needless_borrows_for_generic_args)] #![allow(
clippy::collapsible_else_if,
clippy::needless_borrows_for_generic_args,
clippy::module_name_repetitions
)]
mod config; mod config;
mod driver; mod driver;
mod input;
mod json; mod json;
mod output;
mod popular_crates; mod popular_crates;
mod recursive; mod recursive;
use crate::config::{Commands, LintcheckConfig, OutputFormat}; use crate::config::{Commands, LintcheckConfig, OutputFormat};
use crate::recursive::LintcheckServer; use crate::recursive::LintcheckServer;
use std::collections::{HashMap, HashSet};
use std::env::consts::EXE_SUFFIX; use std::env::consts::EXE_SUFFIX;
use std::fmt::{self, Display, Write as _}; use std::io::{self};
use std::hash::Hash;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio}; use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration; use std::{env, fs};
use std::{env, fs, thread};
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan};
use cargo_metadata::Message; use cargo_metadata::Message;
use input::{read_crates, CrateSource};
use output::{ClippyCheckOutput, ClippyWarning, RustcIce};
use rayon::prelude::*; use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use walkdir::{DirEntry, WalkDir};
const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
const LINTCHECK_SOURCES: &str = "target/lintcheck/sources"; const LINTCHECK_SOURCES: &str = "target/lintcheck/sources";
/// List of sources to check, loaded from a .toml file
#[derive(Debug, Deserialize)]
struct SourceList {
crates: HashMap<String, TomlCrate>,
#[serde(default)]
recursive: RecursiveOptions,
}
#[derive(Debug, Deserialize, Default)]
struct RecursiveOptions {
ignore: HashSet<String>,
}
/// A crate source stored inside the .toml
/// will be translated into on one of the `CrateSource` variants
#[derive(Debug, Deserialize)]
struct TomlCrate {
name: String,
versions: Option<Vec<String>>,
git_url: Option<String>,
git_hash: Option<String>,
path: Option<String>,
options: Option<Vec<String>>,
}
/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)]
enum CrateSource {
CratesIo {
name: String,
version: String,
options: Option<Vec<String>>,
},
Git {
name: String,
url: String,
commit: String,
options: Option<Vec<String>>,
},
Path {
name: String,
path: PathBuf,
options: Option<Vec<String>>,
},
}
/// Represents the actual source code of a crate that we ran "cargo clippy" on /// Represents the actual source code of a crate that we ran "cargo clippy" on
#[derive(Debug)] #[derive(Debug)]
struct Crate { struct Crate {
@ -100,248 +55,6 @@ struct Crate {
options: Option<Vec<String>>, options: Option<Vec<String>>,
} }
/// A single emitted output from clippy being executed on a crate. It may either be a
/// `ClippyWarning`, or a `RustcIce` caused by a panic within clippy. A crate may have many
/// `ClippyWarning`s but a maximum of one `RustcIce` (at which point clippy halts execution).
#[derive(Debug)]
enum ClippyCheckOutput {
ClippyWarning(ClippyWarning),
RustcIce(RustcIce),
}
#[derive(Debug)]
struct RustcIce {
pub crate_name: String,
pub ice_content: String,
}
impl Display for RustcIce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:\n{}\n========================================\n",
self.crate_name, self.ice_content
)
}
}
impl RustcIce {
pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option<Self> {
if status.code().unwrap_or(0) == 101
/* ice exit status */
{
Some(Self {
crate_name: crate_name.to_owned(),
ice_content: stderr.to_owned(),
})
} else {
None
}
}
}
/// A single warning that clippy issued while checking a `Crate`
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct ClippyWarning {
crate_name: String,
crate_version: String,
lint_type: String,
diag: Diagnostic,
}
#[allow(unused)]
impl ClippyWarning {
fn new(mut diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option<Self> {
let lint_type = diag.code.clone()?.code;
if !(lint_type.contains("clippy") || diag.message.contains("clippy"))
|| diag.message.contains("could not read cargo metadata")
{
return None;
}
// --recursive bypasses cargo so we have to strip the rendered output ourselves
let rendered = diag.rendered.as_mut().unwrap();
*rendered = strip_ansi_escapes::strip_str(&rendered);
Some(Self {
crate_name: crate_name.to_owned(),
crate_version: crate_version.to_owned(),
lint_type,
diag,
})
}
fn span(&self) -> &DiagnosticSpan {
self.diag.spans.iter().find(|span| span.is_primary).unwrap()
}
fn to_output(&self, format: OutputFormat) -> String {
let span = self.span();
let mut file = span.file_name.clone();
let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end);
match format {
OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.diag.message),
OutputFormat::Markdown => {
if file.starts_with("target") {
file.insert_str(0, "../");
}
let mut output = String::from("| ");
write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap();
write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.diag.message).unwrap();
output.push('\n');
output
},
OutputFormat::Json => unreachable!("JSON output is handled via serde"),
}
}
}
#[allow(clippy::result_large_err)]
fn get(path: &str) -> Result<ureq::Response, ureq::Error> {
const MAX_RETRIES: u8 = 4;
let mut retries = 0;
loop {
match ureq::get(path).call() {
Ok(res) => return Ok(res),
Err(e) if retries >= MAX_RETRIES => return Err(e),
Err(ureq::Error::Transport(e)) => eprintln!("Error: {e}"),
Err(e) => return Err(e),
}
eprintln!("retrying in {retries} seconds...");
thread::sleep(Duration::from_secs(u64::from(retries)));
retries += 1;
}
}
impl CrateSource {
/// Makes the sources available on the disk for clippy to check.
/// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
/// copies a local folder
fn download_and_extract(&self) -> Crate {
match self {
CrateSource::CratesIo { name, version, options } => {
let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
// url to download the crate from crates.io
let url = format!("https://crates.io/api/v1/crates/{name}/{version}/download");
println!("Downloading and extracting {name} {version} from {url}");
create_dirs(&krate_download_dir, &extract_dir);
let krate_file_path = krate_download_dir.join(format!("{name}-{version}.crate.tar.gz"));
// don't download/extract if we already have done so
if !krate_file_path.is_file() {
// create a file path to download and write the crate data into
let mut krate_dest = fs::File::create(&krate_file_path).unwrap();
let mut krate_req = get(&url).unwrap().into_reader();
// copy the crate into the file
io::copy(&mut krate_req, &mut krate_dest).unwrap();
// unzip the tarball
let ungz_tar = flate2::read::GzDecoder::new(fs::File::open(&krate_file_path).unwrap());
// extract the tar archive
let mut archive = tar::Archive::new(ungz_tar);
archive.unpack(&extract_dir).expect("Failed to extract!");
}
// crate is extracted, return a new Krate object which contains the path to the extracted
// sources that clippy can check
Crate {
version: version.clone(),
name: name.clone(),
path: extract_dir.join(format!("{name}-{version}/")),
options: options.clone(),
}
},
CrateSource::Git {
name,
url,
commit,
options,
} => {
let repo_path = {
let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
// add a -git suffix in case we have the same crate from crates.io and a git repo
repo_path.push(format!("{name}-git"));
repo_path
};
// clone the repo if we have not done so
if !repo_path.is_dir() {
println!("Cloning {url} and checking out {commit}");
if !Command::new("git")
.arg("clone")
.arg(url)
.arg(&repo_path)
.status()
.expect("Failed to clone git repo!")
.success()
{
eprintln!("Failed to clone {url} into {}", repo_path.display());
}
}
// check out the commit/branch/whatever
if !Command::new("git")
.args(["-c", "advice.detachedHead=false"])
.arg("checkout")
.arg(commit)
.current_dir(&repo_path)
.status()
.expect("Failed to check out commit")
.success()
{
eprintln!("Failed to checkout {commit} of repo at {}", repo_path.display());
}
Crate {
version: commit.clone(),
name: name.clone(),
path: repo_path,
options: options.clone(),
}
},
CrateSource::Path { name, path, options } => {
fn is_cache_dir(entry: &DirEntry) -> bool {
fs::read(entry.path().join("CACHEDIR.TAG"))
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
.unwrap_or(false)
}
// copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
// The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
// as a result of this filter.
let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
if dest_crate_root.exists() {
println!("Deleting existing directory at {dest_crate_root:?}");
fs::remove_dir_all(&dest_crate_root).unwrap();
}
println!("Copying {path:?} to {dest_crate_root:?}");
for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
let entry = entry.unwrap();
let entry_path = entry.path();
let relative_entry_path = entry_path.strip_prefix(path).unwrap();
let dest_path = dest_crate_root.join(relative_entry_path);
let metadata = entry_path.symlink_metadata().unwrap();
if metadata.is_dir() {
fs::create_dir(dest_path).unwrap();
} else if metadata.is_file() {
fs::copy(entry_path, dest_path).unwrap();
}
}
Crate {
version: String::from("local"),
name: name.clone(),
path: dest_crate_root,
options: options.clone(),
}
},
}
}
}
impl Crate { impl Crate {
/// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
/// issued /// issued
@ -352,7 +65,7 @@ impl Crate {
target_dir_index: &AtomicUsize, target_dir_index: &AtomicUsize,
total_crates_to_lint: usize, total_crates_to_lint: usize,
config: &LintcheckConfig, config: &LintcheckConfig,
lint_filter: &[String], lint_levels_args: &[String],
server: &Option<LintcheckServer>, server: &Option<LintcheckServer>,
) -> Vec<ClippyCheckOutput> { ) -> Vec<ClippyCheckOutput> {
// advance the atomic index by one // advance the atomic index by one
@ -398,16 +111,9 @@ impl Crate {
for opt in options { for opt in options {
clippy_args.push(opt); clippy_args.push(opt);
} }
} else {
clippy_args.extend(["-Wclippy::pedantic", "-Wclippy::cargo"]);
} }
if lint_filter.is_empty() { clippy_args.extend(lint_levels_args.iter().map(String::as_str));
clippy_args.push("--cap-lints=warn");
} else {
clippy_args.push("--cap-lints=allow");
clippy_args.extend(lint_filter.iter().map(String::as_str));
}
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.arg(if config.fix { "fix" } else { "check" }) cmd.arg(if config.fix { "fix" } else { "check" })
@ -479,7 +185,7 @@ impl Crate {
// get all clippy warnings and ICEs // get all clippy warnings and ICEs
let mut entries: Vec<ClippyCheckOutput> = Message::parse_stream(stdout.as_bytes()) let mut entries: Vec<ClippyCheckOutput> = Message::parse_stream(stdout.as_bytes())
.filter_map(|msg| match msg { .filter_map(|msg| match msg {
Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message, &self.name, &self.version), Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message),
_ => None, _ => None,
}) })
.map(ClippyCheckOutput::ClippyWarning) .map(ClippyCheckOutput::ClippyWarning)
@ -509,96 +215,6 @@ fn build_clippy() -> String {
String::from_utf8_lossy(&output.stdout).into_owned() String::from_utf8_lossy(&output.stdout).into_owned()
} }
/// Read a `lintcheck_crates.toml` file
fn read_crates(toml_path: &Path) -> (Vec<CrateSource>, RecursiveOptions) {
let toml_content: String =
fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
let crate_list: SourceList =
toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{e}", toml_path.display()));
// parse the hashmap of the toml file into a list of crates
let tomlcrates: Vec<TomlCrate> = crate_list.crates.into_values().collect();
// flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
// multiple Cratesources)
let mut crate_sources = Vec::new();
for tk in tomlcrates {
if let Some(ref path) = tk.path {
crate_sources.push(CrateSource::Path {
name: tk.name.clone(),
path: PathBuf::from(path),
options: tk.options.clone(),
});
} else if let Some(ref versions) = tk.versions {
// if we have multiple versions, save each one
for ver in versions {
crate_sources.push(CrateSource::CratesIo {
name: tk.name.clone(),
version: ver.to_string(),
options: tk.options.clone(),
});
}
} else if tk.git_url.is_some() && tk.git_hash.is_some() {
// otherwise, we should have a git source
crate_sources.push(CrateSource::Git {
name: tk.name.clone(),
url: tk.git_url.clone().unwrap(),
commit: tk.git_hash.clone().unwrap(),
options: tk.options.clone(),
});
} else {
panic!("Invalid crate source: {tk:?}");
}
// if we have a version as well as a git data OR only one git data, something is funky
if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some())
|| tk.git_hash.is_some() != tk.git_url.is_some()
{
eprintln!("tomlkrate: {tk:?}");
assert_eq!(
tk.git_hash.is_some(),
tk.git_url.is_some(),
"Error: Encountered TomlCrate with only one of git_hash and git_url!"
);
assert!(
tk.path.is_none() || (tk.git_hash.is_none() && tk.versions.is_none()),
"Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields"
);
unreachable!("Failed to translate TomlCrate into CrateSource!");
}
}
// sort the crates
crate_sources.sort();
(crate_sources, crate_list.recursive)
}
/// Generate a short list of occurring lints-types and their count
fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
// count lint type occurrences
let mut counter: HashMap<&String, usize> = HashMap::new();
warnings
.iter()
.for_each(|wrn| *counter.entry(&wrn.lint_type).or_insert(0) += 1);
// collect into a tupled list for sorting
let mut stats: Vec<(&&String, &usize)> = counter.iter().collect();
// sort by "000{count} {clippy::lintname}"
// to not have a lint with 200 and 2 warnings take the same spot
stats.sort_by_key(|(lint, count)| format!("{count:0>4}, {lint}"));
let mut header = String::from("| lint | count |\n");
header.push_str("| -------------------------------------------------- | ----- |\n");
let stats_string = stats
.iter()
.map(|(lint, count)| format!("| {lint:<50} | {count:>4} |\n"))
.fold(header, |mut table, line| {
table.push_str(&line);
table
});
(stats_string, counter)
}
fn main() { fn main() {
// We're being executed as a `RUSTC_WRAPPER` as part of `--recursive` // We're being executed as a `RUSTC_WRAPPER` as part of `--recursive`
if let Ok(addr) = env::var("LINTCHECK_SERVER") { if let Ok(addr) = env::var("LINTCHECK_SERVER") {
@ -638,15 +254,39 @@ fn lintcheck(config: LintcheckConfig) {
let (crates, recursive_options) = read_crates(&config.sources_toml_path); let (crates, recursive_options) = read_crates(&config.sources_toml_path);
let counter = AtomicUsize::new(1); let counter = AtomicUsize::new(1);
let lint_filter: Vec<String> = config let mut lint_level_args: Vec<String> = vec![];
.lint_filter if config.lint_filter.is_empty() {
.iter() lint_level_args.push("--cap-lints=warn".to_string());
.map(|filter| {
let mut filter = filter.clone(); // Set allow-by-default to warn
filter.insert_str(0, "--force-warn="); if config.warn_all {
filter [
}) "clippy::cargo",
.collect(); "clippy::nursery",
"clippy::pedantic",
"clippy::restriction",
]
.iter()
.map(|group| format!("--warn={group}"))
.collect_into(&mut lint_level_args);
} else {
["clippy::cargo", "clippy::pedantic"]
.iter()
.map(|group| format!("--warn={group}"))
.collect_into(&mut lint_level_args);
}
} else {
lint_level_args.push("--cap-lints=allow".to_string());
config
.lint_filter
.iter()
.map(|filter| {
let mut filter = filter.clone();
filter.insert_str(0, "--force-warn=");
filter
})
.collect_into(&mut lint_level_args);
};
let crates: Vec<Crate> = crates let crates: Vec<Crate> = crates
.into_iter() .into_iter()
@ -698,7 +338,7 @@ fn lintcheck(config: LintcheckConfig) {
&counter, &counter,
crates.len(), crates.len(),
&config, &config,
&lint_filter, &lint_level_args,
&server, &server,
) )
}) })
@ -727,7 +367,9 @@ fn lintcheck(config: LintcheckConfig) {
} }
let text = match config.format { let text = match config.format {
OutputFormat::Text | OutputFormat::Markdown => output(&warnings, &raw_ices, clippy_ver, &config), OutputFormat::Text | OutputFormat::Markdown => {
output::summarize_and_print_changes(&warnings, &raw_ices, clippy_ver, &config)
},
OutputFormat::Json => { OutputFormat::Json => {
if !raw_ices.is_empty() { if !raw_ices.is_empty() {
for ice in raw_ices { for ice in raw_ices {
@ -736,7 +378,7 @@ fn lintcheck(config: LintcheckConfig) {
panic!("Some crates ICEd"); panic!("Some crates ICEd");
} }
json::output(&warnings) json::output(warnings)
}, },
}; };
@ -745,135 +387,6 @@ fn lintcheck(config: LintcheckConfig) {
fs::write(&config.lintcheck_results_path, text).unwrap(); fs::write(&config.lintcheck_results_path, text).unwrap();
} }
/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`]
fn output(warnings: &[ClippyWarning], ices: &[RustcIce], clippy_ver: String, config: &LintcheckConfig) -> String {
// generate some stats
let (stats_formatted, new_stats) = gather_stats(warnings);
let old_stats = read_stats_from_file(&config.lintcheck_results_path);
let mut all_msgs: Vec<String> = warnings.iter().map(|warn| warn.to_output(config.format)).collect();
all_msgs.sort();
all_msgs.push("\n\n### Stats:\n\n".into());
all_msgs.push(stats_formatted);
let mut text = clippy_ver; // clippy version number on top
text.push_str("\n### Reports\n\n");
if config.format == OutputFormat::Markdown {
text.push_str("| file | lint | message |\n");
text.push_str("| --- | --- | --- |\n");
}
write!(text, "{}", all_msgs.join("")).unwrap();
text.push_str("\n\n### ICEs:\n");
for ice in ices {
writeln!(text, "{ice}").unwrap();
}
print_stats(old_stats, new_stats, &config.lint_filter);
text
}
/// read the previous stats from the lintcheck-log file
fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
let file_content: String = match fs::read_to_string(file_path).ok() {
Some(content) => content,
None => {
return HashMap::new();
},
};
let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
lines
.iter()
.skip_while(|line| line.as_str() != "### Stats:")
// Skipping the table header and the `Stats:` label
.skip(4)
.take_while(|line| line.starts_with("| "))
.filter_map(|line| {
let mut spl = line.split('|');
// Skip the first `|` symbol
spl.next();
if let (Some(lint), Some(count)) = (spl.next(), spl.next()) {
Some((lint.trim().to_string(), count.trim().parse::<usize>().unwrap()))
} else {
None
}
})
.collect::<HashMap<String, usize>>()
}
/// print how lint counts changed between runs
fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &[String]) {
let same_in_both_hashmaps = old_stats
.iter()
.filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val))
.map(|(k, v)| (k.to_string(), *v))
.collect::<Vec<(String, usize)>>();
let mut old_stats_deduped = old_stats;
let mut new_stats_deduped = new_stats;
// remove duplicates from both hashmaps
for (k, v) in &same_in_both_hashmaps {
assert!(old_stats_deduped.remove(k) == Some(*v));
assert!(new_stats_deduped.remove(k) == Some(*v));
}
println!("\nStats:");
// list all new counts (key is in new stats but not in old stats)
new_stats_deduped
.iter()
.filter(|(new_key, _)| !old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_value)| {
println!("{new_key} 0 => {new_value}");
});
// list all changed counts (key is in both maps but value differs)
new_stats_deduped
.iter()
.filter(|(new_key, _new_val)| old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_val)| {
let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
println!("{new_key} {old_val} => {new_val}");
});
// list all gone counts (key is in old status but not in new stats)
old_stats_deduped
.iter()
.filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key))
.filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
.for_each(|(old_key, old_value)| {
println!("{old_key} {old_value} => 0");
});
}
/// Create necessary directories to run the lintcheck tool.
///
/// # Panics
///
/// This function panics if creating one of the dirs fails.
fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
fs::create_dir("target/lintcheck/").unwrap_or_else(|err| {
assert_eq!(
err.kind(),
ErrorKind::AlreadyExists,
"cannot create lintcheck target dir"
);
});
fs::create_dir(krate_download_dir).unwrap_or_else(|err| {
assert_eq!(err.kind(), ErrorKind::AlreadyExists, "cannot create crate download dir");
});
fs::create_dir(extract_dir).unwrap_or_else(|err| {
assert_eq!(
err.kind(),
ErrorKind::AlreadyExists,
"cannot create crate extraction dir"
);
});
}
/// Returns the path to the Clippy project directory /// Returns the path to the Clippy project directory
#[must_use] #[must_use]
fn clippy_project_root() -> &'static Path { fn clippy_project_root() -> &'static Path {

View File

@ -0,0 +1,235 @@
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Write as _};
use std::fs;
use std::path::Path;
use std::process::ExitStatus;
use crate::config::{LintcheckConfig, OutputFormat};
/// A single emitted output from clippy being executed on a crate. It may either be a
/// `ClippyWarning`, or a `RustcIce` caused by a panic within clippy. A crate may have many
/// `ClippyWarning`s but a maximum of one `RustcIce` (at which point clippy halts execution).
#[derive(Debug)]
pub enum ClippyCheckOutput {
ClippyWarning(ClippyWarning),
RustcIce(RustcIce),
}
#[derive(Debug)]
pub struct RustcIce {
pub crate_name: String,
pub ice_content: String,
}
impl fmt::Display for RustcIce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:\n{}\n========================================\n",
self.crate_name, self.ice_content
)
}
}
impl RustcIce {
pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option<Self> {
if status.code().unwrap_or(0) == 101
/* ice exit status */
{
Some(Self {
crate_name: crate_name.to_owned(),
ice_content: stderr.to_owned(),
})
} else {
None
}
}
}
/// A single warning that clippy issued while checking a `Crate`
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ClippyWarning {
pub lint: String,
pub diag: Diagnostic,
}
#[allow(unused)]
impl ClippyWarning {
pub fn new(mut diag: Diagnostic) -> Option<Self> {
let lint = diag.code.clone()?.code;
if !(lint.contains("clippy") || diag.message.contains("clippy"))
|| diag.message.contains("could not read cargo metadata")
{
return None;
}
// --recursive bypasses cargo so we have to strip the rendered output ourselves
let rendered = diag.rendered.as_mut().unwrap();
*rendered = strip_ansi_escapes::strip_str(&rendered);
Some(Self { lint, diag })
}
pub fn span(&self) -> &DiagnosticSpan {
self.diag.spans.iter().find(|span| span.is_primary).unwrap()
}
pub fn to_output(&self, format: OutputFormat) -> String {
let span = self.span();
let mut file = span.file_name.clone();
let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end);
match format {
OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint, self.diag.message),
OutputFormat::Markdown => {
if file.starts_with("target") {
file.insert_str(0, "../");
}
let mut output = String::from("| ");
write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap();
write!(output, r#" | `{:<50}` | "{}" |"#, self.lint, self.diag.message).unwrap();
output.push('\n');
output
},
OutputFormat::Json => unreachable!("JSON output is handled via serde"),
}
}
}
/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`]
pub fn summarize_and_print_changes(
warnings: &[ClippyWarning],
ices: &[RustcIce],
clippy_ver: String,
config: &LintcheckConfig,
) -> String {
// generate some stats
let (stats_formatted, new_stats) = gather_stats(warnings);
let old_stats = read_stats_from_file(&config.lintcheck_results_path);
let mut all_msgs: Vec<String> = warnings.iter().map(|warn| warn.to_output(config.format)).collect();
all_msgs.sort();
all_msgs.push("\n\n### Stats:\n\n".into());
all_msgs.push(stats_formatted);
let mut text = clippy_ver; // clippy version number on top
text.push_str("\n### Reports\n\n");
if config.format == OutputFormat::Markdown {
text.push_str("| file | lint | message |\n");
text.push_str("| --- | --- | --- |\n");
}
write!(text, "{}", all_msgs.join("")).unwrap();
text.push_str("\n\n### ICEs:\n");
for ice in ices {
writeln!(text, "{ice}").unwrap();
}
print_stats(old_stats, new_stats, &config.lint_filter);
text
}
/// Generate a short list of occurring lints-types and their count
fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
// count lint type occurrences
let mut counter: HashMap<&String, usize> = HashMap::new();
warnings
.iter()
.for_each(|wrn| *counter.entry(&wrn.lint).or_insert(0) += 1);
// collect into a tupled list for sorting
let mut stats: Vec<(&&String, &usize)> = counter.iter().collect();
// sort by "000{count} {clippy::lintname}"
// to not have a lint with 200 and 2 warnings take the same spot
stats.sort_by_key(|(lint, count)| format!("{count:0>4}, {lint}"));
let mut header = String::from("| lint | count |\n");
header.push_str("| -------------------------------------------------- | ----- |\n");
let stats_string = stats
.iter()
.map(|(lint, count)| format!("| {lint:<50} | {count:>4} |\n"))
.fold(header, |mut table, line| {
table.push_str(&line);
table
});
(stats_string, counter)
}
/// read the previous stats from the lintcheck-log file
fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
let file_content: String = match fs::read_to_string(file_path).ok() {
Some(content) => content,
None => {
return HashMap::new();
},
};
let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
lines
.iter()
.skip_while(|line| line.as_str() != "### Stats:")
// Skipping the table header and the `Stats:` label
.skip(4)
.take_while(|line| line.starts_with("| "))
.filter_map(|line| {
let mut spl = line.split('|');
// Skip the first `|` symbol
spl.next();
if let (Some(lint), Some(count)) = (spl.next(), spl.next()) {
Some((lint.trim().to_string(), count.trim().parse::<usize>().unwrap()))
} else {
None
}
})
.collect::<HashMap<String, usize>>()
}
/// print how lint counts changed between runs
fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &[String]) {
let same_in_both_hashmaps = old_stats
.iter()
.filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val))
.map(|(k, v)| (k.to_string(), *v))
.collect::<Vec<(String, usize)>>();
let mut old_stats_deduped = old_stats;
let mut new_stats_deduped = new_stats;
// remove duplicates from both hashmaps
for (k, v) in &same_in_both_hashmaps {
assert!(old_stats_deduped.remove(k) == Some(*v));
assert!(new_stats_deduped.remove(k) == Some(*v));
}
println!("\nStats:");
// list all new counts (key is in new stats but not in old stats)
new_stats_deduped
.iter()
.filter(|(new_key, _)| !old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_value)| {
println!("{new_key} 0 => {new_value}");
});
// list all changed counts (key is in both maps but value differs)
new_stats_deduped
.iter()
.filter(|(new_key, _new_val)| old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_val)| {
let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
println!("{new_key} {old_val} => {new_val}");
});
// list all gone counts (key is in old status but not in new stats)
old_stats_deduped
.iter()
.filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key))
.filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
.for_each(|(old_key, old_value)| {
println!("{old_key} {old_value} => 0");
});
}

View File

@ -44,7 +44,7 @@ pub(crate) fn fetch(output: PathBuf, number: usize) -> Result<(), Box<dyn Error>
let mut out = "[crates]\n".to_string(); let mut out = "[crates]\n".to_string();
for Crate { name, max_version } in crates { for Crate { name, max_version } in crates {
writeln!(out, "{name} = {{ name = '{name}', versions = ['{max_version}'] }}").unwrap(); writeln!(out, "{name} = {{ name = '{name}', version = '{max_version}' }}").unwrap();
} }
fs::write(output, out)?; fs::write(output, out)?;

View File

@ -3,7 +3,8 @@
//! [`LintcheckServer`] to ask if it should be skipped, and if not sends the stderr of running //! [`LintcheckServer`] to ask if it should be skipped, and if not sends the stderr of running
//! clippy on the crate to the server //! clippy on the crate to the server
use crate::{ClippyWarning, RecursiveOptions}; use crate::input::RecursiveOptions;
use crate::ClippyWarning;
use std::collections::HashSet; use std::collections::HashSet;
use std::io::{BufRead, BufReader, Read, Write}; use std::io::{BufRead, BufReader, Read, Write};
@ -19,8 +20,6 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize)] #[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize)]
pub(crate) struct DriverInfo { pub(crate) struct DriverInfo {
pub package_name: String, pub package_name: String,
pub crate_name: String,
pub version: String,
} }
pub(crate) fn serialize_line<T, W>(value: &T, writer: &mut W) pub(crate) fn serialize_line<T, W>(value: &T, writer: &mut W)
@ -65,7 +64,7 @@ fn process_stream(
let messages = stderr let messages = stderr
.lines() .lines()
.filter_map(|json_msg| serde_json::from_str::<Diagnostic>(json_msg).ok()) .filter_map(|json_msg| serde_json::from_str::<Diagnostic>(json_msg).ok())
.filter_map(|diag| ClippyWarning::new(diag, &driver_info.package_name, &driver_info.version)); .filter_map(ClippyWarning::new);
for message in messages { for message in messages {
sender.send(message).unwrap(); sender.send(message).unwrap();

View File

@ -1,3 +1,4 @@
[toolchain] [toolchain]
channel = "nightly-2024-06-27" channel = "nightly-2024-07-11"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View File

@ -1,22 +1,18 @@
error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
--> tests/ui-internal/disallow_span_lint.rs:14:5 --> tests/ui-internal/disallow_span_lint.rs:14:8
| |
LL | / cx.span_lint(lint, span, |lint| { LL | cx.span_lint(lint, span, |lint| {
LL | | lint.primary_message(msg); | ^^^^^^^^^
LL | | });
| |______^
| |
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml) = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml)
= note: `-D clippy::disallowed-methods` implied by `-D warnings` = note: `-D clippy::disallowed-methods` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
--> tests/ui-internal/disallow_span_lint.rs:20:5 --> tests/ui-internal/disallow_span_lint.rs:20:9
| |
LL | / tcx.node_span_lint(lint, hir_id, span, |lint| { LL | tcx.node_span_lint(lint, hir_id, span, |lint| {
LL | | lint.primary_message(msg); | ^^^^^^^^^^^^^^
LL | | });
| |______^
| |
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml) = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml)

Some files were not shown because too many files have changed in this diff Show More