mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-23 05:03:21 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
1ced73e66a
204 changed files with 4209 additions and 2537 deletions
4
.github/workflows/lintcheck.yml
vendored
4
.github/workflows/lintcheck.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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})")
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
80
clippy_lints/src/byte_char_slices.rs
Normal file
80
clippy_lints/src/byte_char_slices.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
60
clippy_lints/src/cfg_not_test.rs
Normal file
60
clippy_lints/src/cfg_not_test.rs
Normal 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),
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(_))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 target’s endianness rather than little endian.
|
||||
/// To ensure use of big-endian or the target’s 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 target’s endianness rather than big endian.
|
||||
/// To ensure use of little-endian or the target’s endianness rather than big-endian.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
|
|
|
@ -181,6 +181,9 @@ fn convert_to_from(
|
|||
let from = snippet_opt(cx, self_ty.span)?;
|
||||
let into = snippet_opt(cx, target_ty.span)?;
|
||||
|
||||
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
|
||||
.then_some(String::from("Self"))
|
||||
.unwrap_or_default();
|
||||
let mut suggestions = vec![
|
||||
// impl Into<T> for U -> impl From<T> for U
|
||||
// ~~~~ ~~~~
|
||||
|
@ -197,13 +200,10 @@ fn convert_to_from(
|
|||
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
|
||||
// ~~~~ ~~~~
|
||||
(self_ident.span, format!("val: {from}")),
|
||||
];
|
||||
|
||||
if let FnRetTy::Return(_) = sig.decl.output {
|
||||
// fn into(self) -> T -> fn into(self) -> Self
|
||||
// ~ ~~~~
|
||||
suggestions.push((sig.decl.output.span(), String::from("Self")));
|
||||
}
|
||||
(sig.decl.output.span(), return_type),
|
||||
];
|
||||
|
||||
let mut finder = SelfFinder {
|
||||
cx,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
#![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)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
|
@ -90,8 +91,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 +215,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 +233,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 +280,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 +323,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 +345,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 +644,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 +790,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 +1026,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 +1156,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 +1173,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`
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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, ..) => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
117
clippy_lints/src/manual_rotate.rs
Normal file
117
clippy_lints/src/manual_rotate.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::iter;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
|
@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
|||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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`");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
86
clippy_lints/src/panicking_overflow_checks.rs
Normal file
86
clippy_lints/src/panicking_overflow_checks.rs
Normal 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 (<.kind, >.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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
125
clippy_lints/src/set_contains_or_insert.rs
Normal file
125
clippy_lints/src/set_contains_or_insert.rs
Normal 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(())
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#![feature(f16)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(let_chains)]
|
||||
#![cfg_attr(bootstrap, feature(lint_reasons))]
|
||||
#![feature(never_type)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(assert_matches)]
|
||||
|
@ -102,10 +101,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 +210,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 +226,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 +1925,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 +2503,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 +2636,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.
|
||||
///
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
288
lintcheck/src/input.rs
Normal file
288
lintcheck/src/input.rs
Normal 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"
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
235
lintcheck/src/output.rs
Normal file
235
lintcheck/src/output.rs
Normal 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");
|
||||
});
|
||||
}
|
|
@ -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)?;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue