mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-10 07:04:18 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
81fe8dc084
210 changed files with 4057 additions and 2370 deletions
8
.github/workflows/remark.yml
vendored
8
.github/workflows/remark.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install remark
|
||||
run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm
|
||||
|
@ -29,19 +29,19 @@ jobs:
|
|||
- name: Install mdbook
|
||||
run: |
|
||||
mkdir mdbook
|
||||
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
|
||||
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.34/mdbook-v0.4.34-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
|
||||
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||
|
||||
# Run
|
||||
- name: Check *.md files
|
||||
run: git ls-files -z '*.md' | xargs -0 -n 1 -I {} ./node_modules/.bin/remark {} -u lint -f > /dev/null
|
||||
run: ./node_modules/.bin/remark -u lint -f .
|
||||
|
||||
- name: Linkcheck book
|
||||
run: |
|
||||
rustup toolchain install nightly --component rust-docs
|
||||
curl https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh -o linkcheck.sh
|
||||
sh linkcheck.sh clippy --path ./book
|
||||
|
||||
|
||||
- name: Build mdbook
|
||||
run: mdbook build book
|
||||
|
||||
|
|
|
@ -5171,6 +5171,7 @@ Released 2018-09-13
|
|||
[`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
|
||||
[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
|
||||
[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
|
||||
[`needless_borrows_for_generic_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args
|
||||
[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
|
||||
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
|
||||
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
|
||||
|
@ -5245,6 +5246,7 @@ Released 2018-09-13
|
|||
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
|
||||
[`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none
|
||||
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
|
||||
[`path_ends_with_ext`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_ends_with_ext
|
||||
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
|
||||
[`permissions_set_readonly_false`]: https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
|
||||
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
|
||||
|
@ -5279,6 +5281,7 @@ Released 2018-09-13
|
|||
[`readonly_write_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#readonly_write_lock
|
||||
[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
|
||||
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
|
||||
[`redundant_as_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_as_str
|
||||
[`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block
|
||||
[`redundant_at_rest_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_at_rest_pattern
|
||||
[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
|
||||
|
@ -5437,6 +5440,7 @@ Released 2018-09-13
|
|||
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
|
||||
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
|
||||
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap
|
||||
[`unnecessary_map_on_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_map_on_constructor
|
||||
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
|
||||
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
|
||||
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
|
||||
|
@ -5574,5 +5578,6 @@ Released 2018-09-13
|
|||
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
|
||||
[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments
|
||||
[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates
|
||||
[`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles
|
||||
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
||||
<!-- end autogenerated links to configuration documentation -->
|
||||
|
|
|
@ -38,7 +38,6 @@ itertools = "0.10.1"
|
|||
|
||||
# UI test dependencies
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
derive-new = "0.5"
|
||||
if_chain = "1.0"
|
||||
quote = "1.0"
|
||||
serde = { version = "1.0.125", features = ["derive"] }
|
||||
|
|
|
@ -703,7 +703,7 @@ Minimum chars an ident can have, anything below or equal to this will be linted.
|
|||
## `accept-comment-above-statement`
|
||||
Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
|
||||
|
||||
**Default Value:** `false` (`bool`)
|
||||
**Default Value:** `true` (`bool`)
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
|
@ -713,7 +713,7 @@ Whether to accept a safety comment to be placed above the statement containing t
|
|||
## `accept-comment-above-attributes`
|
||||
Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
|
||||
|
||||
**Default Value:** `false` (`bool`)
|
||||
**Default Value:** `true` (`bool`)
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
|
@ -751,6 +751,16 @@ Which crates to allow absolute paths from
|
|||
* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths)
|
||||
|
||||
|
||||
## `allowed-dotfiles`
|
||||
Additional dotfiles (files or directories starting with a dot) to allow
|
||||
|
||||
**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`)
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`path_ends_with_ext`](https://rust-lang.github.io/rust-clippy/master/index.html#path_ends_with_ext)
|
||||
|
||||
|
||||
## `enforce-iter-loop-reborrow`
|
||||
#### Example
|
||||
```
|
||||
|
|
|
@ -616,7 +616,7 @@ fn check_should_panic_reason(cx: &LateContext<'_>, attr: &Attribute) {
|
|||
attr.span,
|
||||
"#[should_panic] attribute without a reason",
|
||||
"consider specifying the expected panic",
|
||||
r#"#[should_panic(expected = /* panic message */)]"#.into(),
|
||||
"#[should_panic(expected = /* panic message */)]".into(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub(super) fn check(
|
|||
// The suggestion is to use a function call, so if the original expression
|
||||
// has parens on the outside, they are no longer needed.
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let opt = snippet_opt(cx, cast_op.span);
|
||||
let opt = snippet_opt(cx, cast_op.span.source_callsite());
|
||||
let sugg = opt.as_ref().map_or_else(
|
||||
|| {
|
||||
applicability = Applicability::HasPlaceholders;
|
||||
|
|
|
@ -44,7 +44,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
|||
.unwrap_or(u64::max_value())
|
||||
.min(apply_reductions(cx, nbits, left, signed)),
|
||||
BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
|
||||
.saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
|
||||
.saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).unwrap_or_default())),
|
||||
_ => nbits,
|
||||
},
|
||||
ExprKind::MethodCall(method, left, [right], _) => {
|
||||
|
|
|
@ -20,6 +20,7 @@ mod ptr_as_ptr;
|
|||
mod ptr_cast_constness;
|
||||
mod unnecessary_cast;
|
||||
mod utils;
|
||||
mod zero_ptr;
|
||||
|
||||
use clippy_utils::is_hir_ty_cfg_dependant;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
|
@ -665,6 +666,29 @@ declare_clippy_lint! {
|
|||
"casting a known floating-point NaN into an integer"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Catch casts from `0` to some pointer type
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This generally means `null` and is better expressed as
|
||||
/// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let a = 0 as *const u32;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let a = std::ptr::null::<u32>();
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub ZERO_PTR,
|
||||
style,
|
||||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
@ -699,6 +723,7 @@ impl_lint_pass!(Casts => [
|
|||
CAST_SLICE_FROM_RAW_PARTS,
|
||||
AS_PTR_CAST_MUT,
|
||||
CAST_NAN_TO_INT,
|
||||
ZERO_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
@ -729,6 +754,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
zero_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
|
||||
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
|
||||
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span);
|
||||
|
|
39
clippy_lints/src/casts/zero_ptr.rs
Normal file
39
clippy_lints/src/casts/zero_ptr.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{in_constant, is_integer_literal, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::ZERO_PTR;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
|
||||
if let TyKind::Ptr(ref mut_ty) = to.kind
|
||||
&& is_integer_literal(from, 0)
|
||||
&& !in_constant(cx, from.hir_id)
|
||||
&& let Some(std_or_core) = std_or_core(cx)
|
||||
{
|
||||
let (msg, sugg_fn) = match mut_ty.mutbl {
|
||||
Mutability::Mut => ("`0 as *mut _` detected", "ptr::null_mut"),
|
||||
Mutability::Not => ("`0 as *const _` detected", "ptr::null"),
|
||||
};
|
||||
|
||||
let sugg = if let TyKind::Infer = mut_ty.ty.kind {
|
||||
format!("{std_or_core}::{sugg_fn}()")
|
||||
} else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
|
||||
format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ZERO_PTR,
|
||||
expr.span,
|
||||
msg,
|
||||
"try",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -97,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::casts::PTR_AS_PTR_INFO,
|
||||
crate::casts::PTR_CAST_CONSTNESS_INFO,
|
||||
crate::casts::UNNECESSARY_CAST_INFO,
|
||||
crate::casts::ZERO_PTR_INFO,
|
||||
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
|
||||
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
|
||||
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
|
||||
|
@ -399,9 +400,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::OR_FUN_CALL_INFO,
|
||||
crate::methods::OR_THEN_UNWRAP_INFO,
|
||||
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
|
||||
crate::methods::PATH_ENDS_WITH_EXT_INFO,
|
||||
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
|
||||
crate::methods::READONLY_WRITE_LOCK_INFO,
|
||||
crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
|
||||
crate::methods::REDUNDANT_AS_STR_INFO,
|
||||
crate::methods::REPEAT_ONCE_INFO,
|
||||
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
|
||||
crate::methods::SEARCH_IS_SOME_INFO,
|
||||
|
@ -441,7 +444,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
|
||||
crate::misc::TOPLEVEL_REF_ARG_INFO,
|
||||
crate::misc::USED_UNDERSCORE_BINDING_INFO,
|
||||
crate::misc::ZERO_PTR_INFO,
|
||||
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
|
||||
crate::misc_early::DOUBLE_NEG_INFO,
|
||||
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
|
||||
|
@ -479,6 +481,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::needless_bool::NEEDLESS_BOOL_INFO,
|
||||
crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO,
|
||||
crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO,
|
||||
crate::needless_borrows_for_generic_args::NEEDLESS_BORROWS_FOR_GENERIC_ARGS_INFO,
|
||||
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
|
||||
crate::needless_else::NEEDLESS_ELSE_INFO,
|
||||
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
|
||||
|
@ -671,6 +674,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
|
||||
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
|
||||
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
|
||||
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
|
||||
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
|
||||
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
|
||||
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_hir::{self as hir, HirId, Item, ItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_hir::{HirId, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, FieldDef, GenericArg, List};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
|
@ -52,7 +52,10 @@ declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION])
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) {
|
||||
if !item.span.from_expansion()
|
||||
&& is_union_with_two_non_zst_fields(cx, item)
|
||||
&& !has_c_repr_attr(cx, item.hir_id())
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
DEFAULT_UNION_REPRESENTATION,
|
||||
|
@ -73,18 +76,17 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
|
|||
/// if there is only one field left after ignoring ZST fields then the offset
|
||||
/// of that field does not matter either.)
|
||||
fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if let ItemKind::Union(data, _) = &item.kind {
|
||||
data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2
|
||||
if let ItemKind::Union(..) = &item.kind
|
||||
&& let ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
|
||||
{
|
||||
adt_def.all_fields().filter(|f| !is_zst(cx, f, args)).count() >= 2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool {
|
||||
if hir_ty.span.from_expansion() {
|
||||
return false;
|
||||
}
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
fn is_zst<'tcx>(cx: &LateContext<'tcx>, field: &FieldDef, args: &'tcx List<GenericArg<'tcx>>) -> bool {
|
||||
let ty = field.ty(cx.tcx, args);
|
||||
if let Ok(layout) = cx.layout_of(ty) {
|
||||
layout.is_zst()
|
||||
} else {
|
||||
|
|
|
@ -1,41 +1,24 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{implements_trait, is_copy, peel_mid_ty_refs};
|
||||
use clippy_utils::ty::{implements_trait, peel_mid_ty_refs};
|
||||
use clippy_utils::{
|
||||
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
|
||||
};
|
||||
|
||||
use hir::def::DefKind;
|
||||
use hir::MatchSource;
|
||||
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::intravisit::{walk_ty, Visitor};
|
||||
use rustc_hir::{
|
||||
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind,
|
||||
Path, QPath, TyKind, UnOp,
|
||||
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
|
||||
Pat, PatKind, Path, QPath, TyKind, UnOp,
|
||||
};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::{Rvalue, StatementKind};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::{
|
||||
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamEnv, ParamTy, ProjectionPredicate, Ty,
|
||||
TyCtxt, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, TypeVisitableExt, TypeckResults};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{Obligation, ObligationCause};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -183,24 +166,6 @@ pub struct Dereferencing<'tcx> {
|
|||
///
|
||||
/// e.g. `m!(x) | Foo::Bar(ref x)`
|
||||
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
|
||||
|
||||
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
|
||||
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
|
||||
/// be moved.
|
||||
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
|
||||
// `IntoIterator` for arrays requires Rust 1.53.
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl<'tcx> Dereferencing<'tcx> {
|
||||
#[must_use]
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
..Dereferencing::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -355,52 +320,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
));
|
||||
},
|
||||
(Some(use_cx), RefOp::AddrOf(mutability)) => {
|
||||
let defined_ty = use_cx.node.defined_ty(cx);
|
||||
|
||||
// Check needless_borrow for generic arguments.
|
||||
if !use_cx.is_ty_unified
|
||||
&& let Some(DefinedTy::Mir(ty)) = defined_ty
|
||||
&& let ty::Param(ty) = *ty.value.skip_binder().kind()
|
||||
&& let Some((hir_id, fn_id, i)) = match use_cx.node {
|
||||
ExprUseNode::MethodArg(_, _, 0) => None,
|
||||
ExprUseNode::MethodArg(hir_id, None, i) => {
|
||||
typeck.type_dependent_def_id(hir_id).map(|id| (hir_id, id, i))
|
||||
},
|
||||
ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i)
|
||||
if !path_has_args(p) => match typeck.qpath_res(p, hir_id) {
|
||||
Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => {
|
||||
Some((hir_id, id, i))
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
} && let count = needless_borrow_generic_arg_count(
|
||||
cx,
|
||||
&mut self.possible_borrowers,
|
||||
fn_id,
|
||||
typeck.node_args(hir_id),
|
||||
i,
|
||||
ty,
|
||||
expr,
|
||||
&self.msrv,
|
||||
) && count != 0
|
||||
{
|
||||
self.state = Some((
|
||||
State::DerefedBorrow(DerefedBorrow {
|
||||
count: count - 1,
|
||||
msg: "the borrowed expression implements the required traits",
|
||||
stability: TyCoercionStability::None,
|
||||
for_field_access: None,
|
||||
}),
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
|
||||
},
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the number of times the borrow is auto-derefed.
|
||||
let mut iter = use_cx.adjustments.iter();
|
||||
let mut deref_count = 0usize;
|
||||
|
@ -419,7 +338,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
};
|
||||
};
|
||||
|
||||
let stability = defined_ty.map_or(TyCoercionStability::None, |ty| {
|
||||
let stability = use_cx.node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
|
||||
TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
|
||||
});
|
||||
let can_auto_borrow = match use_cx.node {
|
||||
|
@ -700,12 +619,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||
if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
|
||||
local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
|
||||
}) {
|
||||
self.possible_borrowers.pop();
|
||||
}
|
||||
|
||||
if Some(body.id()) == self.current_body {
|
||||
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
|
||||
let replacements = pat.replacements;
|
||||
|
@ -729,8 +642,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
self.current_body = None;
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn try_parse_ref_op<'tcx>(
|
||||
|
@ -788,13 +699,6 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn path_has_args(p: &QPath<'_>) -> bool {
|
||||
match *p {
|
||||
QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
|
||||
if let Some(parent) = get_parent_expr(cx, e)
|
||||
&& parent.span.ctxt() == e.span.ctxt()
|
||||
|
@ -980,274 +884,6 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
|
|||
v.0
|
||||
}
|
||||
|
||||
/// Checks for the number of borrow expressions which can be removed from the given expression
|
||||
/// where the expression is used as an argument to a function expecting a generic type.
|
||||
///
|
||||
/// The following constraints will be checked:
|
||||
/// * The borrowed expression meets all the generic type's constraints.
|
||||
/// * The generic type appears only once in the functions signature.
|
||||
/// * The borrowed value will not be moved if it is used later in the function.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn needless_borrow_generic_arg_count<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
fn_id: DefId,
|
||||
callee_args: &'tcx List<GenericArg<'tcx>>,
|
||||
arg_index: usize,
|
||||
param_ty: ParamTy,
|
||||
mut expr: &Expr<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) -> usize {
|
||||
let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
|
||||
let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
|
||||
|
||||
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder();
|
||||
let predicates = cx.tcx.param_env(fn_id).caller_bounds();
|
||||
let projection_predicates = predicates
|
||||
.iter()
|
||||
.filter_map(|predicate| {
|
||||
if let ClauseKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
|
||||
Some(projection_predicate)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut trait_with_ref_mut_self_method = false;
|
||||
|
||||
// If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
|
||||
if predicates
|
||||
.iter()
|
||||
.filter_map(|predicate| {
|
||||
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
|
||||
&& trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
|
||||
{
|
||||
Some(trait_predicate.trait_ref.def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.inspect(|trait_def_id| {
|
||||
trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
|
||||
})
|
||||
.all(|trait_def_id| {
|
||||
Some(trait_def_id) == destruct_trait_def_id
|
||||
|| Some(trait_def_id) == sized_trait_def_id
|
||||
|| cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
|
||||
})
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// See:
|
||||
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
|
||||
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
|
||||
if projection_predicates
|
||||
.iter()
|
||||
.any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `args_with_referent_ty` can be constructed outside of `check_referent` because the same
|
||||
// elements are modified each time `check_referent` is called.
|
||||
let mut args_with_referent_ty = callee_args.to_vec();
|
||||
|
||||
let mut check_reference_and_referent = |reference, referent| {
|
||||
let referent_ty = cx.typeck_results().expr_ty(referent);
|
||||
|
||||
if !is_copy(cx, referent_ty)
|
||||
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
|
||||
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
|
||||
if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !replace_types(
|
||||
cx,
|
||||
param_ty,
|
||||
referent_ty,
|
||||
fn_sig,
|
||||
arg_index,
|
||||
&projection_predicates,
|
||||
&mut args_with_referent_ty,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
predicates.iter().all(|predicate| {
|
||||
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
|
||||
&& cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
|
||||
&& let ty::Param(param_ty) = trait_predicate.self_ty().kind()
|
||||
&& let GenericArgKind::Type(ty) = args_with_referent_ty[param_ty.index as usize].unpack()
|
||||
&& ty.is_array()
|
||||
&& !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty);
|
||||
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
|
||||
let infcx = cx.tcx.infer_ctxt().build();
|
||||
infcx.predicate_must_hold_modulo_regions(&obligation)
|
||||
})
|
||||
};
|
||||
|
||||
let mut count = 0;
|
||||
while let ExprKind::AddrOf(_, _, referent) = expr.kind {
|
||||
if !check_reference_and_referent(expr, referent) {
|
||||
break;
|
||||
}
|
||||
expr = referent;
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
|
||||
cx.tcx
|
||||
.associated_items(trait_def_id)
|
||||
.in_definition_order()
|
||||
.any(|assoc_item| {
|
||||
if assoc_item.fn_has_self_parameter {
|
||||
let self_ty = cx
|
||||
.tcx
|
||||
.fn_sig(assoc_item.def_id)
|
||||
.instantiate_identity()
|
||||
.skip_binder()
|
||||
.inputs()[0];
|
||||
matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_mixed_projection_predicate<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
callee_def_id: DefId,
|
||||
projection_predicate: &ProjectionPredicate<'tcx>,
|
||||
) -> bool {
|
||||
let generics = cx.tcx.generics_of(callee_def_id);
|
||||
// The predicate requires the projected type to equal a type parameter from the parent context.
|
||||
if let Some(term_ty) = projection_predicate.term.ty()
|
||||
&& let ty::Param(term_param_ty) = term_ty.kind()
|
||||
&& (term_param_ty.index as usize) < generics.parent_count
|
||||
{
|
||||
// The inner-most self type is a type parameter from the current function.
|
||||
let mut projection_ty = projection_predicate.projection_ty;
|
||||
loop {
|
||||
match projection_ty.self_ty().kind() {
|
||||
ty::Alias(ty::Projection, inner_projection_ty) => {
|
||||
projection_ty = *inner_projection_ty;
|
||||
}
|
||||
ty::Param(param_ty) => {
|
||||
return (param_ty.index as usize) >= generics.parent_count;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn referent_used_exactly_once<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
reference: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
|
||||
&& let Some(local) = expr_local(cx.tcx, reference)
|
||||
&& let [location] = *local_assignments(mir, local).as_slice()
|
||||
&& let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index)
|
||||
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
|
||||
&& !place.is_indirect_first_projection()
|
||||
// Ensure not in a loop (https://github.com/rust-lang/rust-clippy/issues/9710)
|
||||
&& TriColorDepthFirstSearch::new(&mir.basic_blocks).run_from(location.block, &mut CycleDetector).is_none()
|
||||
{
|
||||
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
|
||||
if possible_borrowers
|
||||
.last()
|
||||
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
|
||||
{
|
||||
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
|
||||
}
|
||||
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
|
||||
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
|
||||
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
|
||||
// itself. See the comment in that method for an explanation as to why.
|
||||
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
|
||||
&& used_exactly_once(mir, place.local).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
|
||||
// projected type that is a type parameter. Returns `false` if replacing the types would have an
|
||||
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
|
||||
// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
|
||||
fn replace_types<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
param_ty: ParamTy,
|
||||
new_ty: Ty<'tcx>,
|
||||
fn_sig: FnSig<'tcx>,
|
||||
arg_index: usize,
|
||||
projection_predicates: &[ProjectionPredicate<'tcx>],
|
||||
args: &mut [ty::GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
let mut replaced = BitSet::new_empty(args.len());
|
||||
|
||||
let mut deque = VecDeque::with_capacity(args.len());
|
||||
deque.push_back((param_ty, new_ty));
|
||||
|
||||
while let Some((param_ty, new_ty)) = deque.pop_front() {
|
||||
// If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
|
||||
if !fn_sig
|
||||
.inputs_and_output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
args[param_ty.index as usize] = ty::GenericArg::from(new_ty);
|
||||
|
||||
// The `replaced.insert(...)` check provides some protection against infinite loops.
|
||||
if replaced.insert(param_ty.index) {
|
||||
for projection_predicate in projection_predicates {
|
||||
if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
|
||||
&& let Some(term_ty) = projection_predicate.term.ty()
|
||||
&& let ty::Param(term_param_ty) = term_ty.kind()
|
||||
{
|
||||
let projection = cx.tcx.mk_ty_from_kind(ty::Alias(
|
||||
ty::Projection,
|
||||
projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty),
|
||||
));
|
||||
|
||||
if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
|
||||
&& args[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty)
|
||||
{
|
||||
deque.push_back((*term_param_ty, projected_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
|
||||
if let ty::Adt(adt, _) = *ty.kind() {
|
||||
adt.is_struct() && adt.all_fields().any(|f| f.name == name)
|
||||
|
|
|
@ -167,7 +167,10 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
|
|||
return;
|
||||
}
|
||||
|
||||
let first = &def.variants[0].ident.name.as_str();
|
||||
let first = match def.variants.first() {
|
||||
Some(variant) => variant.ident.name.as_str(),
|
||||
None => return,
|
||||
};
|
||||
let mut pre = camel_case_split(first);
|
||||
let mut post = pre.clone();
|
||||
post.reverse();
|
||||
|
|
|
@ -3,7 +3,6 @@ use clippy_utils::path_res;
|
|||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Visibility;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
@ -42,9 +41,10 @@ impl<'tcx> LateLintPass<'tcx> for ErrorImplError {
|
|||
};
|
||||
|
||||
match item.kind {
|
||||
ItemKind::TyAlias(ty, _) if implements_trait(cx, hir_ty_to_ty(cx.tcx, ty), error_def_id, &[])
|
||||
&& item.ident.name == sym::Error
|
||||
&& is_visible_outside_module(cx, item.owner_id.def_id) =>
|
||||
ItemKind::TyAlias(..) if item.ident.name == sym::Error
|
||||
&& is_visible_outside_module(cx, item.owner_id.def_id)
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& implements_trait(cx, ty, error_def_id, &[]) =>
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
|
|
|
@ -57,54 +57,52 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
} else {
|
||||
None
|
||||
}
|
||||
&& let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root())
|
||||
{
|
||||
find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
|
||||
let calling_macro =
|
||||
// ordering is important here, since `writeln!` uses `write!` internally
|
||||
if is_expn_of(write_call.span, "writeln").is_some() {
|
||||
Some("writeln")
|
||||
} else if is_expn_of(write_call.span, "write").is_some() {
|
||||
Some("write")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prefix = if dest_name == "stderr" {
|
||||
"e"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
// ordering is important here, since `writeln!` uses `write!` internally
|
||||
let calling_macro = if is_expn_of(write_call.span, "writeln").is_some() {
|
||||
Some("writeln")
|
||||
} else if is_expn_of(write_call.span, "write").is_some() {
|
||||
Some("write")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prefix = if dest_name == "stderr" {
|
||||
"e"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
// We need to remove the last trailing newline from the string because the
|
||||
// underlying `fmt::write` function doesn't know whether `println!` or `print!` was
|
||||
// used.
|
||||
let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
|
||||
(
|
||||
format!("{macro_name}!({dest_name}(), ...)"),
|
||||
macro_name.replace("write", "print"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("{dest_name}().write_fmt(...)"),
|
||||
"print".into(),
|
||||
)
|
||||
};
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args_inputs_span(format_args),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&format!("use of `{used}.unwrap()`"),
|
||||
"try",
|
||||
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
|
||||
applicability,
|
||||
);
|
||||
});
|
||||
// We need to remove the last trailing newline from the string because the
|
||||
// underlying `fmt::write` function doesn't know whether `println!` or `print!` was
|
||||
// used.
|
||||
let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
|
||||
(
|
||||
format!("{macro_name}!({dest_name}(), ...)"),
|
||||
macro_name.replace("write", "print"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("{dest_name}().write_fmt(...)"),
|
||||
"print".into(),
|
||||
)
|
||||
};
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args_inputs_span(&format_args),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&format!("use of `{used}.unwrap()`"),
|
||||
"try",
|
||||
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,8 +246,13 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
|
|||
{
|
||||
self.ty_params.remove(&def_id);
|
||||
}
|
||||
} else {
|
||||
// If the bounded type isn't a generic param, but is instead a concrete generic
|
||||
// type, any params we find nested inside of it are being used as concrete types,
|
||||
// and can therefore can be considered used. So, we're fine to walk the left-hand
|
||||
// side of the where bound.
|
||||
walk_ty(self, predicate.bounded_ty);
|
||||
}
|
||||
// Only walk the right-hand side of where bounds
|
||||
for bound in predicate.bounds {
|
||||
walk_param_bound(self, bound);
|
||||
}
|
||||
|
|
|
@ -43,14 +43,10 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return;
|
||||
};
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let call_site = macro_call.span;
|
||||
|
||||
|
@ -91,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
|||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -186,15 +186,10 @@ impl FormatArgs {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return;
|
||||
};
|
||||
if !is_format_macro(cx, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
let name = cx.tcx.item_name(macro_call.def_id);
|
||||
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& is_format_macro(cx, macro_call.def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
|
@ -206,12 +201,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
|||
|
||||
if placeholder.format_trait != FormatTrait::Display
|
||||
|| placeholder.format_options != FormatOptions::default()
|
||||
|| is_aliased(format_args, index)
|
||||
|| is_aliased(&format_args, index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(arg_hir_expr) = arg_expr {
|
||||
let name = cx.tcx.item_name(macro_call.def_id);
|
||||
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
|
||||
check_to_string_in_format_args(cx, name, arg_hir_expr);
|
||||
}
|
||||
|
@ -219,9 +215,9 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
|||
}
|
||||
|
||||
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
|
||||
check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
|
||||
check_uninlined_args(cx, &format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
|
|
|
@ -170,30 +170,29 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
|
|||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, outer_macro.expn)
|
||||
{
|
||||
find_format_args(cx, expr, outer_macro.expn, |format_args| {
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, ConstKind};
|
||||
|
@ -50,12 +49,12 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
|
|||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if_chain! {
|
||||
if !item.span.from_expansion();
|
||||
if let ItemKind::Const(hir_ty, 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.
|
||||
if generics.params.is_empty() && !generics.has_where_clause_predicates;
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
if let ty::Array(element_type, cst) = ty.kind();
|
||||
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
|
||||
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);
|
||||
|
|
|
@ -17,26 +17,20 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
|
||||
/// async fn large_future(_x: [u8; 16 * 1024]) {}
|
||||
///
|
||||
/// async fn big_fut(arg: [u8; 1024]) {}
|
||||
///
|
||||
/// pub async fn test() {
|
||||
/// let fut = big_fut([0u8; 1024]);
|
||||
/// wait(fut).await;
|
||||
/// pub async fn trigger() {
|
||||
/// large_future([0u8; 16 * 1024]).await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `Box::pin` the big future instead.
|
||||
///
|
||||
/// ```rust
|
||||
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
|
||||
/// async fn large_future(_x: [u8; 16 * 1024]) {}
|
||||
///
|
||||
/// async fn big_fut(arg: [u8; 1024]) {}
|
||||
///
|
||||
/// pub async fn test() {
|
||||
/// let fut = Box::pin(big_fut([0u8; 1024]));
|
||||
/// wait(fut).await;
|
||||
/// pub async fn trigger() {
|
||||
/// Box::pin(large_future([0u8; 16 * 1024])).await;
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
|
|
|
@ -424,6 +424,14 @@ fn check_for_is_empty(
|
|||
item_name: Symbol,
|
||||
item_kind: &str,
|
||||
) {
|
||||
// Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
|
||||
// find the correct inherent impls.
|
||||
let impl_ty = if let Some(adt) = cx.tcx.type_of(impl_ty).skip_binder().ty_adt_def() {
|
||||
adt.did()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_empty = Symbol::intern("is_empty");
|
||||
let is_empty = cx
|
||||
.tcx
|
||||
|
|
|
@ -230,6 +230,7 @@ mod mutex_atomic;
|
|||
mod needless_arbitrary_self_type;
|
||||
mod needless_bool;
|
||||
mod needless_borrowed_ref;
|
||||
mod needless_borrows_for_generic_args;
|
||||
mod needless_continue;
|
||||
mod needless_else;
|
||||
mod needless_for_each;
|
||||
|
@ -331,6 +332,7 @@ mod unit_return_expecting_ord;
|
|||
mod unit_types;
|
||||
mod unnamed_address;
|
||||
mod unnecessary_box_returns;
|
||||
mod unnecessary_map_on_constructor;
|
||||
mod unnecessary_owned_empty_strings;
|
||||
mod unnecessary_self_imports;
|
||||
mod unnecessary_struct_initialization;
|
||||
|
@ -610,7 +612,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
.collect(),
|
||||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(utils::format_args_collector::FormatArgsCollector));
|
||||
store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
|
||||
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
|
||||
store.register_late_pass(|_| Box::new(utils::author::Author));
|
||||
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
|
||||
|
@ -637,7 +639,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool));
|
||||
store.register_late_pass(|_| Box::new(needless_bool::BoolComparison));
|
||||
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
|
||||
store.register_late_pass(|_| Box::<misc::LintPass>::default());
|
||||
store.register_late_pass(|_| Box::new(misc::LintPass));
|
||||
store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
|
||||
store.register_late_pass(|_| Box::new(mut_mut::MutMut));
|
||||
store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed));
|
||||
|
@ -663,12 +665,19 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
|
||||
let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const;
|
||||
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
|
||||
let allowed_dotfiles = conf
|
||||
.allowed_dotfiles
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(methods::DEFAULT_ALLOWED_DOTFILES.iter().copied().map(ToOwned::to_owned))
|
||||
.collect::<FxHashSet<_>>();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(methods::Methods::new(
|
||||
avoid_breaking_exported_api,
|
||||
msrv(),
|
||||
allow_expect_in_tests,
|
||||
allow_unwrap_in_tests,
|
||||
allowed_dotfiles.clone(),
|
||||
))
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
|
||||
|
@ -881,7 +890,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
|
||||
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
|
||||
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
|
||||
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
|
||||
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
|
||||
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
|
||||
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
|
||||
let future_size_threshold = conf.future_size_threshold;
|
||||
|
@ -1104,6 +1113,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(|_| Box::<reserve_after_initialization::ReserveAfterInitialization>::default());
|
||||
store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls));
|
||||
store.register_late_pass(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new(
|
||||
msrv(),
|
||||
))
|
||||
});
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ use rustc_ast::ast::{LitIntType, LitKind};
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
|
||||
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
@ -150,7 +149,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
|
|||
if l.pat.hir_id == self.var_id;
|
||||
if let PatKind::Binding(.., ident, _) = l.pat.kind;
|
||||
then {
|
||||
let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty));
|
||||
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
|
||||
|
||||
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
|
||||
InitializeVisitorState::Initialized {
|
||||
|
|
|
@ -36,7 +36,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
|
@ -8,8 +8,7 @@ use clippy_utils::{
|
|||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, Guard, ItemKind, Node, Pat, PatKind, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
|
@ -141,11 +140,15 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
|
|||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(..) => {
|
||||
if let Some(fn_decl) = p_node.fn_decl() {
|
||||
if let FnRetTy::Return(ret_ty) = fn_decl.output {
|
||||
return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
Node::Item(item) => {
|
||||
if let ItemKind::Fn(..) = item.kind {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
|
|
|
@ -2,11 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::path_to_local;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::visitors::{for_each_expr, is_local_used};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_ast::{BorrowKind, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::Span;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
|
@ -34,32 +35,45 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
|
|||
],
|
||||
MatchSource::Normal,
|
||||
) = if_expr.kind
|
||||
&& let Some(binding) = get_pat_binding(cx, scrutinee, outer_arm)
|
||||
{
|
||||
let pat_span = match (arm.pat.kind, binding.byref_ident) {
|
||||
(PatKind::Ref(pat, _), Some(_)) => pat.span,
|
||||
(PatKind::Ref(..), None) | (_, Some(_)) => continue,
|
||||
_ => arm.pat.span,
|
||||
};
|
||||
emit_redundant_guards(
|
||||
cx,
|
||||
outer_arm,
|
||||
if_expr.span,
|
||||
scrutinee,
|
||||
arm.pat.span,
|
||||
pat_span,
|
||||
&binding,
|
||||
arm.guard,
|
||||
);
|
||||
}
|
||||
// `Some(x) if let Some(2) = x`
|
||||
else if let Guard::IfLet(let_expr) = guard {
|
||||
else if let Guard::IfLet(let_expr) = guard
|
||||
&& let Some(binding) = get_pat_binding(cx, let_expr.init, outer_arm)
|
||||
{
|
||||
let pat_span = match (let_expr.pat.kind, binding.byref_ident) {
|
||||
(PatKind::Ref(pat, _), Some(_)) => pat.span,
|
||||
(PatKind::Ref(..), None) | (_, Some(_)) => continue,
|
||||
_ => let_expr.pat.span,
|
||||
};
|
||||
emit_redundant_guards(
|
||||
cx,
|
||||
outer_arm,
|
||||
let_expr.span,
|
||||
let_expr.init,
|
||||
let_expr.pat.span,
|
||||
pat_span,
|
||||
&binding,
|
||||
None,
|
||||
);
|
||||
}
|
||||
// `Some(x) if x == Some(2)`
|
||||
// `Some(x) if Some(2) == x`
|
||||
else if let Guard::If(if_expr) = guard
|
||||
&& let ExprKind::Binary(bin_op, local, pat) = if_expr.kind
|
||||
&& matches!(bin_op.node, BinOpKind::Eq)
|
||||
&& expr_can_be_pat(cx, pat)
|
||||
// Ensure they have the same type. If they don't, we'd need deref coercion which isn't
|
||||
// possible (currently) in a pattern. In some cases, you can use something like
|
||||
// `as_deref` or similar but in general, we shouldn't lint this as it'd create an
|
||||
|
@ -67,43 +81,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
|
|||
//
|
||||
// This isn't necessary in the other two checks, as they must be a pattern already.
|
||||
&& cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat)
|
||||
// Since we want to lint on both `x == Some(2)` and `Some(2) == x`, we might have to "swap"
|
||||
// `local` and `pat`, depending on which side they are.
|
||||
&& let Some((binding, pat)) = get_pat_binding(cx, local, outer_arm)
|
||||
.map(|binding| (binding, pat))
|
||||
.or_else(|| get_pat_binding(cx, pat, outer_arm).map(|binding| (binding, local)))
|
||||
&& expr_can_be_pat(cx, pat)
|
||||
{
|
||||
let pat_span = match (pat.kind, binding.byref_ident) {
|
||||
(ExprKind::AddrOf(BorrowKind::Ref, _, expr), Some(_)) => expr.span,
|
||||
(ExprKind::AddrOf(..), None) | (_, Some(_)) => continue,
|
||||
_ => pat.span,
|
||||
};
|
||||
emit_redundant_guards(
|
||||
cx,
|
||||
outer_arm,
|
||||
if_expr.span,
|
||||
local,
|
||||
pat.span,
|
||||
pat_span,
|
||||
&binding,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> {
|
||||
struct PatBindingInfo {
|
||||
span: Span,
|
||||
byref_ident: Option<Ident>,
|
||||
is_field: bool,
|
||||
}
|
||||
|
||||
fn get_pat_binding<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
guard_expr: &Expr<'_>,
|
||||
outer_arm: &Arm<'tcx>,
|
||||
) -> Option<PatBindingInfo> {
|
||||
if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) {
|
||||
let mut span = None;
|
||||
let mut byref_ident = None;
|
||||
let mut multiple_bindings = false;
|
||||
// `each_binding` gives the `HirId` of the `Pat` itself, not the binding
|
||||
outer_arm.pat.walk(|pat| {
|
||||
if let PatKind::Binding(_, hir_id, _, _) = pat.kind
|
||||
if let PatKind::Binding(bind_annot, hir_id, ident, _) = pat.kind
|
||||
&& hir_id == local
|
||||
&& span.replace(pat.span).is_some()
|
||||
{
|
||||
multiple_bindings = true;
|
||||
return false;
|
||||
if matches!(bind_annot.0, rustc_ast::ByRef::Yes) {
|
||||
let _ = byref_ident.insert(ident);
|
||||
}
|
||||
// the second call of `replace()` returns a `Some(span)`, meaning a multi-binding pattern
|
||||
if span.replace(pat.span).is_some() {
|
||||
multiple_bindings = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
// Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)`
|
||||
if !multiple_bindings {
|
||||
return span.map(|span| {
|
||||
(
|
||||
span,
|
||||
!matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
|
||||
)
|
||||
return span.map(|span| PatBindingInfo {
|
||||
span,
|
||||
byref_ident,
|
||||
is_field: matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -115,14 +154,11 @@ fn emit_redundant_guards<'tcx>(
|
|||
cx: &LateContext<'tcx>,
|
||||
outer_arm: &Arm<'tcx>,
|
||||
guard_span: Span,
|
||||
local: &Expr<'_>,
|
||||
pat_span: Span,
|
||||
pat_binding: &PatBindingInfo,
|
||||
inner_guard: Option<Guard<'_>>,
|
||||
) {
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
@ -131,14 +167,21 @@ fn emit_redundant_guards<'tcx>(
|
|||
"redundant guard",
|
||||
|diag| {
|
||||
let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app);
|
||||
let suggestion_span = match *pat_binding {
|
||||
PatBindingInfo {
|
||||
span,
|
||||
byref_ident: Some(ident),
|
||||
is_field: true,
|
||||
} => (span, format!("{ident}: {binding_replacement}")),
|
||||
PatBindingInfo {
|
||||
span, is_field: true, ..
|
||||
} => (span.shrink_to_hi(), format!(": {binding_replacement}")),
|
||||
PatBindingInfo { span, .. } => (span, binding_replacement.into_owned()),
|
||||
};
|
||||
diag.multipart_suggestion_verbose(
|
||||
"try",
|
||||
vec![
|
||||
if can_use_shorthand {
|
||||
(pat_binding, binding_replacement.into_owned())
|
||||
} else {
|
||||
(pat_binding.shrink_to_hi(), format!(": {binding_replacement}"))
|
||||
},
|
||||
suggestion_span,
|
||||
(
|
||||
guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()),
|
||||
inner_guard.map_or_else(String::new, |guard| {
|
||||
|
|
|
@ -131,13 +131,12 @@ pub(super) fn check<'tcx>(
|
|||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
//Special handling for `format!` as arg_root
|
||||
// Special handling for `format!` as arg_root
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
find_format_args(cx, arg_root, macro_call.expn, |format_args| {
|
||||
let span = format_args_inputs_span(format_args);
|
||||
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn)
|
||||
{
|
||||
let span = format_args_inputs_span(&format_args);
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -148,7 +147,7 @@ pub(super) fn check<'tcx>(
|
|||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::Binder;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
|
@ -36,6 +37,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
|
|||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& match_def_path(cx, def_id, &BOOL_THEN)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
// Count the number of derefs needed to get to the bool because we need those in the suggestion
|
||||
&& let needed_derefs = cx.typeck_results().expr_adjustments(recv)
|
||||
.iter()
|
||||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count()
|
||||
&& let Some(param_snippet) = snippet_opt(cx, param.span)
|
||||
&& let Some(filter) = snippet_opt(cx, recv.span)
|
||||
&& let Some(map) = snippet_opt(cx, then_body.span)
|
||||
|
@ -46,7 +52,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
|
|||
call_span,
|
||||
"usage of `bool::then` in `filter_map`",
|
||||
"use `filter` then `map` instead",
|
||||
format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"),
|
||||
format!(
|
||||
"filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})",
|
||||
derefs="*".repeat(needed_derefs)
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -74,9 +74,11 @@ mod option_map_unwrap_or;
|
|||
mod or_fun_call;
|
||||
mod or_then_unwrap;
|
||||
mod path_buf_push_overwrite;
|
||||
mod path_ends_with_ext;
|
||||
mod range_zip_with_len;
|
||||
mod read_line_without_trim;
|
||||
mod readonly_write_lock;
|
||||
mod redundant_as_str;
|
||||
mod repeat_once;
|
||||
mod search_is_some;
|
||||
mod seek_from_current;
|
||||
|
@ -120,9 +122,10 @@ use clippy_utils::msrvs::{self, Msrv};
|
|||
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
|
||||
use if_chain::if_chain;
|
||||
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, TraitRef, Ty};
|
||||
|
@ -3563,11 +3566,77 @@ declare_clippy_lint! {
|
|||
"calls to `.take()` or `.skip()` that are out of bounds"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension.
|
||||
///
|
||||
/// By default, Clippy has a short list of known filenames that start with a dot
|
||||
/// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default.
|
||||
/// The `allowed-dotfiles` configuration can be used to allow additional
|
||||
/// file extensions that Clippy should not lint.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument
|
||||
/// to the last **component** of the path and checks if it matches exactly.
|
||||
///
|
||||
/// ### Known issues
|
||||
/// File extensions are often at most three characters long, so this only lints in those cases
|
||||
/// in an attempt to avoid false positives.
|
||||
/// Any extension names longer than that are assumed to likely be real path components and are
|
||||
/// therefore ignored.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::path::Path;
|
||||
/// fn is_markdown(path: &Path) -> bool {
|
||||
/// path.ends_with(".md")
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::path::Path;
|
||||
/// fn is_markdown(path: &Path) -> bool {
|
||||
/// path.extension().is_some_and(|ext| ext == "md")
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
pub PATH_ENDS_WITH_EXT,
|
||||
suspicious,
|
||||
"attempting to compare file extensions using `Path::ends_with`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `as_str()` on a `String`` chained with a method available on the `String` itself.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # #![allow(unused)]
|
||||
/// let owned_string = "This is a string".to_owned();
|
||||
/// owned_string.as_str().as_bytes();
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # #![allow(unused)]
|
||||
/// let owned_string = "This is a string".to_owned();
|
||||
/// owned_string.as_bytes();
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
pub REDUNDANT_AS_STR,
|
||||
complexity,
|
||||
"`as_str` used to call a method on `str` that is also available on `String`"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
allow_expect_in_tests: bool,
|
||||
allow_unwrap_in_tests: bool,
|
||||
allowed_dotfiles: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl Methods {
|
||||
|
@ -3577,12 +3646,14 @@ impl Methods {
|
|||
msrv: Msrv,
|
||||
allow_expect_in_tests: bool,
|
||||
allow_unwrap_in_tests: bool,
|
||||
allowed_dotfiles: FxHashSet<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
avoid_breaking_exported_api,
|
||||
msrv,
|
||||
allow_expect_in_tests,
|
||||
allow_unwrap_in_tests,
|
||||
allowed_dotfiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3703,6 +3774,8 @@ impl_lint_pass!(Methods => [
|
|||
FILTER_MAP_BOOL_THEN,
|
||||
READONLY_WRITE_LOCK,
|
||||
ITER_OUT_OF_BOUNDS,
|
||||
PATH_ENDS_WITH_EXT,
|
||||
REDUNDANT_AS_STR,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
@ -3852,18 +3925,20 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
if_chain! {
|
||||
if let TraitItemKind::Fn(ref sig, _) = item.kind;
|
||||
if sig.decl.implicit_self.has_implicit_self();
|
||||
if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
|
||||
|
||||
if let Some(first_arg_hir_ty) = sig.decl.inputs.first();
|
||||
if let Some(&first_arg_ty) = cx.tcx.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.inputs()
|
||||
.skip_binder()
|
||||
.first();
|
||||
then {
|
||||
let first_arg_span = first_arg_ty.span;
|
||||
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
|
||||
wrong_self_convention::check(
|
||||
cx,
|
||||
item.ident.name.as_str(),
|
||||
self_ty,
|
||||
first_arg_ty,
|
||||
first_arg_span,
|
||||
first_arg_hir_ty.span,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
@ -3929,6 +4004,7 @@ impl Methods {
|
|||
("as_deref" | "as_deref_mut", []) => {
|
||||
needless_option_as_deref::check(cx, expr, recv, name);
|
||||
},
|
||||
("as_bytes" | "is_empty", []) => if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { redundant_as_str::check(cx, expr, recv, as_str_span, span); },
|
||||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
|
||||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
|
@ -3978,6 +4054,7 @@ impl Methods {
|
|||
if let ExprKind::MethodCall(.., span) = expr.kind {
|
||||
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
|
||||
}
|
||||
path_ends_with_ext::check(cx, recv, arg, expr, &self.msrv, &self.allowed_dotfiles);
|
||||
},
|
||||
("expect", [_]) => {
|
||||
match method_call(recv) {
|
||||
|
|
53
clippy_lints/src/methods/path_ends_with_ext.rs
Normal file
53
clippy_lints/src/methods/path_ends_with_ext.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use super::PATH_ENDS_WITH_EXT;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_ast::{LitKind, StrStyle};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use std::fmt::Write;
|
||||
|
||||
pub const DEFAULT_ALLOWED_DOTFILES: &[&str] = &[
|
||||
"git", "svn", "gem", "npm", "vim", "env", "rnd", "ssh", "vnc", "smb", "nvm", "bin",
|
||||
];
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
recv: &Expr<'_>,
|
||||
path: &Expr<'_>,
|
||||
expr: &Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
allowed_dotfiles: &FxHashSet<String>,
|
||||
) {
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::Path)
|
||||
&& !path.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = path.kind
|
||||
&& let LitKind::Str(path, StrStyle::Cooked) = lit.node
|
||||
&& let Some(path) = path.as_str().strip_prefix('.')
|
||||
&& (1..=3).contains(&path.len())
|
||||
&& !allowed_dotfiles.contains(path)
|
||||
&& path.chars().all(char::is_alphanumeric)
|
||||
{
|
||||
let mut sugg = snippet(cx, recv.span, "..").into_owned();
|
||||
if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
|
||||
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
|
||||
} else {
|
||||
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PATH_ENDS_WITH_EXT,
|
||||
expr.span,
|
||||
"this looks like a failed attempt at checking for the file extension",
|
||||
"try",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect
|
||||
);
|
||||
}
|
||||
}
|
34
clippy_lints/src/methods/redundant_as_str.rs
Normal file
34
clippy_lints/src/methods/redundant_as_str.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use super::REDUNDANT_AS_STR;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::query::Key;
|
||||
use rustc_span::Span;
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
_expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
as_str_span: Span,
|
||||
other_method_span: Span,
|
||||
) {
|
||||
if cx
|
||||
.tcx
|
||||
.lang_items()
|
||||
.string()
|
||||
.is_some_and(|id| Some(id) == cx.typeck_results().expr_ty(recv).ty_adt_id())
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REDUNDANT_AS_STR,
|
||||
as_str_span.to(other_method_span),
|
||||
"this `as_str` is redundant and can be removed as the method immediately following exists on `String` too",
|
||||
"try",
|
||||
snippet_with_applicability(cx, other_method_span, "..", &mut applicability).into_owned(),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,8 @@ use if_chain::if_chain;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
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,24 +1,22 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_opt, snippet_with_context};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, Stmt,
|
||||
StmtKind, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::hygiene::DesugaringKind;
|
||||
use rustc_span::source_map::{ExpnKind, Span};
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
get_parent_expr, in_constant, is_integer_literal, is_lint_allowed, is_no_std_crate, iter_input_pats,
|
||||
any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, is_lint_allowed, iter_input_pats,
|
||||
last_path_segment, SpanlessEq,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
use crate::ref_patterns::REF_PATTERNS;
|
||||
|
||||
|
@ -56,6 +54,7 @@ declare_clippy_lint! {
|
|||
style,
|
||||
"an entire binding declared as `ref`, in a function argument or a `let` statement"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the use of bindings with a single leading
|
||||
|
@ -103,51 +102,13 @@ declare_clippy_lint! {
|
|||
"using a short circuit boolean condition as a statement"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Catch casts from `0` to some pointer type
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This generally means `null` and is better expressed as
|
||||
/// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let a = 0 as *const u32;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let a = std::ptr::null::<u32>();
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub ZERO_PTR,
|
||||
style,
|
||||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
pub struct LintPass {
|
||||
std_or_core: &'static str,
|
||||
}
|
||||
impl Default for LintPass {
|
||||
fn default() -> Self {
|
||||
Self { std_or_core: "std" }
|
||||
}
|
||||
}
|
||||
impl_lint_pass!(LintPass => [
|
||||
declare_lint_pass!(LintPass => [
|
||||
TOPLEVEL_REF_ARG,
|
||||
USED_UNDERSCORE_BINDING,
|
||||
SHORT_CIRCUIT_STATEMENT,
|
||||
ZERO_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LintPass {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
if is_no_std_crate(cx) {
|
||||
self.std_or_core = "core";
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
|
@ -253,50 +214,56 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
|
|||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Cast(e, ty) = expr.kind {
|
||||
self.check_cast(cx, expr.span, e, ty);
|
||||
if in_external_macro(cx.sess(), expr.span)
|
||||
|| expr.span.desugaring_kind().is_some()
|
||||
|| any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
|
||||
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring
|
||||
return;
|
||||
}
|
||||
let sym;
|
||||
let binding = match expr.kind {
|
||||
ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
|
||||
let binding = last_path_segment(qpath).ident.as_str();
|
||||
if binding.starts_with('_') &&
|
||||
!binding.starts_with("__") &&
|
||||
binding != "_result" && // FIXME: #944
|
||||
is_used(cx, expr) &&
|
||||
// don't lint if the declaration is in a macro
|
||||
non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
|
||||
let (definition_hir_id, ident) = match expr.kind {
|
||||
ExprKind::Path(ref qpath) => {
|
||||
if let QPath::Resolved(None, path) = qpath
|
||||
&& let Res::Local(id) = path.res
|
||||
&& is_used(cx, expr)
|
||||
{
|
||||
Some(binding)
|
||||
(id, last_path_segment(qpath).ident)
|
||||
} else {
|
||||
None
|
||||
return;
|
||||
}
|
||||
},
|
||||
ExprKind::Field(_, ident) => {
|
||||
sym = ident.name;
|
||||
let name = sym.as_str();
|
||||
if name.starts_with('_') && !name.starts_with("__") {
|
||||
Some(name)
|
||||
ExprKind::Field(recv, ident) => {
|
||||
if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
|
||||
&& let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
|
||||
&& let Some(local_did) = field.did.as_local()
|
||||
&& let Some(hir_id) = cx.tcx.opt_local_def_id_to_hir_id(local_did)
|
||||
&& !cx.tcx.type_of(field.did).skip_binder().is_phantom_data()
|
||||
{
|
||||
(hir_id, ident)
|
||||
} else {
|
||||
None
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
_ => return,
|
||||
};
|
||||
if let Some(binding) = binding {
|
||||
span_lint(
|
||||
|
||||
let name = ident.name.as_str();
|
||||
if name.starts_with('_')
|
||||
&& !name.starts_with("__")
|
||||
&& let definition_span = cx.tcx.hir().span(definition_hir_id)
|
||||
&& !definition_span.from_expansion()
|
||||
&& !fulfill_or_allowed(cx, USED_UNDERSCORE_BINDING, [expr.hir_id, definition_hir_id])
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
USED_UNDERSCORE_BINDING,
|
||||
expr.span,
|
||||
&format!(
|
||||
"used binding `{binding}` which is prefixed with an underscore. A leading \
|
||||
"used binding `{name}` which is prefixed with an underscore. A leading \
|
||||
underscore signals that a binding will not be used"
|
||||
),
|
||||
|diag| {
|
||||
diag.span_note(definition_span, format!("`{name}` is defined here"));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -311,50 +278,3 @@ fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
_ => is_used(cx, parent),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests whether an expression is in a macro expansion (e.g., something
|
||||
/// generated by `#[derive(...)]` or the like).
|
||||
fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
|
||||
use rustc_span::hygiene::MacroKind;
|
||||
if expr.span.from_expansion() {
|
||||
let data = expr.span.ctxt().outer_expn_data();
|
||||
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests whether `res` is a variable defined outside a macro.
|
||||
fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
|
||||
if let def::Res::Local(id) = res {
|
||||
!cx.tcx.hir().span(id).from_expansion()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl LintPass {
|
||||
fn check_cast(&self, cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
|
||||
if_chain! {
|
||||
if let TyKind::Ptr(ref mut_ty) = ty.kind;
|
||||
if is_integer_literal(e, 0);
|
||||
if !in_constant(cx, e.hir_id);
|
||||
then {
|
||||
let (msg, sugg_fn) = match mut_ty.mutbl {
|
||||
Mutability::Mut => ("`0 as *mut _` detected", "ptr::null_mut"),
|
||||
Mutability::Not => ("`0 as *const _` detected", "ptr::null"),
|
||||
};
|
||||
|
||||
let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
|
||||
(format!("{}::{sugg_fn}()", self.std_or_core), Applicability::MachineApplicable)
|
||||
} else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
|
||||
(format!("{}::{sugg_fn}::<{mut_ty_snip}>()", self.std_or_core), Applicability::MachineApplicable)
|
||||
} else {
|
||||
// `MaybeIncorrect` as type inference may not work with the suggested code
|
||||
(format!("{}::{sugg_fn}()", self.std_or_core), Applicability::MaybeIncorrect)
|
||||
};
|
||||
span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use rustc_hir as hir;
|
|||
use rustc_hir::def_id::CRATE_DEF_ID;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, Constness, FnDecl, GenericParamKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
@ -124,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
|
|||
FnKind::Method(_, sig, ..) => {
|
||||
if trait_ref_of_method(cx, def_id).is_some()
|
||||
|| already_const(sig.header)
|
||||
|| method_accepts_droppable(cx, sig.decl.inputs)
|
||||
|| method_accepts_droppable(cx, def_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -165,12 +164,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
|
|||
|
||||
/// Returns true if any of the method parameters is a type that implements `Drop`. The method
|
||||
/// can't be made const then, because `drop` can't be const-evaluated.
|
||||
fn method_accepts_droppable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
|
||||
fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
|
||||
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
|
||||
|
||||
// If any of the params are droppable, return true
|
||||
param_tys.iter().any(|hir_ty| {
|
||||
let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
has_drop(cx, ty_ty)
|
||||
})
|
||||
sig.inputs().iter().any(|&ty| has_drop(cx, ty))
|
||||
}
|
||||
|
||||
// We don't have to lint on something that's already `const`
|
||||
|
|
|
@ -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
|
||||
|
|
410
clippy_lints/src/needless_borrows_for_generic_args.rs
Normal file
410
clippy_lints/src/needless_borrows_for_generic_args.rs
Normal file
|
@ -0,0 +1,410 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{expr_use_ctxt, peel_n_hir_expr_refs, DefinedTy, ExprUseNode};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::{Body, Expr, ExprKind, Mutability, Path, QPath};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::{Rvalue, StatementKind};
|
||||
use rustc_middle::ty::{
|
||||
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamTy, ProjectionPredicate, Ty,
|
||||
};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{Obligation, ObligationCause};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for borrow operations (`&`) that used as a generic argument to a
|
||||
/// function when the borrowed value could be used.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Suggests that the receiver of the expression borrows
|
||||
/// the expression.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// The lint cannot tell when the implementation of a trait
|
||||
/// for `&T` and `T` do different things. Removing a borrow
|
||||
/// in such a case can change the semantics of the code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// fn f(_: impl AsRef<str>) {}
|
||||
///
|
||||
/// let x = "foo";
|
||||
/// f(&x);
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn f(_: impl AsRef<str>) {}
|
||||
///
|
||||
/// let x = "foo";
|
||||
/// f(x);
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub NEEDLESS_BORROWS_FOR_GENERIC_ARGS,
|
||||
style,
|
||||
"taking a reference that is going to be automatically dereferenced"
|
||||
}
|
||||
|
||||
pub struct NeedlessBorrowsForGenericArgs<'tcx> {
|
||||
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
|
||||
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
|
||||
/// be moved.
|
||||
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
|
||||
// `IntoIterator` for arrays requires Rust 1.53.
|
||||
msrv: Msrv,
|
||||
}
|
||||
impl_lint_pass!(NeedlessBorrowsForGenericArgs<'_> => [NEEDLESS_BORROWS_FOR_GENERIC_ARGS]);
|
||||
|
||||
impl NeedlessBorrowsForGenericArgs<'_> {
|
||||
#[must_use]
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
possible_borrowers: Vec::new(),
|
||||
msrv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if matches!(expr.kind, ExprKind::AddrOf(..))
|
||||
&& !expr.span.from_expansion()
|
||||
&& let Some(use_cx) = expr_use_ctxt(cx, expr)
|
||||
&& !use_cx.is_ty_unified
|
||||
&& let Some(DefinedTy::Mir(ty)) = use_cx.node.defined_ty(cx)
|
||||
&& let ty::Param(ty) = *ty.value.skip_binder().kind()
|
||||
&& let Some((hir_id, fn_id, i)) = match use_cx.node {
|
||||
ExprUseNode::MethodArg(_, _, 0) => None,
|
||||
ExprUseNode::MethodArg(hir_id, None, i) => {
|
||||
cx.typeck_results().type_dependent_def_id(hir_id).map(|id| (hir_id, id, i))
|
||||
},
|
||||
ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i)
|
||||
if !path_has_args(p) => match cx.typeck_results().qpath_res(p, hir_id) {
|
||||
Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => {
|
||||
Some((hir_id, id, i))
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
} && let count = needless_borrow_count(
|
||||
cx,
|
||||
&mut self.possible_borrowers,
|
||||
fn_id,
|
||||
cx.typeck_results().node_args(hir_id),
|
||||
i,
|
||||
ty,
|
||||
expr,
|
||||
&self.msrv,
|
||||
) && count != 0
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NEEDLESS_BORROWS_FOR_GENERIC_ARGS,
|
||||
expr.span,
|
||||
"the borrowed expression implements the required traits",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip_span = peel_n_hir_expr_refs(expr, count).0.span;
|
||||
let snip = snippet_with_context(cx, snip_span, expr.span.ctxt(), "..", &mut app).0;
|
||||
diag.span_suggestion(expr.span, "change this to", snip.into_owned(), app);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||
if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
|
||||
local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
|
||||
}) {
|
||||
self.possible_borrowers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn path_has_args(p: &QPath<'_>) -> bool {
|
||||
match *p {
|
||||
QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the number of borrow expressions which can be removed from the given expression
|
||||
/// where the expression is used as an argument to a function expecting a generic type.
|
||||
///
|
||||
/// The following constraints will be checked:
|
||||
/// * The borrowed expression meets all the generic type's constraints.
|
||||
/// * The generic type appears only once in the functions signature.
|
||||
/// * The borrowed value will not be moved if it is used later in the function.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn needless_borrow_count<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
fn_id: DefId,
|
||||
callee_args: &'tcx List<GenericArg<'tcx>>,
|
||||
arg_index: usize,
|
||||
param_ty: ParamTy,
|
||||
mut expr: &Expr<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) -> usize {
|
||||
let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
|
||||
let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
|
||||
|
||||
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder();
|
||||
let predicates = cx.tcx.param_env(fn_id).caller_bounds();
|
||||
let projection_predicates = predicates
|
||||
.iter()
|
||||
.filter_map(|predicate| {
|
||||
if let ClauseKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
|
||||
Some(projection_predicate)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut trait_with_ref_mut_self_method = false;
|
||||
|
||||
// If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
|
||||
if predicates
|
||||
.iter()
|
||||
.filter_map(|predicate| {
|
||||
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
|
||||
&& trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
|
||||
{
|
||||
Some(trait_predicate.trait_ref.def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.inspect(|trait_def_id| {
|
||||
trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
|
||||
})
|
||||
.all(|trait_def_id| {
|
||||
Some(trait_def_id) == destruct_trait_def_id
|
||||
|| Some(trait_def_id) == sized_trait_def_id
|
||||
|| cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
|
||||
})
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// See:
|
||||
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
|
||||
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
|
||||
if projection_predicates
|
||||
.iter()
|
||||
.any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `args_with_referent_ty` can be constructed outside of `check_referent` because the same
|
||||
// elements are modified each time `check_referent` is called.
|
||||
let mut args_with_referent_ty = callee_args.to_vec();
|
||||
|
||||
let mut check_reference_and_referent = |reference, referent| {
|
||||
let referent_ty = cx.typeck_results().expr_ty(referent);
|
||||
|
||||
if !is_copy(cx, referent_ty)
|
||||
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
|
||||
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
|
||||
if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !replace_types(
|
||||
cx,
|
||||
param_ty,
|
||||
referent_ty,
|
||||
fn_sig,
|
||||
arg_index,
|
||||
&projection_predicates,
|
||||
&mut args_with_referent_ty,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
predicates.iter().all(|predicate| {
|
||||
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
|
||||
&& cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
|
||||
&& let ty::Param(param_ty) = trait_predicate.self_ty().kind()
|
||||
&& let GenericArgKind::Type(ty) = args_with_referent_ty[param_ty.index as usize].unpack()
|
||||
&& ty.is_array()
|
||||
&& !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty);
|
||||
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
|
||||
let infcx = cx.tcx.infer_ctxt().build();
|
||||
infcx.predicate_must_hold_modulo_regions(&obligation)
|
||||
})
|
||||
};
|
||||
|
||||
let mut count = 0;
|
||||
while let ExprKind::AddrOf(_, _, referent) = expr.kind {
|
||||
if !check_reference_and_referent(expr, referent) {
|
||||
break;
|
||||
}
|
||||
expr = referent;
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
|
||||
cx.tcx
|
||||
.associated_items(trait_def_id)
|
||||
.in_definition_order()
|
||||
.any(|assoc_item| {
|
||||
if assoc_item.fn_has_self_parameter {
|
||||
let self_ty = cx
|
||||
.tcx
|
||||
.fn_sig(assoc_item.def_id)
|
||||
.instantiate_identity()
|
||||
.skip_binder()
|
||||
.inputs()[0];
|
||||
matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_mixed_projection_predicate<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
callee_def_id: DefId,
|
||||
projection_predicate: &ProjectionPredicate<'tcx>,
|
||||
) -> bool {
|
||||
let generics = cx.tcx.generics_of(callee_def_id);
|
||||
// The predicate requires the projected type to equal a type parameter from the parent context.
|
||||
if let Some(term_ty) = projection_predicate.term.ty()
|
||||
&& let ty::Param(term_param_ty) = term_ty.kind()
|
||||
&& (term_param_ty.index as usize) < generics.parent_count
|
||||
{
|
||||
// The inner-most self type is a type parameter from the current function.
|
||||
let mut projection_ty = projection_predicate.projection_ty;
|
||||
loop {
|
||||
match projection_ty.self_ty().kind() {
|
||||
ty::Alias(ty::Projection, inner_projection_ty) => {
|
||||
projection_ty = *inner_projection_ty;
|
||||
}
|
||||
ty::Param(param_ty) => {
|
||||
return (param_ty.index as usize) >= generics.parent_count;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn referent_used_exactly_once<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
reference: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
|
||||
&& let Some(local) = expr_local(cx.tcx, reference)
|
||||
&& let [location] = *local_assignments(mir, local).as_slice()
|
||||
&& let block_data = &mir.basic_blocks[location.block]
|
||||
&& let Some(statement) = block_data.statements.get(location.statement_index)
|
||||
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
|
||||
&& !place.is_indirect_first_projection()
|
||||
{
|
||||
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
|
||||
if possible_borrowers
|
||||
.last()
|
||||
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
|
||||
{
|
||||
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
|
||||
}
|
||||
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
|
||||
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
|
||||
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
|
||||
// itself. See the comment in that method for an explanation as to why.
|
||||
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
|
||||
&& used_exactly_once(mir, place.local).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
|
||||
// projected type that is a type parameter. Returns `false` if replacing the types would have an
|
||||
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
|
||||
// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
|
||||
fn replace_types<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
param_ty: ParamTy,
|
||||
new_ty: Ty<'tcx>,
|
||||
fn_sig: FnSig<'tcx>,
|
||||
arg_index: usize,
|
||||
projection_predicates: &[ProjectionPredicate<'tcx>],
|
||||
args: &mut [ty::GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
let mut replaced = BitSet::new_empty(args.len());
|
||||
|
||||
let mut deque = VecDeque::with_capacity(args.len());
|
||||
deque.push_back((param_ty, new_ty));
|
||||
|
||||
while let Some((param_ty, new_ty)) = deque.pop_front() {
|
||||
// If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
|
||||
if !fn_sig
|
||||
.inputs_and_output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
args[param_ty.index as usize] = ty::GenericArg::from(new_ty);
|
||||
|
||||
// The `replaced.insert(...)` check provides some protection against infinite loops.
|
||||
if replaced.insert(param_ty.index) {
|
||||
for projection_predicate in projection_predicates {
|
||||
if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
|
||||
&& let Some(term_ty) = projection_predicate.term.ty()
|
||||
&& let ty::Param(term_param_ty) = term_ty.kind()
|
||||
{
|
||||
let projection = cx.tcx.mk_ty_from_kind(ty::Alias(
|
||||
ty::Projection,
|
||||
projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty),
|
||||
));
|
||||
|
||||
if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
|
||||
&& args[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty)
|
||||
{
|
||||
deque.push_back((*term_param_ty, projected_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use super::needless_pass_by_value::requires_exact_signature;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::for_each_expr_with_closures;
|
||||
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -9,7 +10,7 @@ use rustc_hir::{
|
|||
Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath,
|
||||
};
|
||||
use rustc_hir_typeck::expr_use_visitor as euv;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::associated_body;
|
||||
use rustc_middle::hir::nested_filter::OnlyBodies;
|
||||
|
@ -21,6 +22,8 @@ use rustc_span::symbol::kw;
|
|||
use rustc_span::Span;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use core::ops::ControlFlow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Check if a `&mut` function argument is actually used mutably.
|
||||
|
@ -95,6 +98,30 @@ fn should_skip<'tcx>(
|
|||
is_from_proc_macro(cx, &input)
|
||||
}
|
||||
|
||||
fn check_closures<'tcx>(
|
||||
ctx: &mut MutablyUsedVariablesCtxt<'tcx>,
|
||||
cx: &LateContext<'tcx>,
|
||||
infcx: &InferCtxt<'tcx>,
|
||||
checked_closures: &mut FxHashSet<LocalDefId>,
|
||||
closures: FxHashSet<LocalDefId>,
|
||||
) {
|
||||
let hir = cx.tcx.hir();
|
||||
for closure in closures {
|
||||
if !checked_closures.insert(closure) {
|
||||
continue;
|
||||
}
|
||||
ctx.prev_bind = None;
|
||||
ctx.prev_move_to_closure.clear();
|
||||
if let Some(body) = hir
|
||||
.find_by_def_id(closure)
|
||||
.and_then(associated_body)
|
||||
.map(|(_, body_id)| hir.body(body_id))
|
||||
{
|
||||
euv::ExprUseVisitor::new(ctx, infcx, closure, cx.param_env, cx.typeck_results()).consume_body(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
|
@ -161,25 +188,22 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
|||
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
|
||||
if is_async {
|
||||
let mut checked_closures = FxHashSet::default();
|
||||
while !ctx.async_closures.is_empty() {
|
||||
let closures = ctx.async_closures.clone();
|
||||
ctx.async_closures.clear();
|
||||
let hir = cx.tcx.hir();
|
||||
for closure in closures {
|
||||
if !checked_closures.insert(closure) {
|
||||
continue;
|
||||
}
|
||||
ctx.prev_bind = None;
|
||||
ctx.prev_move_to_closure.clear();
|
||||
if let Some(body) = hir
|
||||
.find_by_def_id(closure)
|
||||
.and_then(associated_body)
|
||||
.map(|(_, body_id)| hir.body(body_id))
|
||||
{
|
||||
euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results())
|
||||
.consume_body(body);
|
||||
}
|
||||
|
||||
// We retrieve all the closures declared in the async function because they will
|
||||
// not be found by `euv::Delegate`.
|
||||
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
|
||||
for_each_expr_with_closures(cx, body, |expr| {
|
||||
if let ExprKind::Closure(closure) = expr.kind {
|
||||
closures.insert(closure.def_id);
|
||||
}
|
||||
ControlFlow::<()>::Continue(())
|
||||
});
|
||||
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
|
||||
|
||||
while !ctx.async_closures.is_empty() {
|
||||
let async_closures = ctx.async_closures.clone();
|
||||
ctx.async_closures.clear();
|
||||
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, async_closures);
|
||||
}
|
||||
}
|
||||
ctx
|
||||
|
@ -244,6 +268,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
|||
struct MutablyUsedVariablesCtxt<'tcx> {
|
||||
mutably_used_vars: HirIdSet,
|
||||
prev_bind: Option<HirId>,
|
||||
/// In async functions, the inner AST is composed of multiple layers until we reach the code
|
||||
/// defined by the user. Because of that, some variables are marked as mutably borrowed even
|
||||
/// though they're not. This field lists the `HirId` that should not be considered as mutable
|
||||
/// use of a variable.
|
||||
prev_move_to_closure: HirIdSet,
|
||||
aliases: HirIdMap<HirId>,
|
||||
async_closures: FxHashSet<LocalDefId>,
|
||||
|
@ -308,7 +336,12 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
|||
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
|
||||
self.prev_bind = None;
|
||||
if let euv::Place {
|
||||
base: euv::PlaceBase::Local(vid),
|
||||
base:
|
||||
euv::PlaceBase::Local(vid)
|
||||
| euv::PlaceBase::Upvar(UpvarId {
|
||||
var_path: UpvarPath { hir_id: vid },
|
||||
..
|
||||
}),
|
||||
base_ty,
|
||||
..
|
||||
} = &cmt.place
|
||||
|
|
|
@ -5,10 +5,8 @@ use clippy_utils::{get_parent_node, is_lint_allowed, peel_blocks};
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, FnRetTy, ItemKind, Node, PatKind, Stmt, StmtKind,
|
||||
UnsafeSource,
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, ItemKind, Node, PatKind, Stmt, StmtKind, UnsafeSource,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_infer::infer::TyCtxtInferExt as _;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
@ -99,14 +97,13 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
|||
|diag| {
|
||||
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
|
||||
if let Node::Item(item) = parent.1
|
||||
&& let ItemKind::Fn(sig, ..) = item.kind
|
||||
&& let FnRetTy::Return(ret_ty) = sig.decl.output
|
||||
&& let ItemKind::Fn(..) = item.kind
|
||||
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
|
||||
&& let [.., final_stmt] = block.stmts
|
||||
&& final_stmt.hir_id == stmt.hir_id
|
||||
{
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||
let mut ret_ty = hir_ty_to_ty(cx.tcx, ret_ty);
|
||||
let mut ret_ty = cx.tcx.fn_sig(item.owner_id).instantiate_identity().output().skip_binder();
|
||||
|
||||
// Remove `impl Future<Output = T>` to get `T`
|
||||
if cx.tcx.ty_is_opaque_future(ret_ty) &&
|
||||
|
@ -115,7 +112,7 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
|||
ret_ty = true_ret_ty;
|
||||
}
|
||||
|
||||
if ret_ty == expr_ty {
|
||||
if !ret_ty.is_unit() && ret_ty == expr_ty {
|
||||
diag.span_suggestion(
|
||||
stmt.span.shrink_to_lo(),
|
||||
"did you mean to return it?",
|
||||
|
|
|
@ -4,7 +4,7 @@ use clippy_utils::ty::implements_trait;
|
|||
use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path, path_res, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp};
|
||||
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::EarlyBinder;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
@ -122,9 +122,6 @@ impl LateLintPass<'_> for NonCanonicalImpls {
|
|||
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
|
||||
return;
|
||||
}
|
||||
let ItemKind::Impl(_) = item.kind else {
|
||||
return;
|
||||
};
|
||||
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
|
||||
return;
|
||||
};
|
||||
|
@ -180,17 +177,8 @@ impl LateLintPass<'_> for NonCanonicalImpls {
|
|||
|
||||
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id)
|
||||
&& impl_item.ident.name == sym::partial_cmp
|
||||
&& let Some(ord_def_id) = cx
|
||||
.tcx
|
||||
.diagnostic_items(trait_impl.def_id.krate)
|
||||
.name_to_id
|
||||
.get(&sym::Ord)
|
||||
&& implements_trait(
|
||||
cx,
|
||||
trait_impl.self_ty(),
|
||||
*ord_def_id,
|
||||
&[],
|
||||
)
|
||||
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
|
||||
{
|
||||
// If the `cmp` call likely needs to be fully qualified in the suggestion
|
||||
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
|
||||
|
|
|
@ -13,7 +13,6 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_hir::{
|
||||
BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint};
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
|
@ -297,8 +296,8 @@ declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTER
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
|
||||
if let ItemKind::Const(hir_ty, _generics, body_id) = it.kind {
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
if let ItemKind::Const(.., body_id) = it.kind {
|
||||
let ty = cx.tcx.type_of(it.owner_id).instantiate_identity();
|
||||
if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
|
||||
lint(cx, Source::Item { item: it.span });
|
||||
}
|
||||
|
@ -306,8 +305,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) {
|
||||
if let TraitItemKind::Const(hir_ty, body_id_opt) = &trait_item.kind {
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
if let TraitItemKind::Const(_, body_id_opt) = &trait_item.kind {
|
||||
let ty = cx.tcx.type_of(trait_item.owner_id).instantiate_identity();
|
||||
|
||||
// Normalize assoc types because ones originated from generic params
|
||||
// bounded other traits could have their bound.
|
||||
|
@ -333,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
|
||||
if let ImplItemKind::Const(hir_ty, body_id) = &impl_item.kind {
|
||||
if let ImplItemKind::Const(_, body_id) = &impl_item.kind {
|
||||
let item_def_id = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id;
|
||||
let item = cx.tcx.hir().expect_item(item_def_id);
|
||||
|
||||
|
@ -366,7 +365,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
// we should use here as a frozen variant is a potential to be frozen
|
||||
// similar to unknown layouts.
|
||||
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
|
||||
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
|
||||
if is_unfrozen(cx, normalized);
|
||||
if is_value_unfrozen_poly(cx, *body_id, normalized);
|
||||
|
@ -381,7 +380,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
}
|
||||
},
|
||||
ItemKind::Impl(Impl { of_trait: None, .. }) => {
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
|
||||
// Normalize assoc types originated from generic params.
|
||||
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ use rustc_hir::{
|
|||
ImplItemKind, ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind,
|
||||
TyKind, Unsafety,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::traits::{Obligation, ObligationCause};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
@ -172,13 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
|
||||
for arg in check_fn_args(
|
||||
cx,
|
||||
cx.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.skip_binder()
|
||||
.inputs(),
|
||||
cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(),
|
||||
sig.decl.inputs,
|
||||
&sig.decl.output,
|
||||
&[],
|
||||
)
|
||||
.filter(|arg| arg.mutability() == Mutability::Not)
|
||||
|
@ -237,7 +231,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
|
||||
let decl = sig.decl;
|
||||
let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder();
|
||||
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, &decl.output, body.params)
|
||||
let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params)
|
||||
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
|
||||
.collect();
|
||||
let results = check_ptr_arg_usage(cx, body, &lint_args);
|
||||
|
@ -443,12 +437,13 @@ impl<'tcx> DerefTy<'tcx> {
|
|||
#[expect(clippy::too_many_lines)]
|
||||
fn check_fn_args<'cx, 'tcx: 'cx>(
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
tys: &'tcx [Ty<'tcx>],
|
||||
fn_sig: ty::FnSig<'tcx>,
|
||||
hir_tys: &'tcx [hir::Ty<'tcx>],
|
||||
ret_ty: &'tcx FnRetTy<'tcx>,
|
||||
params: &'tcx [Param<'tcx>],
|
||||
) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
|
||||
tys.iter()
|
||||
fn_sig
|
||||
.inputs()
|
||||
.iter()
|
||||
.zip(hir_tys.iter())
|
||||
.enumerate()
|
||||
.filter_map(move |(i, (ty, hir_ty))| {
|
||||
|
@ -494,9 +489,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
|
|||
})
|
||||
{
|
||||
if !lifetime.is_anonymous()
|
||||
&& let FnRetTy::Return(ret_ty) = ret_ty
|
||||
&& let ret_ty = hir_ty_to_ty(cx.tcx, ret_ty)
|
||||
&& ret_ty
|
||||
&& fn_sig.output()
|
||||
.walk()
|
||||
.filter_map(|arg| {
|
||||
arg.as_region().and_then(|lifetime| {
|
||||
|
|
|
@ -105,8 +105,9 @@ impl EarlyLintPass for RawStrings {
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
if !matches!(cx.get_lint_level(NEEDLESS_RAW_STRINGS), rustc_lint::Allow) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let req = {
|
||||
|
|
|
@ -3,11 +3,9 @@ use clippy_utils::is_from_proc_macro;
|
|||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{
|
||||
BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath,
|
||||
};
|
||||
use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::{in_external_macro, is_from_async_await};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::DesugaringKind;
|
||||
|
@ -72,9 +70,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
// the local is user-controlled
|
||||
if !in_external_macro(cx.sess(), local.span);
|
||||
if !is_from_proc_macro(cx, expr);
|
||||
// Async function parameters are lowered into the closure body, so we can't lint them.
|
||||
// see `lower_maybe_async_body` in `rust_ast_lowering`
|
||||
if !is_from_async_await(local.span);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -111,12 +106,7 @@ fn affects_assignments(cx: &LateContext<'_>, mutability: Mutability, bind: HirId
|
|||
}
|
||||
|
||||
/// Check if a rebinding of a local affects the code's drop behavior.
|
||||
fn affects_drop_behavior<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
bind: HirId,
|
||||
rebind: HirId,
|
||||
rebind_expr: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool {
|
||||
let hir = cx.tcx.hir();
|
||||
|
||||
// the rebinding is in a different scope than the original binding
|
||||
|
|
|
@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
|
||||
&& let item = cx.tcx.hir().item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
|
|
@ -28,35 +28,43 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
|
|||
return false;
|
||||
}
|
||||
|
||||
match arg.kind {
|
||||
let casts_peeled = peel_casts(arg);
|
||||
match casts_peeled.kind {
|
||||
// Catching:
|
||||
// transmute over constants that resolve to `null`.
|
||||
ExprKind::Path(ref _qpath) if matches!(constant(cx, cx.typeck_results(), arg), Some(Constant::RawPtr(0))) => {
|
||||
ExprKind::Path(ref _qpath)
|
||||
if matches!(
|
||||
constant(cx, cx.typeck_results(), casts_peeled),
|
||||
Some(Constant::RawPtr(0))
|
||||
) =>
|
||||
{
|
||||
lint_expr(cx, expr);
|
||||
true
|
||||
},
|
||||
|
||||
// Catching:
|
||||
// `std::mem::transmute(0 as *const i32)`
|
||||
ExprKind::Cast(inner_expr, _cast_ty) if is_integer_literal(inner_expr, 0) => {
|
||||
lint_expr(cx, expr);
|
||||
true
|
||||
},
|
||||
|
||||
// Catching:
|
||||
// `std::mem::transmute(std::ptr::null::<i32>())`
|
||||
ExprKind::Call(func1, []) if is_path_diagnostic_item(cx, func1, sym::ptr_null) => {
|
||||
lint_expr(cx, expr);
|
||||
true
|
||||
},
|
||||
|
||||
_ => {
|
||||
// FIXME:
|
||||
// Also catch transmutations of variables which are known nulls.
|
||||
// To do this, MIR const propagation seems to be the better tool.
|
||||
// Whenever MIR const prop routines are more developed, this will
|
||||
// become available. As of this writing (25/03/19) it is not yet.
|
||||
if is_integer_literal(casts_peeled, 0) {
|
||||
lint_expr(cx, expr);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn peel_casts<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
match &expr.kind {
|
||||
ExprKind::Cast(inner_expr, _) => peel_casts(inner_expr),
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -315,7 +315,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
|
|||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
_: FnKind<'_>,
|
||||
fn_kind: FnKind<'_>,
|
||||
decl: &FnDecl<'_>,
|
||||
_: &Body<'_>,
|
||||
_: Span,
|
||||
|
@ -340,6 +340,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
|
|||
CheckTyContext {
|
||||
is_in_trait_impl,
|
||||
is_exported,
|
||||
in_body: matches!(fn_kind, FnKind::Closure),
|
||||
..CheckTyContext::default()
|
||||
},
|
||||
);
|
||||
|
@ -427,7 +428,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
|
|||
cx,
|
||||
ty,
|
||||
CheckTyContext {
|
||||
is_local: true,
|
||||
in_body: true,
|
||||
..CheckTyContext::default()
|
||||
},
|
||||
);
|
||||
|
@ -481,7 +482,7 @@ impl Types {
|
|||
}
|
||||
|
||||
match hir_ty.kind {
|
||||
TyKind::Path(ref qpath) if !context.is_local => {
|
||||
TyKind::Path(ref qpath) if !context.in_body => {
|
||||
let hir_id = hir_ty.hir_id;
|
||||
let res = cx.qpath_res(qpath, hir_id);
|
||||
if let Some(def_id) = res.opt_def_id() {
|
||||
|
@ -581,8 +582,8 @@ impl Types {
|
|||
#[derive(Clone, Copy, Default)]
|
||||
struct CheckTyContext {
|
||||
is_in_trait_impl: bool,
|
||||
/// `true` for types on local variables.
|
||||
is_local: bool,
|
||||
/// `true` for types on local variables and in closure signatures.
|
||||
in_body: bool,
|
||||
/// `true` for types that are part of the public API.
|
||||
is_exported: bool,
|
||||
is_nested_call: bool,
|
||||
|
|
|
@ -7,7 +7,7 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::lint::{in_external_macro, is_from_async_await};
|
||||
use rustc_middle::ty;
|
||||
|
||||
use super::LET_UNIT_VALUE;
|
||||
|
@ -16,6 +16,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
|
|||
if let Some(init) = local.init
|
||||
&& !local.pat.span.from_expansion()
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& !is_from_async_await(local.span)
|
||||
&& cx.typeck_results().pat_ty(local.pat).is_unit()
|
||||
{
|
||||
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
|
||||
|
|
93
clippy_lints/src/unnecessary_map_on_constructor.rs
Normal file
93
clippy_lints/src/unnecessary_map_on_constructor.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::get_type_diagnostic_name;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Suggest removing the use of a may (or map_err) method when an Option or Result is being construted.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It introduces unnecessary complexity. In this case the function can be used directly and
|
||||
/// construct the Option or Result from the output.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// Some(4).map(i32::swap_bytes);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// Some(i32::swap_bytes(4));
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
pub UNNECESSARY_MAP_ON_CONSTRUCTOR,
|
||||
complexity,
|
||||
"using `map`/`map_err` on `Option` or `Result` constructors"
|
||||
}
|
||||
declare_lint_pass!(UnnecessaryMapOnConstructor => [UNNECESSARY_MAP_ON_CONSTRUCTOR]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let hir::ExprKind::MethodCall(path, recv, args, ..) = expr.kind
|
||||
&& let Some(sym::Option | sym::Result) = get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv)){
|
||||
let (constructor_path, constructor_item) =
|
||||
if let hir::ExprKind::Call(constructor, constructor_args) = recv.kind
|
||||
&& let hir::ExprKind::Path(constructor_path) = constructor.kind
|
||||
&& let Some(arg) = constructor_args.get(0)
|
||||
{
|
||||
if constructor.span.from_expansion() || arg.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
(constructor_path, arg)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let constructor_symbol = match constructor_path {
|
||||
hir::QPath::Resolved(_, path) => {
|
||||
if let Some(path_segment) = path.segments.last() {
|
||||
path_segment.ident.name
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
hir::QPath::TypeRelative(_, path) => path.ident.name,
|
||||
hir::QPath::LangItem(_, _, _) => return,
|
||||
};
|
||||
match constructor_symbol {
|
||||
sym::Some | sym::Ok if path.ident.name == rustc_span::sym::map => (),
|
||||
sym::Err if path.ident.name == sym!(map_err) => (),
|
||||
_ => return,
|
||||
}
|
||||
|
||||
if let Some(map_arg) = args.get(0)
|
||||
&& let hir::ExprKind::Path(fun) = map_arg.kind
|
||||
{
|
||||
if map_arg.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability);
|
||||
let constructor_snippet =
|
||||
snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability);
|
||||
let constructor_arg_snippet =
|
||||
snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_MAP_ON_CONSTRUCTOR,
|
||||
expr.span,
|
||||
&format!("unnecessary {} on constructor {constructor_snippet}(_)", path.ident.name),
|
||||
"try",
|
||||
format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,10 +8,14 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::traits::Obligation;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::traits::ObligationCause;
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -61,22 +65,69 @@ impl MethodOrFunction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
|
||||
fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, into_iter_did: DefId, param_index: u32) -> Option<Span> {
|
||||
cx.tcx
|
||||
.predicates_of(fn_did)
|
||||
.predicates
|
||||
.iter()
|
||||
.find_map(|&(ref pred, span)| {
|
||||
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder()
|
||||
&& tr.def_id() == into_iter_did
|
||||
&& tr.self_ty().is_param(param_index)
|
||||
{
|
||||
Some(span)
|
||||
} else {
|
||||
None
|
||||
/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`,
|
||||
/// iff all of the bounds also hold for the type of the `.into_iter()` receiver.
|
||||
/// ```ignore
|
||||
/// pub fn foo<I>(i: I)
|
||||
/// where I: IntoIterator<Item=i32> + ExactSizeIterator
|
||||
/// ^^^^^^^^^^^^^^^^^ this extra bound stops us from suggesting to remove `.into_iter()` ...
|
||||
/// {
|
||||
/// assert_eq!(i.len(), 3);
|
||||
/// }
|
||||
///
|
||||
/// pub fn bar() {
|
||||
/// foo([1, 2, 3].into_iter());
|
||||
/// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
|
||||
/// }
|
||||
/// ```
|
||||
fn into_iter_bound<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
fn_did: DefId,
|
||||
into_iter_did: DefId,
|
||||
into_iter_receiver: Ty<'tcx>,
|
||||
param_index: u32,
|
||||
node_args: GenericArgsRef<'tcx>,
|
||||
) -> Option<Span> {
|
||||
let param_env = cx.tcx.param_env(fn_did);
|
||||
let mut into_iter_span = None;
|
||||
|
||||
for (pred, span) in cx.tcx.explicit_predicates_of(fn_did).predicates {
|
||||
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() {
|
||||
if tr.self_ty().is_param(param_index) {
|
||||
if tr.def_id() == into_iter_did {
|
||||
into_iter_span = Some(*span);
|
||||
} else {
|
||||
let tr = cx.tcx.erase_regions(tr);
|
||||
if tr.has_escaping_bound_vars() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Substitute generics in the predicate and replace the IntoIterator type parameter with the
|
||||
// `.into_iter()` receiver to see if the bound also holds for that type.
|
||||
let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| {
|
||||
if i == param_index as usize {
|
||||
GenericArg::from(into_iter_receiver)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
}));
|
||||
|
||||
let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args);
|
||||
let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), param_env, predicate);
|
||||
if !cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build()
|
||||
.predicate_must_hold_modulo_regions(&obligation)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
into_iter_span
|
||||
}
|
||||
|
||||
/// Extracts the receiver of a `.into_iter()` method call.
|
||||
|
@ -160,22 +211,41 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
// `fn_sig` does not ICE. (see #11065)
|
||||
&& cx.tcx.opt_def_kind(did).is_some_and(DefKind::is_fn_like) =>
|
||||
{
|
||||
Some((did, args, MethodOrFunction::Function))
|
||||
Some((
|
||||
did,
|
||||
args,
|
||||
cx.typeck_results().node_args(recv.hir_id),
|
||||
MethodOrFunction::Function
|
||||
))
|
||||
}
|
||||
ExprKind::MethodCall(.., args, _) => {
|
||||
cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
||||
.map(|did| (did, args, MethodOrFunction::Method))
|
||||
.map(|did| {
|
||||
return (
|
||||
did,
|
||||
args,
|
||||
cx.typeck_results().node_args(parent.hir_id),
|
||||
MethodOrFunction::Method
|
||||
);
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((parent_fn_did, args, kind)) = parent_fn
|
||||
if let Some((parent_fn_did, args, node_args, kind)) = parent_fn
|
||||
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
|
||||
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
|
||||
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
|
||||
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
|
||||
&& let ty::Param(param) = into_iter_param.kind()
|
||||
&& let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
|
||||
&& let Some(span) = into_iter_bound(
|
||||
cx,
|
||||
parent_fn_did,
|
||||
into_iter_did,
|
||||
cx.typeck_results().expr_ty(into_iter_recv),
|
||||
param.index,
|
||||
node_args
|
||||
)
|
||||
&& self.expn_depth == 0
|
||||
{
|
||||
// Get the "innermost" `.into_iter()` call, e.g. given this expression:
|
||||
|
|
|
@ -542,11 +542,11 @@ define_Conf! {
|
|||
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
|
||||
///
|
||||
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
|
||||
(accept_comment_above_statement: bool = false),
|
||||
(accept_comment_above_statement: bool = true),
|
||||
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
|
||||
///
|
||||
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
|
||||
(accept_comment_above_attributes: bool = false),
|
||||
(accept_comment_above_attributes: bool = true),
|
||||
/// Lint: UNNECESSARY_RAW_STRING_HASHES.
|
||||
///
|
||||
/// Whether to allow `r#""#` when `r""` can be used
|
||||
|
@ -561,6 +561,11 @@ define_Conf! {
|
|||
/// Which crates to allow absolute paths from
|
||||
(absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> =
|
||||
rustc_data_structures::fx::FxHashSet::default()),
|
||||
/// Lint: PATH_ENDS_WITH_EXT.
|
||||
///
|
||||
/// Additional dotfiles (files or directories starting with a dot) to allow
|
||||
(allowed_dotfiles: rustc_data_structures::fx::FxHashSet<String> =
|
||||
rustc_data_structures::fx::FxHashSet::default()),
|
||||
/// Lint: EXPLICIT_ITER_LOOP
|
||||
///
|
||||
/// Whether to recommend using implicit into iter for reborrowed values.
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use clippy_utils::macros::collect_ast_format_args;
|
||||
use clippy_utils::macros::AST_FORMAT_ARGS;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::{Expr, ExprKind, FormatArgs};
|
||||
use rustc_ast::{Crate, Expr, ExprKind, FormatArgs};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::hygiene;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{hygiene, Span};
|
||||
use std::iter::once;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -17,7 +20,12 @@ declare_clippy_lint! {
|
|||
"collects `format_args` AST nodes for use in later lints"
|
||||
}
|
||||
|
||||
declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
|
||||
#[derive(Default)]
|
||||
pub struct FormatArgsCollector {
|
||||
format_args: FxHashMap<Span, Rc<FormatArgs>>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
|
||||
|
||||
impl EarlyLintPass for FormatArgsCollector {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
|
@ -26,9 +34,17 @@ impl EarlyLintPass for FormatArgsCollector {
|
|||
return;
|
||||
}
|
||||
|
||||
collect_ast_format_args(expr.span, args);
|
||||
self.format_args
|
||||
.insert(expr.span.with_parent(None), Rc::new((**args).clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) {
|
||||
AST_FORMAT_ARGS.with(|ast_format_args| {
|
||||
let result = ast_format_args.set(mem::take(&mut self.format_args));
|
||||
debug_assert!(result.is_ok());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects if the format string or an argument has its span set by a proc macro to something inside
|
||||
|
|
|
@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
|
|||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::interpret::ConstValue;
|
||||
use rustc_middle::mir::ConstValue;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
|
|
@ -5,10 +5,9 @@ use if_chain::if_chain;
|
|||
use rustc_hir as hir;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::Item;
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::fast_reject::SimplifiedType;
|
||||
use rustc_middle::ty::{self, FloatTy};
|
||||
use rustc_middle::ty::FloatTy;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
|
@ -34,25 +33,20 @@ impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
|
|||
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
|
||||
if_chain! {
|
||||
if mod_name.as_str() == "paths";
|
||||
if let hir::ItemKind::Const(ty, _, body_id) = item.kind;
|
||||
let ty = hir_ty_to_ty(cx.tcx, ty);
|
||||
if let ty::Array(el_ty, _) = &ty.kind();
|
||||
if let ty::Ref(_, el_ty, _) = &el_ty.kind();
|
||||
if el_ty.is_str();
|
||||
if let hir::ItemKind::Const(.., body_id) = item.kind;
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let typeck_results = cx.tcx.typeck_body(body_id);
|
||||
if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
|
||||
let path: Vec<&str> = path
|
||||
if let Some(path) = path
|
||||
.iter()
|
||||
.map(|x| {
|
||||
if let Constant::Str(s) = x {
|
||||
s.as_str()
|
||||
Some(s.as_str())
|
||||
} else {
|
||||
// We checked the type of the constant above
|
||||
unreachable!()
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Option<Vec<&str>>>();
|
||||
if !check_path(cx, &path[..]);
|
||||
then {
|
||||
span_lint(cx, INVALID_PATHS, item.span, "invalid path");
|
||||
|
|
|
@ -31,7 +31,7 @@ use serde::{Serialize, Serializer};
|
|||
use std::collections::{BTreeSet, BinaryHeap};
|
||||
use std::fmt;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
@ -229,25 +229,10 @@ impl Drop for MetadataCollector {
|
|||
collect_renames(&mut lints);
|
||||
|
||||
// Outputting json
|
||||
if Path::new(JSON_OUTPUT_FILE).exists() {
|
||||
fs::remove_file(JSON_OUTPUT_FILE).unwrap();
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(JSON_OUTPUT_FILE)
|
||||
.unwrap();
|
||||
writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
|
||||
fs::write(JSON_OUTPUT_FILE, serde_json::to_string_pretty(&lints).unwrap()).unwrap();
|
||||
|
||||
// Outputting markdown
|
||||
if Path::new(MARKDOWN_OUTPUT_FILE).exists() {
|
||||
fs::remove_file(MARKDOWN_OUTPUT_FILE).unwrap();
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(MARKDOWN_OUTPUT_FILE)
|
||||
.unwrap();
|
||||
let mut file = File::create(MARKDOWN_OUTPUT_FILE).unwrap();
|
||||
writeln!(
|
||||
file,
|
||||
"<!--
|
||||
|
@ -261,17 +246,15 @@ Please use that command to update the file and do not edit it by hand.
|
|||
.unwrap();
|
||||
|
||||
// Write configuration links to CHANGELOG.md
|
||||
let mut changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap();
|
||||
let mut changelog_file = OpenOptions::new().read(true).write(true).open(CHANGELOG_PATH).unwrap();
|
||||
|
||||
if let Some(position) = changelog.find("<!-- begin autogenerated links to configuration documentation -->") {
|
||||
// I know this is kinda wasteful, we just don't have regex on `clippy_lints` so... this is the best
|
||||
// we can do AFAIK.
|
||||
changelog = changelog[..position].to_string();
|
||||
}
|
||||
let changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap();
|
||||
let mut changelog_file = File::create(CHANGELOG_PATH).unwrap();
|
||||
let position = changelog
|
||||
.find("<!-- begin autogenerated links to configuration documentation -->")
|
||||
.unwrap();
|
||||
writeln!(
|
||||
changelog_file,
|
||||
"{changelog}<!-- begin autogenerated links to configuration documentation -->\n{}\n<!-- end autogenerated links to configuration documentation -->",
|
||||
"{}<!-- begin autogenerated links to configuration documentation -->\n{}\n<!-- end autogenerated links to configuration documentation -->",
|
||||
&changelog[..position],
|
||||
self.configs_to_markdown(ClippyConfiguration::to_markdown_link)
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
@ -5,9 +5,8 @@ use clippy_utils::{match_def_path, paths};
|
|||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArgKind};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -25,16 +24,14 @@ impl LateLintPass<'_> for MsrvAttrImpl {
|
|||
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
|
||||
if_chain! {
|
||||
if let hir::ItemKind::Impl(hir::Impl {
|
||||
of_trait: Some(lint_pass_trait_ref),
|
||||
self_ty,
|
||||
of_trait: Some(_),
|
||||
items,
|
||||
..
|
||||
}) = &item.kind;
|
||||
if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
|
||||
let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
|
||||
if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
|
||||
let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
|
||||
if let ty::Adt(self_ty_def, _) = self_ty.kind();
|
||||
if let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::instantiate_identity);
|
||||
let is_late_pass = match_def_path(cx, trait_ref.def_id, &paths::LATE_LINT_PASS);
|
||||
if is_late_pass || match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS);
|
||||
if let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind();
|
||||
if self_ty_def.is_struct();
|
||||
if self_ty_def.all_fields().any(|f| {
|
||||
cx.tcx
|
||||
|
|
|
@ -10,7 +10,8 @@ use rustc_hir::def::{DefKind, Res};
|
|||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
|
||||
use rustc_middle::mir::interpret::{Allocation, GlobalAlloc};
|
||||
use rustc_middle::mir::ConstValue;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
@ -232,7 +233,8 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
|
|||
cx.tcx.type_of(def_id).instantiate_identity(),
|
||||
),
|
||||
Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
|
||||
ConstValue::Indirect { alloc, offset } if offset.bytes() == 0 => {
|
||||
ConstValue::Indirect { alloc_id, offset } if offset.bytes() == 0 => {
|
||||
let alloc = cx.tcx.global_alloc(alloc_id).unwrap_memory();
|
||||
read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity())
|
||||
},
|
||||
_ => None,
|
||||
|
|
|
@ -304,7 +304,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
|
|||
_ => return,
|
||||
}
|
||||
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) {
|
||||
// ignore `writeln!(w)` and `write!(v, some_macro!())`
|
||||
if format_args.span.from_expansion() {
|
||||
return;
|
||||
|
@ -312,15 +312,15 @@ impl<'tcx> LateLintPass<'tcx> for Write {
|
|||
|
||||
match diag_name {
|
||||
sym::print_macro | sym::eprint_macro | sym::write_macro => {
|
||||
check_newline(cx, format_args, ¯o_call, name);
|
||||
check_newline(cx, &format_args, ¯o_call, name);
|
||||
},
|
||||
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
|
||||
check_empty_string(cx, format_args, ¯o_call, name);
|
||||
check_empty_string(cx, &format_args, ¯o_call, name);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
check_literal(cx, format_args, name);
|
||||
check_literal(cx, &format_args, name);
|
||||
|
||||
if !self.in_debug_impl {
|
||||
for piece in &format_args.template {
|
||||
|
@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -671,10 +671,11 @@ pub fn miri_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
|
|||
ty::RawPtr(_) => Some(Constant::RawPtr(int.assert_bits(int.size()))),
|
||||
_ => None,
|
||||
},
|
||||
mir::Const::Val(cv, _) if matches!(result.ty().kind(), ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Str)) => {
|
||||
mir::Const::Val(cv, _) if matches!(result.ty().kind(), ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Str)) =>
|
||||
{
|
||||
let data = cv.try_get_slice_bytes_for_diagnostics(lcx.tcx)?;
|
||||
String::from_utf8(data.to_owned()).ok().map(Constant::Str)
|
||||
}
|
||||
},
|
||||
mir::Const::Val(ConstValue::Indirect { alloc_id, offset: _ }, _) => {
|
||||
let alloc = lcx.tcx.global_alloc(alloc_id).unwrap_memory();
|
||||
match result.ty().kind() {
|
||||
|
|
|
@ -83,9 +83,9 @@ pub fn span_lint_and_help<T: LintContext>(
|
|||
cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
|
||||
let help = help.to_string();
|
||||
if let Some(help_span) = help_span {
|
||||
diag.span_help(help_span, help.to_string());
|
||||
diag.span_help(help_span, help);
|
||||
} else {
|
||||
diag.help(help.to_string());
|
||||
diag.help(help);
|
||||
}
|
||||
docs_link(diag, lint);
|
||||
diag
|
||||
|
|
|
@ -1785,6 +1785,33 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
|
|||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if the lint is `#[allow]`ed or `#[expect]`ed at any of the `ids`, fulfilling all
|
||||
/// of the expectations in `ids`
|
||||
///
|
||||
/// This should only be used when the lint would otherwise be emitted, for a way to check if a lint
|
||||
/// is allowed early to skip work see [`is_lint_allowed`]
|
||||
///
|
||||
/// To emit at a lint at a different context than the one current see
|
||||
/// [`span_lint_hir`](diagnostics::span_lint_hir) or
|
||||
/// [`span_lint_hir_and_then`](diagnostics::span_lint_hir_and_then)
|
||||
pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool {
|
||||
let mut suppress_lint = false;
|
||||
|
||||
for id in ids {
|
||||
let (level, _) = cx.tcx.lint_level_at_node(lint, id);
|
||||
if let Some(expectation) = level.get_expectation_id() {
|
||||
cx.fulfill_expectation(expectation);
|
||||
}
|
||||
|
||||
match level {
|
||||
Level::Allow | Level::Expect(_) => suppress_lint = true,
|
||||
Level::Warn | Level::ForceWarn(_) | Level::Deny | Level::Forbid => {},
|
||||
}
|
||||
}
|
||||
|
||||
suppress_lint
|
||||
}
|
||||
|
||||
/// Returns `true` if the lint is allowed in the current context. This is useful for
|
||||
/// skipping long running code when it's unnecessary
|
||||
///
|
||||
|
@ -1958,7 +1985,7 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
|
|||
/// Checks if the given function kind is an async function.
|
||||
pub fn is_async_fn(kind: FnKind<'_>) -> bool {
|
||||
match kind {
|
||||
FnKind::ItemFn(_, _, header) => header.asyncness .is_async(),
|
||||
FnKind::ItemFn(_, _, header) => header.asyncness.is_async(),
|
||||
FnKind::Method(_, sig) => sig.header.asyncness.is_async(),
|
||||
FnKind::Closure => false,
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@ use rustc_lint::LateContext;
|
|||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
|
||||
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol};
|
||||
use std::cell::RefCell;
|
||||
use std::cell::OnceCell;
|
||||
use std::ops::ControlFlow;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
|
||||
|
@ -374,28 +375,21 @@ thread_local! {
|
|||
/// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
|
||||
/// assumption that the early pass that populates the map and the later late passes will all be
|
||||
/// running on the same thread.
|
||||
static AST_FORMAT_ARGS: RefCell<FxHashMap<Span, FormatArgs>> = {
|
||||
#[doc(hidden)]
|
||||
pub static AST_FORMAT_ARGS: OnceCell<FxHashMap<Span, Rc<FormatArgs>>> = {
|
||||
static CALLED: AtomicBool = AtomicBool::new(false);
|
||||
debug_assert!(
|
||||
!CALLED.swap(true, Ordering::SeqCst),
|
||||
"incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
|
||||
);
|
||||
|
||||
RefCell::default()
|
||||
OnceCell::new()
|
||||
};
|
||||
}
|
||||
|
||||
/// Record [`rustc_ast::FormatArgs`] for use in late lint passes, this should only be called by
|
||||
/// `FormatArgsCollector`
|
||||
pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
|
||||
AST_FORMAT_ARGS.with(|ast_format_args| {
|
||||
ast_format_args.borrow_mut().insert(span, format_args.clone());
|
||||
});
|
||||
}
|
||||
|
||||
/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
|
||||
/// descendant of `expn_id`
|
||||
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
|
||||
/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
|
||||
/// `expn_id`
|
||||
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<Rc<FormatArgs>> {
|
||||
let format_args_expr = for_each_expr(start, |expr| {
|
||||
let ctxt = expr.span.ctxt();
|
||||
if ctxt.outer_expn().is_descendant_of(expn_id) {
|
||||
|
@ -410,13 +404,14 @@ pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId,
|
|||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
});
|
||||
})?;
|
||||
|
||||
if let Some(expr) = format_args_expr {
|
||||
AST_FORMAT_ARGS.with(|ast_format_args| {
|
||||
ast_format_args.borrow().get(&expr.span).map(callback);
|
||||
});
|
||||
}
|
||||
AST_FORMAT_ARGS.with(|ast_format_args| {
|
||||
ast_format_args
|
||||
.get()?
|
||||
.get(&format_args_expr.span.with_parent(None))
|
||||
.map(Rc::clone)
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use rustc_hir::{Expr, HirId};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::{
|
||||
traversal, Body, InlineAsmOperand, Local, Location, Place, StatementKind, TerminatorKind, START_BLOCK,
|
||||
traversal, BasicBlock, Body, InlineAsmOperand, Local, Location, Place, StatementKind, TerminatorKind, START_BLOCK,
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
|
@ -79,8 +80,32 @@ impl<'a, 'tcx> Visitor<'tcx> for V<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the block is part of a cycle
|
||||
pub fn block_in_cycle(body: &Body<'_>, block: BasicBlock) -> bool {
|
||||
let mut seen = BitSet::new_empty(body.basic_blocks.len());
|
||||
let mut to_visit = Vec::with_capacity(body.basic_blocks.len() / 2);
|
||||
|
||||
seen.insert(block);
|
||||
let mut next = block;
|
||||
loop {
|
||||
for succ in body.basic_blocks[next].terminator().successors() {
|
||||
if seen.insert(succ) {
|
||||
to_visit.push(succ);
|
||||
} else if succ == block {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(x) = to_visit.pop() {
|
||||
next = x;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper around `visit_local_usage`.
|
||||
pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle::mir::Local) -> Option<bool> {
|
||||
pub fn used_exactly_once(mir: &Body<'_>, local: rustc_middle::mir::Local) -> Option<bool> {
|
||||
visit_local_usage(
|
||||
&[local],
|
||||
mir,
|
||||
|
@ -91,11 +116,14 @@ pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle:
|
|||
)
|
||||
.map(|mut vec| {
|
||||
let LocalUsage { local_use_locs, .. } = vec.remove(0);
|
||||
local_use_locs
|
||||
let mut locations = local_use_locs
|
||||
.into_iter()
|
||||
.filter(|location| !is_local_assignment(mir, local, *location))
|
||||
.count()
|
||||
== 1
|
||||
.filter(|&location| !is_local_assignment(mir, local, location));
|
||||
if let Some(location) = locations.next() {
|
||||
locations.next().is_none() && !block_in_cycle(mir, location.block)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
|
|||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::source_map::{original_sp, SourceMap};
|
||||
use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use rustc_span::{hygiene, BytePos, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Range;
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
|
|||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::{ConstValue, interpret::Scalar};
|
||||
use rustc_middle::mir::interpret::Scalar;
|
||||
use rustc_middle::mir::ConstValue;
|
||||
use rustc_middle::traits::EvaluationResult;
|
||||
use rustc_middle::ty::layout::ValidityRequirement;
|
||||
use rustc_middle::ty::{
|
||||
|
|
|
@ -208,8 +208,7 @@ fn path_segment_certainty(
|
|||
if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
|
||||
let generics = cx.tcx.generics_of(def_id);
|
||||
let count = generics.params.len() - generics.host_effect_index.is_some() as usize;
|
||||
let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && count == 0
|
||||
{
|
||||
let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && count == 0 {
|
||||
Certainty::Certain(None)
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
|
@ -300,10 +299,11 @@ fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> b
|
|||
|
||||
// Check that all type parameters appear in the functions input types.
|
||||
(0..(generics.parent_count + generics.params.len()) as u32).all(|index| {
|
||||
Some(index as usize) == generics.host_effect_index || fn_sig
|
||||
.inputs()
|
||||
.iter()
|
||||
.any(|input_ty| contains_param(*input_ty.skip_binder(), index))
|
||||
Some(index as usize) == generics.host_effect_index
|
||||
|| fn_sig
|
||||
.inputs()
|
||||
.iter()
|
||||
.any(|input_ty| contains_param(*input_ty.skip_binder(), index))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ use test_utils::IS_RUSTC_TEST_SUITE;
|
|||
// in the depinfo file (otherwise cargo thinks they are unused)
|
||||
extern crate clippy_lints;
|
||||
extern crate clippy_utils;
|
||||
extern crate derive_new;
|
||||
extern crate futures;
|
||||
extern crate if_chain;
|
||||
extern crate itertools;
|
||||
|
@ -33,7 +32,6 @@ mod test_utils;
|
|||
static TEST_DEPENDENCIES: &[&str] = &[
|
||||
"clippy_lints",
|
||||
"clippy_utils",
|
||||
"derive_new",
|
||||
"futures",
|
||||
"if_chain",
|
||||
"itertools",
|
||||
|
|
1
tests/ui-toml/decimal_literal_representation/clippy.toml
Normal file
1
tests/ui-toml/decimal_literal_representation/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
literal-representation-threshold = 0xFFFFFF
|
|
@ -0,0 +1,6 @@
|
|||
#![warn(clippy::decimal_literal_representation)]
|
||||
fn main() {
|
||||
let _ = 8388608;
|
||||
let _ = 0x00FF_FFFF;
|
||||
//~^ ERROR: integer literal has a better hexadecimal representation
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#![warn(clippy::decimal_literal_representation)]
|
||||
fn main() {
|
||||
let _ = 8388608;
|
||||
let _ = 16777215;
|
||||
//~^ ERROR: integer literal has a better hexadecimal representation
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
error: integer literal has a better hexadecimal representation
|
||||
--> $DIR/decimal_literal_representation.rs:4:13
|
||||
|
|
||||
LL | let _ = 16777215;
|
||||
| ^^^^^^^^ help: consider: `0x00FF_FFFF`
|
||||
|
|
||||
= note: `-D clippy::decimal-literal-representation` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::decimal_literal_representation)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/disallowed_script_idents/clippy.toml
Normal file
1
tests/ui-toml/disallowed_script_idents/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
allowed-scripts = ["Cyrillic"]
|
|
@ -0,0 +1,6 @@
|
|||
#![warn(clippy::disallowed_script_idents)]
|
||||
fn main() {
|
||||
let счётчик = 10;
|
||||
let カウンタ = 10;
|
||||
//~^ ERROR: identifier `カウンタ` has a Unicode script that is not allowed by configuration
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
error: identifier `カウンタ` has a Unicode script that is not allowed by configuration: Katakana
|
||||
--> $DIR/disallowed_script_idents.rs:4:9
|
||||
|
|
||||
LL | let カウンタ = 10;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::disallowed-script-idents` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::disallowed_script_idents)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/enum_variant_names/clippy.toml
Normal file
1
tests/ui-toml/enum_variant_names/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
enum-variant-name-threshold = 5
|
16
tests/ui-toml/enum_variant_names/enum_variant_names.rs
Normal file
16
tests/ui-toml/enum_variant_names/enum_variant_names.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
enum Foo {
|
||||
AFoo,
|
||||
BFoo,
|
||||
CFoo,
|
||||
DFoo,
|
||||
}
|
||||
enum Foo2 {
|
||||
//~^ ERROR: all variants have the same postfix
|
||||
AFoo,
|
||||
BFoo,
|
||||
CFoo,
|
||||
DFoo,
|
||||
EFoo,
|
||||
}
|
||||
|
||||
fn main() {}
|
18
tests/ui-toml/enum_variant_names/enum_variant_names.stderr
Normal file
18
tests/ui-toml/enum_variant_names/enum_variant_names.stderr
Normal file
|
@ -0,0 +1,18 @@
|
|||
error: all variants have the same postfix: `Foo`
|
||||
--> $DIR/enum_variant_names.rs:7:1
|
||||
|
|
||||
LL | / enum Foo2 {
|
||||
LL | |
|
||||
LL | | AFoo,
|
||||
LL | | BFoo,
|
||||
... |
|
||||
LL | | EFoo,
|
||||
LL | | }
|
||||
| |_^
|
||||
|
|
||||
= help: remove the postfixes and use full paths to the variants instead of glob imports
|
||||
= note: `-D clippy::enum-variant-names` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::enum_variant_names)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/enum_variant_size/clippy.toml
Normal file
1
tests/ui-toml/enum_variant_size/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
enum-variant-size-threshold = 500
|
11
tests/ui-toml/enum_variant_size/enum_variant_size.fixed
Normal file
11
tests/ui-toml/enum_variant_size/enum_variant_size.fixed
Normal file
|
@ -0,0 +1,11 @@
|
|||
enum Fine {
|
||||
A(()),
|
||||
B([u8; 500]),
|
||||
}
|
||||
enum Bad {
|
||||
//~^ ERROR: large size difference between variants
|
||||
A(()),
|
||||
B(Box<[u8; 501]>),
|
||||
}
|
||||
|
||||
fn main() {}
|
11
tests/ui-toml/enum_variant_size/enum_variant_size.rs
Normal file
11
tests/ui-toml/enum_variant_size/enum_variant_size.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
enum Fine {
|
||||
A(()),
|
||||
B([u8; 500]),
|
||||
}
|
||||
enum Bad {
|
||||
//~^ ERROR: large size difference between variants
|
||||
A(()),
|
||||
B([u8; 501]),
|
||||
}
|
||||
|
||||
fn main() {}
|
21
tests/ui-toml/enum_variant_size/enum_variant_size.stderr
Normal file
21
tests/ui-toml/enum_variant_size/enum_variant_size.stderr
Normal file
|
@ -0,0 +1,21 @@
|
|||
error: large size difference between variants
|
||||
--> $DIR/enum_variant_size.rs:5:1
|
||||
|
|
||||
LL | / enum Bad {
|
||||
LL | |
|
||||
LL | | A(()),
|
||||
| | ----- the second-largest variant contains at least 0 bytes
|
||||
LL | | B([u8; 501]),
|
||||
| | ------------ the largest variant contains at least 501 bytes
|
||||
LL | | }
|
||||
| |_^ the entire enum is at least 502 bytes
|
||||
|
|
||||
= note: `-D clippy::large-enum-variant` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]`
|
||||
help: consider boxing the large fields to reduce the total size of the enum
|
||||
|
|
||||
LL | B(Box<[u8; 501]>),
|
||||
| ~~~~~~~~~~~~~~
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/enum_variants_threshold0/clippy.toml
Normal file
1
tests/ui-toml/enum_variants_threshold0/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
enum-variant-name-threshold = 0
|
|
@ -0,0 +1,3 @@
|
|||
enum Actions {}
|
||||
|
||||
fn main() {}
|
1
tests/ui-toml/explicit_iter_loop/clippy.toml
Normal file
1
tests/ui-toml/explicit_iter_loop/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
enforce-iter-loop-reborrow = true
|
10
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.fixed
Normal file
10
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.fixed
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![warn(clippy::explicit_iter_loop)]
|
||||
|
||||
fn main() {
|
||||
let mut vec = vec![1, 2, 3];
|
||||
let rmvec = &mut vec;
|
||||
for _ in &*rmvec {}
|
||||
//~^ ERROR: it is more concise to loop over references to containers
|
||||
for _ in &mut *rmvec {}
|
||||
//~^ ERROR: it is more concise to loop over references to containers
|
||||
}
|
10
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.rs
Normal file
10
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![warn(clippy::explicit_iter_loop)]
|
||||
|
||||
fn main() {
|
||||
let mut vec = vec![1, 2, 3];
|
||||
let rmvec = &mut vec;
|
||||
for _ in rmvec.iter() {}
|
||||
//~^ ERROR: it is more concise to loop over references to containers
|
||||
for _ in rmvec.iter_mut() {}
|
||||
//~^ ERROR: it is more concise to loop over references to containers
|
||||
}
|
17
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.stderr
Normal file
17
tests/ui-toml/explicit_iter_loop/explicit_iter_loop.stderr
Normal file
|
@ -0,0 +1,17 @@
|
|||
error: it is more concise to loop over references to containers instead of using explicit iteration methods
|
||||
--> $DIR/explicit_iter_loop.rs:6:14
|
||||
|
|
||||
LL | for _ in rmvec.iter() {}
|
||||
| ^^^^^^^^^^^^ help: to write this more concisely, try: `&*rmvec`
|
||||
|
|
||||
= note: `-D clippy::explicit-iter-loop` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::explicit_iter_loop)]`
|
||||
|
||||
error: it is more concise to loop over references to containers instead of using explicit iteration methods
|
||||
--> $DIR/explicit_iter_loop.rs:8:14
|
||||
|
|
||||
LL | for _ in rmvec.iter_mut() {}
|
||||
| ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut *rmvec`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
1
tests/ui-toml/large_stack_frames/clippy.toml
Normal file
1
tests/ui-toml/large_stack_frames/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
stack-size-threshold = 1000
|
17
tests/ui-toml/large_stack_frames/large_stack_frames.rs
Normal file
17
tests/ui-toml/large_stack_frames/large_stack_frames.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
#![warn(clippy::large_stack_frames)]
|
||||
|
||||
// We use this helper function instead of writing [0; 4294967297] directly to represent a
|
||||
// case that large_stack_arrays can't catch
|
||||
fn create_array<const N: usize>() -> [u8; N] {
|
||||
[0; N]
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let _x = create_array::<1000>();
|
||||
}
|
||||
fn f2() {
|
||||
//~^ ERROR: this function allocates a large amount of stack space
|
||||
let _x = create_array::<1001>();
|
||||
}
|
||||
|
||||
fn main() {}
|
15
tests/ui-toml/large_stack_frames/large_stack_frames.stderr
Normal file
15
tests/ui-toml/large_stack_frames/large_stack_frames.stderr
Normal file
|
@ -0,0 +1,15 @@
|
|||
error: this function allocates a large amount of stack space
|
||||
--> $DIR/large_stack_frames.rs:12:1
|
||||
|
|
||||
LL | / fn f2() {
|
||||
LL | |
|
||||
LL | | let _x = create_array::<1001>();
|
||||
LL | | }
|
||||
| |_^
|
||||
|
|
||||
= note: allocating large amounts of stack space can overflow the stack
|
||||
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/large_types_passed_by_value/clippy.toml
Normal file
1
tests/ui-toml/large_types_passed_by_value/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
pass-by-value-size-limit = 512
|
|
@ -0,0 +1,7 @@
|
|||
#![warn(clippy::large_types_passed_by_value)]
|
||||
|
||||
fn f(_v: [u8; 512]) {}
|
||||
fn f2(_v: &[u8; 513]) {}
|
||||
//~^ ERROR: this argument (513 byte) is passed by value
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,7 @@
|
|||
#![warn(clippy::large_types_passed_by_value)]
|
||||
|
||||
fn f(_v: [u8; 512]) {}
|
||||
fn f2(_v: [u8; 513]) {}
|
||||
//~^ ERROR: this argument (513 byte) is passed by value
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,11 @@
|
|||
error: this argument (513 byte) is passed by value, but might be more efficient if passed by reference (limit: 512 byte)
|
||||
--> $DIR/large_types_passed_by_value.rs:4:11
|
||||
|
|
||||
LL | fn f2(_v: [u8; 513]) {}
|
||||
| ^^^^^^^^^ help: consider passing by reference instead: `&[u8; 513]`
|
||||
|
|
||||
= note: `-D clippy::large-types-passed-by-value` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_types_passed_by_value)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/manual_let_else/clippy.toml
Normal file
1
tests/ui-toml/manual_let_else/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
matches-for-let-else = "AllTypes"
|
10
tests/ui-toml/manual_let_else/manual_let_else.fixed
Normal file
10
tests/ui-toml/manual_let_else/manual_let_else.fixed
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![warn(clippy::manual_let_else)]
|
||||
|
||||
enum Foo {
|
||||
A(u8),
|
||||
B,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let Foo::A(x) = Foo::A(1) else { return };
|
||||
}
|
14
tests/ui-toml/manual_let_else/manual_let_else.rs
Normal file
14
tests/ui-toml/manual_let_else/manual_let_else.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
#![warn(clippy::manual_let_else)]
|
||||
|
||||
enum Foo {
|
||||
A(u8),
|
||||
B,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = match Foo::A(1) {
|
||||
//~^ ERROR: this could be rewritten as `let...else`
|
||||
Foo::A(x) => x,
|
||||
Foo::B => return,
|
||||
};
|
||||
}
|
15
tests/ui-toml/manual_let_else/manual_let_else.stderr
Normal file
15
tests/ui-toml/manual_let_else/manual_let_else.stderr
Normal file
|
@ -0,0 +1,15 @@
|
|||
error: this could be rewritten as `let...else`
|
||||
--> $DIR/manual_let_else.rs:9:5
|
||||
|
|
||||
LL | / let x = match Foo::A(1) {
|
||||
LL | |
|
||||
LL | | Foo::A(x) => x,
|
||||
LL | | Foo::B => return,
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let Foo::A(x) = Foo::A(1) else { return };`
|
||||
|
|
||||
= note: `-D clippy::manual-let-else` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::manual_let_else)]`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
1
tests/ui-toml/path_ends_with_ext/clippy.toml
Normal file
1
tests/ui-toml/path_ends_with_ext/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
allowed-dotfiles = ["dot"]
|
9
tests/ui-toml/path_ends_with_ext/path_ends_with_ext.rs
Normal file
9
tests/ui-toml/path_ends_with_ext/path_ends_with_ext.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
#![warn(clippy::path_ends_with_ext)]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
fn f(p: &Path) {
|
||||
p.ends_with(".dot");
|
||||
}
|
||||
|
||||
fn main() {}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue