Merge commit 'b794b8e08c16517a941dc598bb1483e8e12a8592' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-07-11 15:44:03 +02:00
commit 2ed6ed41be
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
191 changed files with 4157 additions and 2504 deletions

View File

@ -58,7 +58,7 @@ jobs:
- name: Run lintcheck
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
uses: actions/upload-artifact@v4
@ -86,7 +86,7 @@ jobs:
run: cargo build --manifest-path=lintcheck/Cargo.toml
- name: Run lintcheck
run: ./target/debug/lintcheck --format json
run: ./target/debug/lintcheck --format json --warn-all
- name: Upload head JSON
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
[`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
[`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_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
@ -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_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
[`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
[`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
@ -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_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_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_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
@ -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_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_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_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
@ -5701,6 +5705,7 @@ Released 2018-09-13
[`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_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
[`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
@ -5797,6 +5802,7 @@ Released 2018-09-13
[`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
[`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_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

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)
* [`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)
* [`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)
* [`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)
@ -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 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:**

View File

@ -1,5 +1,9 @@
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]]
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"

View File

@ -31,6 +31,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"OCaml",
"OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry",
"WebGL", "WebGL2", "WebGPU",
"WebP", "OpenExr", "YCbCr", "sRGB",
"TensorFlow",
"TrueType",
"iOS", "macOS", "FreeBSD",
@ -262,7 +263,7 @@ define_Conf! {
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ```
(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.
(avoid_breaking_exported_api: bool = true),

View File

@ -25,8 +25,8 @@ msrv_aliases! {
1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
1,63,0 { CLONE_INTO }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_FN }
1,59,0 { THREAD_LOCAL_CONST_INIT }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
1,56,0 { CONST_FN_UNION }
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 {
formatdoc!(
r#"
use clippy_utils::msrvs::{{self, Msrv}};
use clippy_config::msrvs::{{self, Msrv}};
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
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!(
lint_file_contents,
r#"
use clippy_utils::msrvs::{{self, Msrv}};
use clippy_config::msrvs::{{self, Msrv}};
use rustc_lint::{{{context_import}, LintContext}};
use super::{name_upper};

View File

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

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::is_inside_always_const_context;
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_session::declare_lint_pass;
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 {
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;
};
if let ConstantSource::Constant = source
&& let Node::Item(Item {
kind: ItemKind::Const(..),
..
}) = cx.tcx.parent_hir_node(e.hir_id)
{
return;
match condition.kind {
ExprKind::Path(..) | ExprKind::Lit(_) => {},
_ if is_inside_always_const_context(cx.tcx, e.hir_id) => return,
_ => {},
}
if val {
span_lint_and_help(
cx,

View File

@ -1,18 +1,16 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::HirNode;
use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap};
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_hir::{self as hir, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir;
use rustc_middle::ty::{self, Instance, Mutability};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::{ExpnKind, Span, SyntaxContext};
use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -68,167 +66,84 @@ impl AssigningClones {
impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
impl<'tcx> LateLintPass<'tcx> for AssigningClones {
fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) {
// Do not fire the lint in macros
let ctxt = assign_expr.span().ctxt();
let expn_data = ctxt.outer_expn_data();
match expn_data.kind {
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return,
ExpnKind::Root => {},
}
let ExprKind::Assign(lhs, rhs, _span) = assign_expr.kind else {
return;
};
let Some(call) = extract_call(cx, rhs) else {
return;
};
if is_ok_to_suggest(cx, lhs, &call, &self.msrv) {
suggest(cx, ctxt, assign_expr, lhs, &call);
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Assign(lhs, rhs, _) = e.kind
&& let typeck = cx.typeck_results()
&& let (call_kind, fn_name, fn_id, fn_arg, fn_gen_args) = match rhs.kind {
ExprKind::Call(f, [arg])
if let ExprKind::Path(fn_path) = &f.kind
&& 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))
},
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))
},
_ => return,
}
&& let ctxt = e.span.ctxt()
// Don't lint in macros.
&& ctxt.is_root()
&& 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);
}
// 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:
///
/// ```
@ -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.
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 {
return false;
};
@ -267,172 +172,123 @@ fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_spa
//
// bb2:
// s = s_temp
for bb in mir.basic_blocks.iter() {
let terminator = bb.terminator();
// Look for the to_owned/clone call.
if terminator.source_info.span != call_span {
continue;
if let Some(terminator) = mir.basic_blocks.iter()
.map(mir::BasicBlockData::terminator)
.find(|term| term.source_info.span == call_span)
&& let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind
&& let [source] = &**args
&& 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,
}
if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind
&& let [source] = &**args
&& let mir::Operand::Move(source) = &source.node
&& let assign_bb = skip_drop_block(mir, assign_bb)
// Skip any storage statements as they are just noise
&& let Some(assignment) = mir.basic_blocks[assign_bb].statements
.iter()
.find(|stmt| {
!matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))
})
&& let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind
&& let Some(borrowers) = borrow_map.get(&borrowed.local)
&& borrowers.contains(source.local)
{
return true;
}
return false;
// Skip any storage statements as they are just noise
&& let Some(assignment) = assign_bb.statements
.iter()
.find(|stmt| {
!matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))
})
&& let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind
&& let Some(borrowers) = borrow_map.get(&borrowed.local)
{
borrowers.contains(source.local)
} else {
false
}
false
}
fn suggest<'tcx>(
cx: &LateContext<'tcx>,
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 {
#[derive(Clone, Copy)]
enum CloneTrait {
Clone,
ToOwned,
}
#[derive(Debug)]
struct CallCandidate<'tcx> {
span: Span,
target: TargetTrait,
kind: CallKind<'tcx>,
// DefId of the called method from an impl block that implements the target trait
method_def_id: DefId,
#[derive(Copy, Clone)]
enum CallKind {
Ufcs,
Method,
}
impl<'tcx> CallCandidate<'tcx> {
#[inline]
fn message(&self) -> &'static str {
match self.target {
TargetTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient",
TargetTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient",
}
}
#[inline]
fn suggestion_msg(&self) -> &'static str {
match self.target {
TargetTrait::Clone => "use `clone_from()`",
TargetTrait::ToOwned => "use `clone_into()`",
}
}
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
fn build_sugg<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
lhs: &'tcx Expr<'_>,
fn_arg: &'tcx Expr<'_>,
which_trait: CloneTrait,
call_kind: CallKind,
app: &mut Applicability,
) -> String {
match which_trait {
CloneTrait::Clone => {
match call_kind {
CallKind::Method => {
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)
} else {
sugg.mut_addr()
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app)
}
} 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, "_", applicability)
.maybe_par()
.mut_addr()
};
.maybe_par();
match self.kind {
CallKind::MethodCall { receiver } => {
let receiver_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability);
format!("{receiver_sugg}.clone_into({rhs_sugg})")
},
CallKind::FunctionCall { self_arg, .. } => {
let self_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability);
format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})")
},
// Determine whether we need to reference the argument to clone_from().
let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);
let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(fn_arg);
let mut arg_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app);
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::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! {
/// ### 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?
/// The Mutex types found in std::sync and parking_lot
/// are not designed to operate in an async context across await points.
/// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and
/// [`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
/// type. Many asynchronous foundation crates provide such a Mutex type. The
/// other solution is to ensure the mutex is unlocked before calling await,
/// either by introducing a scope or an explicit call to Drop::drop.
/// There are two potential solutions. One is to use an async-aware `Mutex`
/// type. Many asynchronous foundation crates provide such a `Mutex` type.
/// The other solution is to ensure the mutex is unlocked before calling
/// `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
/// Will report false positive for explicitly dropped guards
/// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
/// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
/// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A
/// workaround for this is to wrap the `.lock()` call in a block instead of
/// explicitly dropping the guard.
///
/// ### Example
/// ```no_run
@ -73,11 +77,11 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### 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?
/// `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.
///
/// ### Known problems
@ -131,13 +135,13 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### 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.
///
/// ### Why is this bad?
/// There are some types which are perfectly "safe" to be used concurrently
/// from a memory access perspective but will cause bugs at runtime if they
/// are held in such a way.
/// There are some types which are perfectly safe to use concurrently from
/// a memory access perspective, but that will cause bugs at runtime if
/// they are held in such a way.
///
/// ### Example
///
@ -228,15 +232,15 @@ impl AwaitHolding {
cx,
AWAIT_HOLDING_LOCK,
ty_cause.source_info.span,
"this `MutexGuard` is held across an `await` point",
"this `MutexGuard` is held across an await point",
|diag| {
diag.help(
"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(
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,
AWAIT_HOLDING_REFCELL_REF,
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.help("ensure the reference is dropped before calling `await`");
diag.span_note(
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,
span,
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()
),
|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::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_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -47,80 +45,64 @@ declare_clippy_lint! {
declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) {
check_if_else(cx, expr);
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::If(cond, then, Some(else_)) = expr.kind
&& 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>) {
if let Some(If {
cond,
then,
r#else: Some(r#else),
}) = If::hir(expr)
&& let Some(then_lit) = int_literal(then)
&& let Some(else_lit) = int_literal(r#else)
fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> {
if let ExprKind::Block(b, _) = e.kind
&& b.stmts.is_empty()
&& let Some(e) = b.expr
&& let ExprKind::Lit(lit) = e.kind
&& let LitKind::Int(x, _) = lit.node
{
let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) {
false
} else if is_integer_literal(then_lit, 0) && is_integer_literal(else_lit, 1) {
true
} 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)
match x.get() {
0 => Some(false),
1 => Some(true),
_ => None,
}
} else {
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! {
/// ### 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
/// rounding errors. This possible rounding is to be expected, so this lint is
/// `Allow` by default.
@ -58,14 +58,14 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### 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,
/// 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.
///
/// ### Why is this bad?
/// 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
/// ```no_run
@ -80,7 +80,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### 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
/// default. It suggests user either explicitly ignore the lint,
/// or use `try_from()` and handle the truncation, default, or panic explicitly.
@ -120,17 +120,16 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts from an unsigned type to a signed type of
/// 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
/// changed at the bit level, and the binary representation of the value is
/// the same size, or possibly smaller due to target-dependent integers.
/// 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
/// 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
/// is `Allow` by default.
///
/// ### Why is this bad?
/// 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
/// example below.
/// be surprising when this is not the intended behavior:
///
/// ### Example
/// ```no_run
@ -144,16 +143,16 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts between numerical types that may
/// be replaced by safe conversion functions.
/// Checks for casts between numeric types that can be replaced by safe
/// conversion functions.
///
/// ### Why is this bad?
/// Rust's `as` keyword will perform many kinds of
/// conversions, including silently lossy conversions. Conversion functions such
/// as `i32::from` will only perform lossless conversions. Using the conversion
/// functions prevents conversions from turning into silent lossy conversions if
/// the types of the input expressions ever change, and make it easier for
/// people reading the code to know that the conversion is lossless.
/// Rust's `as` keyword will perform many kinds of conversions, including
/// silently lossy conversions. Conversion functions such as `i32::from`
/// will only perform lossless conversions. Using the conversion functions
/// prevents conversions from becoming silently lossy if the input types
/// ever change, and makes it clear for people reading the code that the
/// conversion is lossless.
///
/// ### Example
/// ```no_run
@ -177,19 +176,21 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts to the same type, casts of int literals to integer types, casts of float
/// literals to float types and casts between raw pointers without changing type or constness.
/// Checks for casts to the same type, casts of int literals to integer
/// types, casts of float literals to float types, and casts between raw
/// pointers that don't change type or constness.
///
/// ### Why is this bad?
/// It's just unnecessary.
///
/// ### Known problems
/// When the expression on the left is a function call, the lint considers the return type to be
/// a type alias if it's aliased through a `use` statement
/// (like `use std::io::Result as IoResult`). It will not lint such cases.
/// When the expression on the left is a function call, the lint considers
/// the return type to be a type alias if it's aliased through a `use`
/// statement (like `use std::io::Result as IoResult`). It will not lint
/// such cases.
///
/// This check is also rather primitive. It will only work on primitive types without any
/// intermediate references, raw pointers and trait objects may or may not work.
/// This check will only work on primitive types without any intermediate
/// references: raw pointers and trait objects may or may not work.
///
/// ### Example
/// ```no_run
@ -211,17 +212,17 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for casts, using `as` or `pointer::cast`,
/// from a less-strictly-aligned pointer to a more-strictly-aligned pointer
/// Checks for casts, using `as` or `pointer::cast`, from a
/// less strictly aligned pointer to a more strictly aligned pointer.
///
/// ### Why is this bad?
/// Dereferencing the resulting pointer may be undefined
/// behavior.
/// Dereferencing the resulting pointer may be undefined behavior.
///
/// ### Known problems
/// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
/// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like
/// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis.
/// 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
/// similar on the resulting pointer is fine. Is over-zealous: casts with
/// manual alignment checks or casts like `u64` -> `u8` -> `u16` can be
/// fine. Miri is able to do a more in-depth analysis.
///
/// ### Example
/// ```no_run
@ -234,20 +235,21 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"]
pub CAST_PTR_ALIGNMENT,
pedantic,
"cast from a pointer to a more-strictly-aligned pointer"
"cast from a pointer to a more strictly aligned pointer"
}
declare_clippy_lint! {
/// ### 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?
/// Casting a function pointer to anything other than usize/isize is not portable across
/// architectures, because you end up losing bits if the target type is too small or end up with a
/// bunch of extra bits that waste space and add more instructions to the final binary than
/// strictly necessary for the problem
/// Casting a function pointer to anything other than `usize`/`isize` is
/// not portable across architectures. If the target type is too small the
/// address would be truncated, and target types larger than `usize` are
/// 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
/// ```no_run
@ -263,17 +265,17 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"]
pub FN_TO_NUMERIC_CAST,
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! {
/// ### What it does
/// Checks for casts of a function pointer to a numeric type not wide enough to
/// store address.
/// store an address.
///
/// ### Why is this bad?
/// 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.
///
/// ### Example
@ -306,7 +308,7 @@ declare_clippy_lint! {
/// ### Why restrict this?
/// 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
/// 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
/// pointer casts in your code.
///
@ -349,8 +351,8 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// In general, casting values to smaller types is
/// 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
/// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
/// 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 also slightly shorter
/// than `'a' as u8`.
///
/// ### Example
@ -371,12 +373,13 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for `as` casts between raw pointers without changing its mutability,
/// namely `*const T` to `*const U` and `*mut T` to `*mut U`.
/// Checks for `as` casts between raw pointers that don't change their
/// constness, namely `*const T` to `*const U` and `*mut T` to `*mut U`.
///
/// ### Why is this bad?
/// Though `as` casts between raw pointers are not terrible, `pointer::cast` is safer because
/// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`.
/// Though `as` casts between raw pointers are not terrible,
/// `pointer::cast` is safer because it cannot accidentally change the
/// pointer's mutability, nor cast the pointer to other types like `usize`.
///
/// ### Example
/// ```no_run
@ -395,12 +398,12 @@ declare_clippy_lint! {
#[clippy::version = "1.51.0"]
pub PTR_AS_PTR,
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! {
/// ### 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`.
///
/// ### Why is this bad?
@ -423,12 +426,12 @@ declare_clippy_lint! {
#[clippy::version = "1.72.0"]
pub PTR_CAST_CONSTNESS,
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! {
/// ### 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.
///
/// ### Why is this bad?
@ -442,7 +445,7 @@ declare_clippy_lint! {
#[clippy::version = "1.61.0"]
pub CAST_ENUM_TRUNCATION,
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! {
@ -621,7 +624,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### 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?
/// 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,
PTR_AS_PTR,
expr.span,
"`as` casting between raw pointers without changing its mutability",
"`as` casting between raw pointers without changing their constness",
help,
final_suggestion,
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")]
crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
#[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,
#[cfg(feature = "internal")]
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::borrow_deref_ref::BORROW_DEREF_REF_INFO,
crate::box_default::BOX_DEFAULT_INFO,
crate::byte_char_slices::BYTE_CHAR_SLICES_INFO,
crate::cargo::CARGO_COMMON_METADATA_INFO,
crate::cargo::LINT_GROUPS_PRIORITY_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::UNNECESSARY_CAST_INFO,
crate::casts::ZERO_PTR_INFO,
crate::cfg_not_test::CFG_NOT_TEST_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_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_rem_euclid::MANUAL_REM_EUCLID_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_string_new::MANUAL_STRING_NEW_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_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_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_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_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::option_env_unwrap::OPTION_ENV_UNWRAP_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_unimplemented::PANIC_INFO,
crate::panic_unimplemented::TODO_INFO,
crate::panic_unimplemented::UNIMPLEMENTED_INFO,
crate::panic_unimplemented::UNREACHABLE_INFO,
crate::panicking_overflow_checks::PANICKING_OVERFLOW_CHECKS_INFO,
crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO,
crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_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_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_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_SAME_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::temporary_assignment::TEMPORARY_ASSIGNMENT_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_string_trait_impl::TO_STRING_TRAIT_IMPL_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::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro};
use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// Avoid cases already linted by `field_reassign_with_default`
&& !self.reassigned_linted.contains(&expr.span)
&& 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 Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_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
// statements, see if they re-assign the fields of the binding
let stmts_head = match block.stmts {
[] | [_] => return,
// Skip the last statement since there cannot possibly be any following statements that re-assign fields.
[head @ .., _] if !head.is_empty() => head,
_ => return,
[head @ .., _] => head,
};
for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
// 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
// only take `let ...` statements
&& 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()
// only take bindings to identifiers
&& 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 {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) {
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!(
hir.body_const_context(hir.body_owner_def_id(body.id())),
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::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
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 rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS};
@ -175,6 +176,7 @@ struct StateData<'tcx> {
adjusted_ty: Ty<'tcx>,
}
#[derive(Debug)]
struct DerefedBorrow {
count: usize,
msg: &'static str,
@ -182,6 +184,7 @@ struct DerefedBorrow {
for_field_access: Option<Symbol>,
}
#[derive(Debug)]
enum State {
// Any number of deref method calls.
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 {
Deref,
Reborrow,
@ -1042,16 +1045,28 @@ fn report<'tcx>(
return;
}
let (prefix, precedence) = if let Some(mutability) = mutability
&& !typeck.expr_ty(expr).is_ref()
let ty = typeck.expr_ty(expr);
// `&&[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 {
Mutability::Not => "&",
Mutability::Mut => "&mut ",
};
(prefix, PREC_PREFIX)
} else {
("", 0)
let (src, n_src_refs) = peel_middle_ty_refs(ty);
if n_src_refs >= 2 && src.is_array() {
return;
}
}
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(
cx,

View File

@ -1,6 +1,6 @@
use clippy_config::types::DisallowedPath;
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::{Expr, ExprKind};
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<'_>) {
let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::Call(receiver, _) = parent.kind
&& receiver.hir_id == expr.hir_id
{
None
} else {
path_def_id(cx, expr)
let (id, span) = match &expr.kind {
ExprKind::Path(path)
if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) =
cx.qpath_res(path, expr.hir_id) =>
{
(id, expr.span)
},
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 {
return;
};
let conf = match self.disallowed.get(&def_id) {
Some(&index) => &self.conf_disallowed[index],
None => return,
};
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);
}
});
if let Some(&index) = self.disallowed.get(&id) {
let conf = &self.conf_disallowed[index];
let msg = format!("use of a disallowed method `{}`", conf.path());
span_lint_and_then(cx, DISALLOWED_METHODS, 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::is_test_module_or_function;
use clippy_utils::is_in_test;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{Item, Pat, PatKind};
use rustc_hir::{Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -27,52 +27,30 @@ declare_clippy_lint! {
#[derive(Clone, Debug)]
pub struct DisallowedNames {
disallow: FxHashSet<String>,
test_modules_deep: u32,
}
impl DisallowedNames {
pub fn new(disallowed_names: &[String]) -> Self {
Self {
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<'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<'_>) {
// Check whether we are under the `test` attribute.
if self.in_test_module() {
return;
}
if let PatKind::Binding(.., ident, _) = pat.kind {
if self.disallow.contains(&ident.name.to_string()) {
span_lint(
cx,
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);
if let PatKind::Binding(.., ident, _) = pat.kind
&& self.disallow.contains(&ident.name.to_string())
&& !is_in_test(cx.tcx, pat.hir_id)
{
span_lint(
cx,
DISALLOWED_NAMES,
ident.span,
format!("use of a disallowed/placeholder name `{}`", ident.name),
);
}
}
}

View File

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

View File

@ -22,6 +22,7 @@ pub(super) fn check(
range: Range<usize>,
mut span: Span,
containers: &[super::Container],
line_break_span: Span,
) {
if doc[range.clone()].contains('\t') {
// We don't do tab stops correctly.
@ -46,11 +47,35 @@ pub(super) fn check(
.sum();
if ccount < blockquote_level || lcount < list_indentation {
let msg = if ccount < blockquote_level {
"doc quote missing `>` marker"
"doc quote line without `>` marker"
} else {
"doc list item missing indentation"
"doc list item without indentation"
};
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 {
// simpler suggestion style for indentation
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::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args};
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::{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,
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 => {
if !containers.is_empty()
&& 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,
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
&containers[..],
span,
);
}
},
TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (),
FootnoteReference(text) | Text(text) => {
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 {
// Probably a link of the form `<http://example.com>`
// 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`.
///
/// ### 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
/// ```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`.
///
/// ### 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
/// ```rust,ignore

View File

@ -1,5 +1,5 @@
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::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) {
if let FnKind::ItemFn(_, generics, _) = kind
&& 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 {
if param.is_impl_trait() {
@ -59,7 +59,7 @@ pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
&& of_trait.is_none()
&& let body = cx.tcx.hir().body(body_id)
&& 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 {
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())
// ^^ (Will always be a trait)
&& !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 {
if param.is_impl_trait() {

View File

@ -1,6 +1,6 @@
use clippy_config::msrvs::Msrv;
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_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind, HirId};
@ -88,7 +88,7 @@ impl IncompatibleMsrv {
return;
}
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;
}
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 {
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'
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// #11201
@ -106,6 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
&& decl.implicit_self.has_implicit_self()
&& decl.inputs.len() == 1
&& impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }))
&& !impl_item.span.from_expansion()
// Check if return type is 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

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 rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use std::borrow::Cow;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
declare_clippy_lint! {
/// ### What it does
@ -44,38 +43,56 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]);
impl<'tcx> LateLintPass<'tcx> for NumberedFields {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Struct(path, fields, None) = e.kind {
if !fields.is_empty()
&& !e.span.from_expansion()
&& fields
.iter()
.all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit))
&& !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..))
{
let expr_spans = fields
.iter()
.map(|f| (Reverse(f.ident.as_str().parse::<usize>().unwrap()), f.expr.span))
.collect::<BinaryHeap<_>>();
let mut appl = Applicability::MachineApplicable;
let snippet = format!(
"{}({})",
snippet_with_applicability(cx, path.span(), "..", &mut appl),
expr_spans
.into_iter_sorted()
.map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0)
.intersperse(Cow::Borrowed(", "))
.collect::<String>()
);
span_lint_and_sugg(
cx,
INIT_NUMBERED_FIELDS,
e.span,
"used a field initializer for a tuple struct",
"try",
snippet,
appl,
);
}
if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind
// If the first character of any field is a digit it has to be a tuple.
&& field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit)
// Type aliases can't be used as functions.
&& !matches!(
cx.qpath_res(path, e.hir_id),
Res::Def(DefKind::TyAlias | DefKind::AssocTy, _)
)
// This is the only syntax macros can use that works for all struct types.
&& !e.span.from_expansion()
&& let mut has_side_effects = false
&& let Ok(mut expr_spans) = fields
.iter()
.map(|f| {
has_side_effects |= f.expr.can_have_side_effects();
f.ident.as_str().parse::<usize>().map(|x| (x, f.expr.span))
})
.collect::<Result<Vec<_>, _>>()
// We can only reorder the expressions if there are no side effects.
&& (!has_side_effects || expr_spans.is_sorted_by_key(|&(idx, _)| idx))
{
span_lint_and_then(
cx,
INIT_NUMBERED_FIELDS,
e.span,
"used a field initializer for a tuple struct",
|diag| {
if !has_side_effects {
// We already checked the order if there are side effects.
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::sugg::DiagExt;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::{sym, Symbol};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -34,27 +33,23 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind {
let attrs = cx.tcx.hir().attrs(item.hir_id());
check_attrs(cx, item.ident.name, attrs);
if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind
&& let Some(attr) = cx
.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,
rhs,
) = 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)
&& is_an_instant(cx, rhs)
&& ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant)
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
{
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)
&& is_an_instant(cx, lhs)
&& is_a_duration(cx, rhs)
{
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<'_>) {
span_lint_and_sugg(
cx,

View File

@ -54,36 +54,35 @@ declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
impl LateLintPass<'_> for ItemsAfterStatements {
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
if in_external_macro(cx.sess(), block.span) {
return;
}
// skip initial items
let stmts = block
.stmts
.iter()
.skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)));
// lint on all further items
for stmt in stmts {
if let StmtKind::Item(item_id) = stmt.kind {
let item = cx.tcx.hir().item(item_id);
if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) {
return;
}
if let ItemKind::Macro(..) = item.kind {
// do not lint `macro_rules`, but continue processing further statements
continue;
}
span_lint_hir(
cx,
ITEMS_AFTER_STATEMENTS,
item.hir_id(),
item.span,
"adding items after statements is confusing, since items exist from the \
start of the scope",
);
}
if block.stmts.len() > 1 {
let ctxt = block.span.ctxt();
let mut in_external = None;
block
.stmts
.iter()
.skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)))
.filter_map(|stmt| match stmt.kind {
StmtKind::Item(id) => Some(cx.tcx.hir().item(id)),
_ => None,
})
// Ignore macros since they can only see previously defined locals.
.filter(|item| !matches!(item.kind, ItemKind::Macro(..)))
// Stop linting if macros define items.
.take_while(|item| item.span.ctxt() == ctxt)
// Don't use `next` due to the complex filter chain.
.for_each(|item| {
// Only do the macro check once, but delay it until it's needed.
if !*in_external.get_or_insert_with(|| in_external_macro(cx.sess(), block.span)) {
span_lint_hir(
cx,
ITEMS_AFTER_STATEMENTS,
item.hir_id(),
item.span,
"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_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym;
use rustc_span::{sym, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -43,30 +43,27 @@ declare_lint_pass!(IterNotReturningIterator => [ITER_NOT_RETURNING_ITERATOR]);
impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
let name = item.ident.name.as_str();
if matches!(name, "iter" | "iter_mut") {
if let TraitItemKind::Fn(fn_sig, _) = &item.kind {
check_sig(cx, name, fn_sig, item.owner_id.def_id);
}
if let TraitItemKind::Fn(fn_sig, _) = &item.kind
&& matches!(item.ident.name, sym::iter | sym::iter_mut)
{
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>) {
let name = item.ident.name.as_str();
if matches!(name, "iter" | "iter_mut")
if let ImplItemKind::Fn(fn_sig, _) = &item.kind
&& matches!(item.ident.name, sym::iter | sym::iter_mut)
&& !matches!(
cx.tcx.parent_hir_node(item.hir_id()),
Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some()
)
{
if let ImplItemKind::Fn(fn_sig, _) = &item.kind {
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_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() {
let ret_ty = cx
.tcx

View File

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

View File

@ -46,12 +46,12 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !item.span.from_expansion()
&& let ItemKind::Const(_, generics, _) = &item.kind
if let ItemKind::Const(_, generics, _) = &item.kind
// 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
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
&& 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::Array(element_type, cst) = ty.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 {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
if in_external_macro(cx.tcx.sess, item.span) {
return;
}
if let ItemKind::Enum(ref def, _) = item.kind {
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
let ty::Adt(adt, subst) = ty.kind() else {
panic!("already checked whether this is an enum")
};
if adt.variants().len() <= 1 {
return;
}
if let ItemKind::Enum(ref def, _) = item.kind
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Adt(adt, subst) = ty.kind()
&& adt.variants().len() > 1
&& !in_external_macro(cx.tcx.sess, item.span)
{
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
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 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
return;
}
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
if let ExprKind::Call(func, [expr, ..]) = expr.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, ty, future_trait_def_id, &[])
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(self.future_size_threshold)
{
span_lint_and_sugg(
cx,
LARGE_FUTURES,
expr.span,
format!("large future with a size of {} bytes", size.bytes()),
"consider `Box::pin` on it",
format!("Box::pin({})", snippet(cx, expr.span, "..")),
Applicability::Unspecified,
);
}
if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind
&& let ExprKind::Call(func, [arg, ..]) = scrutinee.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
&& !expr.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(arg)
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, ty, future_trait_def_id, &[])
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(self.future_size_threshold)
{
span_lint_and_sugg(
cx,
LARGE_FUTURES,
arg.span,
format!("large future with a size of {} bytes", size.bytes()),
"consider `Box::pin` on it",
format!("Box::pin({})", snippet(cx, arg.span, "..")),
Applicability::Unspecified,
);
}
}
}

View File

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

View File

@ -48,15 +48,11 @@ impl_lint_pass!(LegacyNumericConstants => [LEGACY_NUMERIC_CONSTANTS]);
impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
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,
// so lint on the `use` statement directly.
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 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>) {
let Self { msrv } = self;
if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), expr.span) {
return;
}
let ExprKind::Path(qpath) = expr.kind else {
let ExprKind::Path(qpath) = &expr.kind else {
return;
};
@ -129,10 +120,10 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
)
// `<integer>::xxx_value` check
} else if let QPath::TypeRelative(_, last_segment) = qpath
&& let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id()
&& is_integer_method(cx, def_id)
&& let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id()
&& 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();
@ -145,19 +136,20 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
return;
};
if is_from_proc_macro(cx, expr) {
return;
if self.msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS)
&& !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);

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 {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if item.span.from_expansion() {
return;
}
if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind {
if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind
&& !item.span.from_expansion()
{
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<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Let(lt) = expr.kind
&& has_is_empty(cx, lt.init)
&& match lt.pat.kind {
PatKind::Slice([], None, []) => true,
PatKind::Lit(lit) if is_empty_string(lit) => true,
_ => false,
}
&& !expr.span.from_expansion()
&& has_is_empty(cx, lt.init)
{
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
let actual_span = span_without_enclosing_paren(cx, expr.span);
match cmp {

View File

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

View File

@ -139,9 +139,9 @@ const SYNC_GUARD_PATHS: [&[&str]; 3] = [
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) {
if matches!(local.source, LocalSource::Normal)
&& !in_external_macro(cx.tcx.sess, local.span)
&& let PatKind::Wild = local.pat.kind
&& let Some(init) = local.init
&& !in_external_macro(cx.tcx.sess, local.span)
{
let init_ty = cx.typeck_results().expr_ty(init);
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::source::snippet;
use clippy_utils::is_from_proc_macro;
use rustc_hir::{LetStmt, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
@ -25,19 +25,14 @@ declare_clippy_lint! {
}
declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'_>, local: &LetStmt<'_>) {
if !in_external_macro(cx.tcx.sess, local.span)
&& let Some(ty) = local.ty // Ensure that it has a type defined
impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
if let Some(ty) = local.ty // Ensure that it has a type defined
&& let TyKind::Infer = &ty.kind // that type is '_'
&& 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(
cx,
LET_WITH_TYPE_UNDERSCORE,

View File

@ -4,7 +4,9 @@
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]
#![feature(is_sorted)]
#![feature(iter_intersperse)]
#![feature(iter_partition_in_place)]
#![feature(let_chains)]
#![cfg_attr(bootstrap, feature(lint_reasons))]
#![feature(never_type)]
@ -90,8 +92,10 @@ mod bool_to_int_with_if;
mod booleans;
mod borrow_deref_ref;
mod box_default;
mod byte_char_slices;
mod cargo;
mod casts;
mod cfg_not_test;
mod checked_conversions;
mod cognitive_complexity;
mod collapsible_if;
@ -212,6 +216,7 @@ mod manual_non_exhaustive;
mod manual_range_patterns;
mod manual_rem_euclid;
mod manual_retain;
mod manual_rotate;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
@ -229,6 +234,7 @@ mod mismatching_type_param_order;
mod missing_assert_message;
mod missing_asserts_for_indexing;
mod missing_const_for_fn;
mod missing_const_for_thread_local;
mod missing_doc;
mod missing_enforced_import_rename;
mod missing_fields_in_debug;
@ -275,9 +281,9 @@ mod only_used_in_recursion;
mod operators;
mod option_env_unwrap;
mod option_if_let_else;
mod overflow_check_conditional;
mod panic_in_result_fn;
mod panic_unimplemented;
mod panicking_overflow_checks;
mod partial_pub_fields;
mod partialeq_ne_impl;
mod partialeq_to_none;
@ -318,6 +324,7 @@ mod self_named_constructors;
mod semicolon_block;
mod semicolon_if_nothing_returned;
mod serde_api;
mod set_contains_or_insert;
mod shadow;
mod significant_drop_tightening;
mod single_call_fn;
@ -339,7 +346,6 @@ mod swap_ptr_to_ref;
mod tabs_in_doc_comments;
mod temporary_assignment;
mod tests_outside_test_module;
mod thread_local_initializer_can_be_made_const;
mod to_digit_is_some;
mod to_string_trait_impl;
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_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::<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();
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(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(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names)));
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(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_rotate::ManualRotate));
store.register_late_pass(move |_| {
Box::new(operators::Operators::new(
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,
})
});
store.register_late_pass(move |_| {
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
});
store
.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(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
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_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`
}

View File

@ -233,11 +233,9 @@ impl_lint_pass!(LiteralDigitGrouping => [
impl EarlyLintPass for LiteralDigitGrouping {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Lit(lit) = expr.kind {
if let ExprKind::Lit(lit) = expr.kind
&& !in_external_macro(cx.sess(), expr.span)
{
self.check_lit(cx, lit, expr.span);
}
}
@ -448,11 +446,9 @@ impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]
impl EarlyLintPass for DecimalLiteralRepresentation {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Lit(lit) = expr.kind {
if let ExprKind::Lit(lit) = expr.kind
&& !in_external_macro(cx.sess(), 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 {
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
&& let BinOpKind::Mul = &bin_op.node
&& !in_external_macro(cx.sess(), expr.span)
&& self.msrv.meets(msrvs::MANUAL_BITS)
&& let ctxt = expr.span.ctxt()
&& left_expr.span.ctxt() == ctxt
&& right_expr.span.ctxt() == ctxt

View File

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

View File

@ -49,16 +49,14 @@ declare_clippy_lint! {
impl<'tcx> QuestionMark {
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
&& let Some(init) = local.init
&& local.els.is_none()
&& local.ty.is_none()
&& init.span.eq_ctxt(stmt.span)
&& 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 {
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 {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR)
&& let (target, _) = peel_hir_expr_refs(expr)
&& is_trait_method(cx, target, sym::ToString)
&& let ExprKind::MethodCall(path, receiver, &[], _) = target.kind
let (target, _) = peel_hir_expr_refs(expr);
if let ExprKind::MethodCall(path, receiver, &[], _) = target.kind
&& path.ident.name == sym::to_string
&& let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind
&& let Res::Def(DefKind::Const, receiver_def_id) = path.res
&& 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)
&& let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind()
&& ty.is_str()

View File

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

View File

@ -76,14 +76,11 @@ impl Num {
impl LateLintPass<'_> for ManualRangePatterns {
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
// or at least one range
if let PatKind::Or(pats) = pat.kind
&& (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 max = Num::dummy(i128::MIN);

View File

@ -48,35 +48,30 @@ impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
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
if let ExprKind::Binary(op1, expr1, right) = expr.kind
&& op1.node == BinOpKind::Rem
if let ExprKind::Binary(rem_op, rem_lhs, rem_rhs) = expr.kind
&& 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()
&& expr1.span.ctxt() == ctxt
&& let Some(const1) = check_for_unsigned_int_constant(cx, right)
&& let ExprKind::Binary(op2, left, right) = expr1.kind
&& op2.node == BinOpKind::Add
&& let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right)
&& expr2.span.ctxt() == ctxt
&& let ExprKind::Binary(op3, expr3, right) = expr2.kind
&& op3.node == BinOpKind::Rem
&& let Some(const3) = check_for_unsigned_int_constant(cx, right)
&& rem_lhs.span.ctxt() == ctxt
&& rem_rhs.span.ctxt() == ctxt
&& add_lhs.span.ctxt() == ctxt
&& add_rhs.span.ctxt() == ctxt
&& !in_external_macro(cx.sess(), expr.span)
&& self.msrv.meets(msrvs::REM_EUCLID)
&& (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !in_constant(cx, expr.hir_id))
&& let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
&& 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
&& const1 == const2 && const2 == const3
&& let Some(hir_id) = path_to_local(expr3)
&& let Node::Pat(_) = cx.tcx.hir_node(hir_id)
&& const2 == const3
&& rem2_lhs.span.ctxt() == ctxt
&& rem2_rhs.span.ctxt() == ctxt
{
// Apply only to params or locals with annotated types
match cx.tcx.parent_hir_node(hir_id) {
@ -91,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
};
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(
cx,
MANUAL_REM_EUCLID,

View File

@ -70,9 +70,8 @@ impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
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()
&& 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)
&& 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 {
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 !in_constant(cx, expr.hir_id)
&& let ExprKind::Binary(ref op, left, right) = expr.kind
if let ExprKind::Binary(ref op, left, right) = expr.kind
&& BinOpKind::Mul == op.node
&& !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 ctxt = expr.span.ctxt();

View File

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

View File

@ -253,14 +253,11 @@ fn lint_map_unit_fn(
impl<'tcx> LateLintPass<'tcx> for MapUnit {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
if stmt.span.from_expansion() {
return;
}
if let hir::StmtKind::Semi(expr) = stmt.kind {
if let Some(arglists) = method_chain_args(expr, &["map"]) {
lint_map_unit_fn(cx, stmt, expr, arglists[0]);
}
if let hir::StmtKind::Semi(expr) = stmt.kind
&& !stmt.span.from_expansion()
&& 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 {
#[expect(clippy::too_many_lines)]
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) {
return;
@ -1037,7 +1038,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
return;
}
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);
@ -1084,6 +1085,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
} 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);
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 let Some(else_expr) = if_let.if_else {
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);
}
} else if !from_expansion {
redundant_pattern_match::check(cx, expr);
} else {
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;
pub(super) fn check<'tcx>(
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
@ -27,10 +27,89 @@ pub(super) fn check<'tcx>(
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 {
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);
diag.span_label(s, "temporary lives until here");
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 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> {
seen_types: FxHashSet<Ty<'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);
for arm in arms {
helper.visit_expr(arm.body);
helper.visit_expr(arm);
}
helper.found_sig_drop_spans
}

View File

@ -167,14 +167,12 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
} else {
edits.extend(addr_of_edits);
}
edits.push((
name_span,
String::from(match name {
"map" => "inspect",
"map_err" => "inspect_err",
_ => return,
}),
));
let edit = match name {
"map" => "inspect",
"map_err" => "inspect_err",
_ => return,
};
edits.push((name_span, edit.to_string()));
edits.push((
final_expr
.span
@ -187,9 +185,15 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
} else {
Applicability::MachineApplicable
};
span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| {
diag.multipart_suggestion("try", edits, app);
});
span_lint_and_then(
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! {
/// ### What it does
/// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
/// `_.or_else(|x| Err(y))`.
/// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))`
/// or `_.or_else(|x| Err(y))`.
///
/// ### Why is this bad?
/// Readability, this can be written more concisely as
/// `_.map(|x| y)` or `_.map_err(|x| y)`.
/// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`.
///
/// ### Example
/// ```no_run
@ -4121,7 +4120,7 @@ declare_clippy_lint! {
/// ```no_run
/// let x = Some(0).inspect(|x| println!("{x}"));
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.81.0"]
pub MANUAL_INSPECT,
complexity,
"use of `map` returning the original item"

View File

@ -1,13 +1,15 @@
use super::implicit_clone::is_clone_like;
use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{
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::{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_hir::def::{DefKind, Res};
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) {
return;
}
if check_string_from_utf8(cx, expr, receiver) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver);
}
} else {
@ -240,6 +245,65 @@ fn check_into_iter_call_arg(
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
/// call of a `to_owned`-like function is unnecessary.
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::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_lint::{LateContext, Lint};
use rustc_middle::ty;
@ -61,7 +61,7 @@ pub(super) fn check(
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;
}

View File

@ -5,7 +5,7 @@ use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use std::cmp::Ordering;
use std::cmp::Ordering::{Equal, Greater, Less};
declare_clippy_lint! {
/// ### What it does
@ -36,26 +36,21 @@ declare_lint_pass!(MinMaxPass => [MIN_MAX]);
impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
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((inner_max, inner_c, ie)) = min_max(cx, oe) {
if outer_max == inner_max {
return;
}
match (
outer_max,
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,
MIN_MAX,
expr.span,
"this `min`/`max` combination leads to constant result",
);
},
}
}
if let Some((outer_max, outer_c, oe)) = min_max(cx, expr)
&& let Some((inner_max, inner_c, ie)) = min_max(cx, oe)
&& outer_max != inner_max
&& let Some(ord) = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c)
&& matches!(
(outer_max, ord),
(MinMax::Max, Equal | Greater) | (MinMax::Min, Equal | Less)
)
{
span_lint(
cx,
MIN_MAX,
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::sugg::Sugg;
use clippy_utils::{
any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, is_lint_allowed, iter_input_pats,
last_path_segment, SpanlessEq,
fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats, last_path_segment,
SpanlessEq,
};
use rustc_errors::Applicability;
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<'_>) {
if in_external_macro(cx.sess(), expr.span)
|| 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;
}

View File

@ -1,6 +1,6 @@
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::{is_in_cfg_test, is_in_test_function};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
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
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;
}

View File

@ -8,9 +8,11 @@ use rustc_hir::intravisit::FnKind;
use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
declare_clippy_lint! {
/// ### What it does
@ -115,7 +117,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
.iter()
.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;
}
},
@ -127,6 +132,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
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);
// 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 {
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"]
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
pub MISSING_CONST_FOR_THREAD_LOCAL,
perf,
"suggest using `const` in `thread_local!` macro"
}
pub struct ThreadLocalInitializerCanBeMadeConst {
pub struct MissingConstForThreadLocal {
msrv: Msrv,
}
impl ThreadLocalInitializerCanBeMadeConst {
impl MissingConstForThreadLocal {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]);
impl_lint_pass!(MissingConstForThreadLocal => [MISSING_CONST_FOR_THREAD_LOCAL]);
#[inline]
fn is_thread_local_initializer(
@ -102,7 +102,7 @@ fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id
false
}
impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
local_defid: rustc_span::def_id::LocalDefId,
) {
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)
// Some implementations of `thread_local!` include an initializer fn.
// 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(
cx,
THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
MISSING_CONST_FOR_THREAD_LOCAL,
unpeeled.span,
"initializer for `thread_local` value can be made `const`",
"replace with",

View File

@ -27,7 +27,7 @@ declare_clippy_lint! {
/// Check if a `&mut` function argument is actually used mutably.
///
/// 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?
/// 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;
}
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 is_async = match kind {
FnKind::ItemFn(.., header) => {
@ -262,9 +266,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
.iter()
.filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id))
{
let show_semver_warning =
self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id);
let mut is_cfged = None;
for input in unused {
// If the argument is never used mutably, we emit the warning.
@ -284,7 +285,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),),
Applicability::Unspecified,
);
if show_semver_warning {
if cx.effective_visibilities.is_exported(*fn_def_id) {
diag.warn("changing this function will impact semver compatibility");
}
if *is_cfged {

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt;
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_hir::def::{DefKind, Res};
use rustc_hir::{
@ -185,7 +187,7 @@ impl NoEffect {
&& has_no_effect(cx, init)
&& let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind
&& 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() {
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<'_>) {
if let StmtKind::Semi(expr) = stmt.kind
&& !in_external_macro(cx.sess(), stmt.span)
&& let ctxt = stmt.span.ctxt()
&& expr.span.ctxt() == ctxt
&& let Some(reduced) = reduce_expression(cx, expr)
&& !in_external_macro(cx.sess(), stmt.span)
&& reduced.iter().all(|e| e.span.ctxt() == ctxt)
{
if let ExprKind::Index(..) = &expr.kind {
if is_inside_always_const_context(cx.tcx, expr.hir_id) {
return;
}
let snippet =
if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
format!("assert!({}.len() > {});", &arr, &func)

View File

@ -57,7 +57,6 @@ pub(crate) fn check<'tcx>(
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;
/// if (x | 1 > 3) { }
/// ```
///
/// Use instead:
///
/// ```no_run
/// # let x = 1;
/// if (x >= 2) { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub INEFFECTIVE_BIT_MASK,
correctness,
@ -265,6 +272,13 @@ declare_clippy_lint! {
/// # let x = 1;
/// if x & 0b1111 == 0 { }
/// ```
///
/// Use instead:
///
/// ```no_run
/// # let x: i32 = 1;
/// if x.trailing_zeros() > 4 { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub VERBOSE_BIT_MASK,
pedantic,
@ -560,74 +574,128 @@ declare_clippy_lint! {
/// implement equality for a type involving floats).
///
/// ### Why is this bad?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
/// Floating point calculations are usually imprecise, so asking if two values are *exactly*
/// equal is asking for trouble because arriving at the same logical result via different
/// routes (e.g. calculation versus constant) may yield different values.
///
/// ### Example
/// ```no_run
/// let x = 1.2331f64;
/// let y = 1.2332f64;
///
/// if y == 1.23f64 { }
/// if y != x {} // where both are floats
/// ```no_run
/// 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
/// # let x = 1.2331f64;
/// # let y = 1.2332f64;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (y - 1.23f64).abs() < error_margin { }
/// if (y - x).abs() > error_margin { }
/// let a: f64 = 1000.1;
/// let b: f64 = 0.2;
/// let x = a + b;
/// let y = 1000.3; // Expected value.
///
/// 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"]
pub FLOAT_CMP,
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! {
/// ### What it does
/// Checks for (in-)equality comparisons on floating-point
/// value and constant, except in functions called `*eq*` (which probably
/// Checks for (in-)equality comparisons on constant floating-point
/// values (apart from zero), except in functions called `*eq*` (which probably
/// implement equality for a type involving floats).
///
/// ### Why restrict this?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
/// Floating point calculations are usually imprecise, so asking if two values are *exactly*
/// equal is asking for trouble because arriving at the same logical result via different
/// routes (e.g. calculation versus constant) may yield different values.
///
/// ### 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
/// # let x: f64 = 1.0;
/// # const ONE: f64 = 1.00;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (x - ONE).abs() < error_margin { }
/// let a: f64 = 1000.1;
/// let b: f64 = 0.2;
/// let x = a + b;
/// const Y: f64 = 1000.3; // Expected value.
///
/// 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"]
pub FLOAT_CMP_CONST,
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! {
/// ### 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.
///
/// ### Why is this bad?
@ -646,7 +714,7 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"]
pub MODULO_ONE,
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! {

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_else", "clippy::map_unwrap_or"),
("clippy::option_unwrap_used", "clippy::unwrap_used"),
("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"),
("clippy::ref_in_deref", "clippy::needless_borrow"),
("clippy::result_expect_used", "clippy::expect_used"),
("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
("clippy::result_unwrap_used", "clippy::unwrap_used"),
("clippy::single_char_push_str", "clippy::single_char_add_str"),
("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::unwrap_or_else_default", "clippy::unwrap_or_default"),
("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,
};
use core::ops::ControlFlow;
use rustc_ast::NestedMetaItem;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
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,
StmtKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_session::declare_lint_pass;
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::fmt::Display;
@ -80,6 +81,9 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
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,
"using a return statement like `return expr;` where an expression would suffice"
}
@ -91,6 +95,9 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// 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
/// ```rust,ignore
/// 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));
if borrows {
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);
},
@ -415,10 +448,6 @@ fn emit_return_lint(
replacement: &RetReplacement<'_>,
at: HirId,
) {
if ret_span.from_expansion() {
return;
}
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,

View File

@ -12,7 +12,7 @@ use std::collections::{BTreeMap, BTreeSet};
declare_clippy_lint! {
/// ### What it does
/// 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?
/// 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 collapsible_calls;
pub mod compiler_lint_functions;
pub mod interning_defined_symbol;
pub mod invalid_paths;
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::is_test_module_or_function;
use clippy_utils::is_in_test;
use clippy_utils::source::{snippet, snippet_with_applicability};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@ -100,7 +100,6 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct WildcardImports {
warn_on_all: bool,
test_modules_deep: u32,
allowed_segments: FxHashSet<String>,
}
@ -108,7 +107,6 @@ impl WildcardImports {
pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
Self {
warn_on_all,
test_modules_deep: 0,
allowed_segments: allowed_wildcard_imports,
}
}
@ -122,15 +120,12 @@ impl LateLintPass<'_> for WildcardImports {
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);
if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) {
return;
}
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)
&& !used_imports.is_empty() // Already handled by `unused_imports`
&& !used_imports.contains(&kw::Underscore)
@ -180,20 +175,14 @@ impl LateLintPass<'_> for WildcardImports {
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 {
fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
item.span.from_expansion()
|| is_prelude_import(segments)
|| (is_super_only_import(segments) && self.test_modules_deep > 0)
|| 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::is_in_test;
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::{is_in_cfg_test, is_in_test_function};
use rustc_ast::token::LitKind;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
@ -297,8 +297,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
.as_ref()
.map_or(false, |crate_name| crate_name == "build_script_build");
let allowed_in_tests = self.allow_print_in_tests
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id));
let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id);
match diag_name {
sym::print_macro | sym::println_macro if !allowed_in_tests => {
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::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{
self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, Destination, Expr,
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item,
ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment,
PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstContext,
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef,
TyKind, UnOp,
};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext};
@ -210,7 +211,10 @@ pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
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
///
@ -223,6 +227,24 @@ pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
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`
/// 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 {
@ -1904,8 +1926,18 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool
false
}
pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
any_parent_has_attr(tcx, node, sym::automatically_derived)
/// Checks if the given HIR node is inside an `impl` block with the `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.
@ -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
/// 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> {
@ -2594,16 +2637,6 @@ pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
.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
/// 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 SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
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
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

View File

@ -96,11 +96,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'
return false;
}
for (predicate, _span) in cx
.tcx
.explicit_item_super_predicates(def_id)
.iter_identity_copied()
{
for (predicate, _span) in cx.tcx.explicit_item_super_predicates(def_id).iter_identity_copied() {
match predicate.kind().skip_binder() {
// For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through
// 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(...)`.
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) {
cx.tcx
.inherent_impls(ty_did)
.into_iter()
.flatten()
.map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
.next()
.flatten()
cx.tcx.inherent_impls(ty_did).into_iter().flatten().find_map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
} else {
None
}

View File

@ -16,6 +16,7 @@ clap = { version = "4.4", features = ["derive", "env"] }
crossbeam-channel = "0.5.6"
diff = "0.1.13"
flate2 = "1.0"
itertools = "0.12"
rayon = "1.5.1"
serde = { version = "1.0", features = ["derive"] }
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:
```
cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml
cargo lintcheck
```
or
```
cargo lintcheck
cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml
```
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
```
> Note: Lintcheck isn't sandboxed. Only use it to check crates that you trust or
> sandbox it manually.
### Configuring the Crate Sources
@ -65,17 +67,11 @@ sources.
#### Command Line Options (optional)
```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
possible to only check a crate for certain lint groups. If no options are
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.
possible to enable or disable features.
### Fix mode
You can run `cargo lintcheck --fix` which will run Clippy with `--fix` and

View File

@ -1,38 +1,38 @@
[crates]
# some of these are from cargotest
cargo = {name = "cargo", versions = ['0.64.0']}
iron = {name = "iron", versions = ['0.6.1']}
ripgrep = {name = "ripgrep", versions = ['12.1.1']}
xsv = {name = "xsv", versions = ['0.13.0']}
cargo = {name = "cargo", version = '0.64.0'}
iron = {name = "iron", version = '0.6.1'}
ripgrep = {name = "ripgrep", version = '12.1.1'}
xsv = {name = "xsv", version = '0.13.0'}
# commented out because of 173K clippy::match_same_arms msgs in language_type.rs
#tokei = { name = "tokei", versions = ['12.0.4']}
rayon = {name = "rayon", versions = ['1.5.0']}
serde = {name = "serde", versions = ['1.0.118']}
#tokei = { name = "tokei", version = '12.0.4'}
rayon = {name = "rayon", version = '1.5.0'}
serde = {name = "serde", version = '1.0.118'}
# 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"}
libc = {name = "libc", versions = ['0.2.81']}
log = {name = "log", versions = ['0.4.11']}
proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']}
quote = {name = "quote", versions = ['1.0.7']}
rand = {name = "rand", versions = ['0.7.3']}
rand_core = {name = "rand_core", versions = ['0.6.0']}
regex = {name = "regex", versions = ['1.3.2']}
syn = {name = "syn", versions = ['1.0.54']}
unicode-xid = {name = "unicode-xid", versions = ['0.2.1']}
libc = {name = "libc", version = '0.2.81'}
log = {name = "log", version = '0.4.11'}
proc-macro2 = {name = "proc-macro2", version = '1.0.24'}
quote = {name = "quote", version = '1.0.7'}
rand = {name = "rand", version = '0.7.3'}
rand_core = {name = "rand_core", version = '0.6.0'}
regex = {name = "regex", version = '1.3.2'}
syn = {name = "syn", version = '1.0.54'}
unicode-xid = {name = "unicode-xid", version = '0.2.1'}
# some more of dtolnays crates
anyhow = {name = "anyhow", versions = ['1.0.38']}
async-trait = {name = "async-trait", versions = ['0.1.42']}
cxx = {name = "cxx", versions = ['1.0.32']}
ryu = {name = "ryu", versions = ['1.0.5']}
serde_yaml = {name = "serde_yaml", versions = ['0.8.17']}
thiserror = {name = "thiserror", versions = ['1.0.24']}
anyhow = {name = "anyhow", version = '1.0.38'}
async-trait = {name = "async-trait", version = '0.1.42'}
cxx = {name = "cxx", version = '1.0.32'}
ryu = {name = "ryu", version = '1.0.5'}
serde_yaml = {name = "serde_yaml", version = '0.8.17'}
thiserror = {name = "thiserror", version = '1.0.24'}
# some embark crates, there are other interesting crates but
# 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"}
rpmalloc = {name = "rpmalloc", versions = ['0.2.0']}
tame-oidc = {name = "tame-oidc", versions = ['0.1.0']}
rpmalloc = {name = "rpmalloc", version = '0.2.0'}
tame-oidc = {name = "tame-oidc", version = '0.1.0'}
[recursive]
ignore = [

View File

@ -36,6 +36,10 @@ pub(crate) struct LintcheckConfig {
/// 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)]
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
#[clap(long, short, default_value = "text")]
pub format: OutputFormat,

View File

@ -11,8 +11,6 @@ use std::{env, mem};
fn run_clippy(addr: &str) -> Option<i32> {
let driver_info = DriverInfo {
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());

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::hash::Hash;
use std::path::Path;
use itertools::EitherOrBoth;
use serde::{Deserialize, Serialize};
use crate::ClippyWarning;
/// Creates the log file output for [`crate::config::OutputFormat::Json`]
pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String {
serde_json::to_string(&clippy_warnings).unwrap()
#[derive(Deserialize, Serialize)]
struct LintJson {
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()));
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 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]) {
fn print_warnings(title: &str, warnings: &[LintJson]) {
if warnings.is_empty() {
return;
}
@ -39,31 +52,20 @@ fn print_warnings(title: &str, warnings: &[&ClippyWarning]) {
println!("### {title}");
println!("```");
for warning in warnings {
print!("{}", warning.diag);
print!("{}", warning.rendered);
}
println!("```");
}
fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) {
fn render(warnings: &[&ClippyWarning]) -> String {
let mut rendered = String::new();
for warning in warnings {
write!(&mut rendered, "{}", warning.diag).unwrap();
}
rendered
}
fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
if changed.is_empty() {
return;
}
println!("### Changed");
println!("```diff");
for &(old, new) in changed {
let old_rendered = render(old);
let new_rendered = render(new);
for change in diff::lines(&old_rendered, &new_rendered) {
for (old, new) in changed {
for change in diff::lines(&old.rendered, &new.rendered) {
use diff::Result::{Both, Left, Right};
match change {
@ -86,26 +88,19 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) {
let old_warnings = load_warnings(old_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 removed = Vec::new();
let mut changed = Vec::new();
for (key, new) in &new_map {
if let Some(old) = old_map.get(key) {
if old != new {
changed.push((old.as_slice(), new.as_slice()));
}
} else {
added.extend(new);
}
}
for (key, old) in &old_map {
if !new_map.contains_key(key) {
removed.extend(old);
for change in itertools::merge_join_by(old_warnings, new_warnings, |old, new| old.key().cmp(&new.key())) {
match change {
EitherOrBoth::Both(old, new) => {
if old.rendered != new.rendered {
changed.push((old, new));
}
},
EitherOrBoth::Left(old) => removed.push(old),
EitherOrBoth::Right(new) => added.push(new),
}
}

View File

@ -5,6 +5,7 @@
// When a new lint is introduced, we can search the results for new warnings and check for false
// positives.
#![feature(iter_collect_into)]
#![warn(
trivial_casts,
trivial_numeric_casts,
@ -12,84 +13,38 @@
unused_lifetimes,
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 driver;
mod input;
mod json;
mod output;
mod popular_crates;
mod recursive;
use crate::config::{Commands, LintcheckConfig, OutputFormat};
use crate::recursive::LintcheckServer;
use std::collections::{HashMap, HashSet};
use std::env::consts::EXE_SUFFIX;
use std::fmt::{self, Display, Write as _};
use std::hash::Hash;
use std::io::{self, ErrorKind};
use std::io::{self};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use std::{env, fs, thread};
use std::{env, fs};
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan};
use cargo_metadata::Message;
use input::{read_crates, CrateSource};
use output::{ClippyCheckOutput, ClippyWarning, RustcIce};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use walkdir::{DirEntry, WalkDir};
const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
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
#[derive(Debug)]
struct Crate {
@ -100,248 +55,6 @@ struct Crate {
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 {
/// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
/// issued
@ -352,7 +65,7 @@ impl Crate {
target_dir_index: &AtomicUsize,
total_crates_to_lint: usize,
config: &LintcheckConfig,
lint_filter: &[String],
lint_levels_args: &[String],
server: &Option<LintcheckServer>,
) -> Vec<ClippyCheckOutput> {
// advance the atomic index by one
@ -398,16 +111,9 @@ impl Crate {
for opt in options {
clippy_args.push(opt);
}
} else {
clippy_args.extend(["-Wclippy::pedantic", "-Wclippy::cargo"]);
}
if lint_filter.is_empty() {
clippy_args.push("--cap-lints=warn");
} else {
clippy_args.push("--cap-lints=allow");
clippy_args.extend(lint_filter.iter().map(String::as_str));
}
clippy_args.extend(lint_levels_args.iter().map(String::as_str));
let mut cmd = Command::new("cargo");
cmd.arg(if config.fix { "fix" } else { "check" })
@ -479,7 +185,7 @@ impl Crate {
// get all clippy warnings and ICEs
let mut entries: Vec<ClippyCheckOutput> = Message::parse_stream(stdout.as_bytes())
.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,
})
.map(ClippyCheckOutput::ClippyWarning)
@ -509,96 +215,6 @@ fn build_clippy() -> String {
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() {
// We're being executed as a `RUSTC_WRAPPER` as part of `--recursive`
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 counter = AtomicUsize::new(1);
let lint_filter: Vec<String> = config
.lint_filter
.iter()
.map(|filter| {
let mut filter = filter.clone();
filter.insert_str(0, "--force-warn=");
filter
})
.collect();
let mut lint_level_args: Vec<String> = vec![];
if config.lint_filter.is_empty() {
lint_level_args.push("--cap-lints=warn".to_string());
// Set allow-by-default to warn
if config.warn_all {
[
"clippy::cargo",
"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
.into_iter()
@ -698,7 +338,7 @@ fn lintcheck(config: LintcheckConfig) {
&counter,
crates.len(),
&config,
&lint_filter,
&lint_level_args,
&server,
)
})
@ -727,7 +367,9 @@ fn lintcheck(config: LintcheckConfig) {
}
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 => {
if !raw_ices.is_empty() {
for ice in raw_ices {
@ -736,7 +378,7 @@ fn lintcheck(config: LintcheckConfig) {
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();
}
/// 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
#[must_use]
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();
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)?;

View File

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

View File

@ -1,3 +1,4 @@
[toolchain]
channel = "nightly-2024-06-27"
channel = "nightly-2024-07-11"
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`
--> 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 | | lint.primary_message(msg);
LL | | });
| |______^
LL | cx.span_lint(lint, span, |lint| {
| ^^^^^^^^^
|
= 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`
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
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 | | lint.primary_message(msg);
LL | | });
| |______^
LL | tcx.node_span_lint(lint, hir_id, span, |lint| {
| ^^^^^^^^^^^^^^
|
= 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