Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2023-09-25 10:26:11 +02:00
commit 81fe8dc084
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
210 changed files with 4057 additions and 2370 deletions

View File

@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: '14.x' node-version: '18.x'
- name: Install remark - name: Install remark
run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm 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 - name: Install mdbook
run: | run: |
mkdir mdbook 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 echo `pwd`/mdbook >> $GITHUB_PATH
# Run # Run
- name: Check *.md files - 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 - name: Linkcheck book
run: | run: |
rustup toolchain install nightly --component rust-docs rustup toolchain install nightly --component rust-docs
curl https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh -o linkcheck.sh curl https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh -o linkcheck.sh
sh linkcheck.sh clippy --path ./book sh linkcheck.sh clippy --path ./book
- name: Build mdbook - name: Build mdbook
run: mdbook build book run: mdbook build book

View File

@ -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_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_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_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_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_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 [`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_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 [`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_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 [`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 [`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 [`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 [`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 [`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_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_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_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 [`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_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_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_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_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_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 [`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 [`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-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 [`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 [`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
<!-- end autogenerated links to configuration documentation --> <!-- end autogenerated links to configuration documentation -->

View File

@ -38,7 +38,6 @@ itertools = "0.10.1"
# UI test dependencies # UI test dependencies
clippy_utils = { path = "clippy_utils" } clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0" if_chain = "1.0"
quote = "1.0" quote = "1.0"
serde = { version = "1.0.125", features = ["derive"] } serde = { version = "1.0.125", features = ["derive"] }

View File

@ -703,7 +703,7 @@ Minimum chars an ident can have, anything below or equal to this will be linted.
## `accept-comment-above-statement` ## `accept-comment-above-statement`
Whether to accept a safety comment to be placed above the statement containing the `unsafe` block 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:** **Affected lints:**
@ -713,7 +713,7 @@ Whether to accept a safety comment to be placed above the statement containing t
## `accept-comment-above-attributes` ## `accept-comment-above-attributes`
Whether to accept a safety comment to be placed above the attributes for the `unsafe` block 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:** **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) * [`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` ## `enforce-iter-loop-reborrow`
#### Example #### Example
``` ```

View File

@ -616,7 +616,7 @@ fn check_should_panic_reason(cx: &LateContext<'_>, attr: &Attribute) {
attr.span, attr.span,
"#[should_panic] attribute without a reason", "#[should_panic] attribute without a reason",
"consider specifying the expected panic", "consider specifying the expected panic",
r#"#[should_panic(expected = /* panic message */)]"#.into(), "#[should_panic(expected = /* panic message */)]".into(),
Applicability::HasPlaceholders, Applicability::HasPlaceholders,
); );
} }

View File

@ -25,7 +25,7 @@ pub(super) fn check(
// The suggestion is to use a function call, so if the original expression // The suggestion is to use a function call, so if the original expression
// has parens on the outside, they are no longer needed. // has parens on the outside, they are no longer needed.
let mut applicability = Applicability::MachineApplicable; 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( let sugg = opt.as_ref().map_or_else(
|| { || {
applicability = Applicability::HasPlaceholders; applicability = Applicability::HasPlaceholders;

View File

@ -44,7 +44,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
.unwrap_or(u64::max_value()) .unwrap_or(u64::max_value())
.min(apply_reductions(cx, nbits, left, signed)), .min(apply_reductions(cx, nbits, left, signed)),
BinOpKind::Shr => 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, _ => nbits,
}, },
ExprKind::MethodCall(method, left, [right], _) => { ExprKind::MethodCall(method, left, [right], _) => {

View File

@ -20,6 +20,7 @@ mod ptr_as_ptr;
mod ptr_cast_constness; mod ptr_cast_constness;
mod unnecessary_cast; mod unnecessary_cast;
mod utils; mod utils;
mod zero_ptr;
use clippy_utils::is_hir_ty_cfg_dependant; use clippy_utils::is_hir_ty_cfg_dependant;
use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::msrvs::{self, Msrv};
@ -665,6 +666,29 @@ declare_clippy_lint! {
"casting a known floating-point NaN into an integer" "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 { pub struct Casts {
msrv: Msrv, msrv: Msrv,
} }
@ -699,6 +723,7 @@ impl_lint_pass!(Casts => [
CAST_SLICE_FROM_RAW_PARTS, CAST_SLICE_FROM_RAW_PARTS,
AS_PTR_CAST_MUT, AS_PTR_CAST_MUT,
CAST_NAN_TO_INT, CAST_NAN_TO_INT,
ZERO_PTR,
]); ]);
impl<'tcx> LateLintPass<'tcx> for Casts { 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_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::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::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) { 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); cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span);

View 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,
);
}
}

View File

@ -97,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::casts::PTR_AS_PTR_INFO, crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO, crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::UNNECESSARY_CAST_INFO, crate::casts::UNNECESSARY_CAST_INFO,
crate::casts::ZERO_PTR_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
@ -399,9 +400,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::OR_FUN_CALL_INFO, crate::methods::OR_FUN_CALL_INFO,
crate::methods::OR_THEN_UNWRAP_INFO, crate::methods::OR_THEN_UNWRAP_INFO,
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
crate::methods::PATH_ENDS_WITH_EXT_INFO,
crate::methods::RANGE_ZIP_WITH_LEN_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO,
crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READONLY_WRITE_LOCK_INFO,
crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
crate::methods::REDUNDANT_AS_STR_INFO,
crate::methods::REPEAT_ONCE_INFO, crate::methods::REPEAT_ONCE_INFO,
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
crate::methods::SEARCH_IS_SOME_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::SHORT_CIRCUIT_STATEMENT_INFO,
crate::misc::TOPLEVEL_REF_ARG_INFO, crate::misc::TOPLEVEL_REF_ARG_INFO,
crate::misc::USED_UNDERSCORE_BINDING_INFO, crate::misc::USED_UNDERSCORE_BINDING_INFO,
crate::misc::ZERO_PTR_INFO,
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO, crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
crate::misc_early::DOUBLE_NEG_INFO, crate::misc_early::DOUBLE_NEG_INFO,
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_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_INFO,
crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO, crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO,
crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_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_continue::NEEDLESS_CONTINUE_INFO,
crate::needless_else::NEEDLESS_ELSE_INFO, crate::needless_else::NEEDLESS_ELSE_INFO,
crate::needless_for_each::NEEDLESS_FOR_EACH_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::FN_ADDRESS_COMPARISONS_INFO,
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO, crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_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_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,

View File

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{self as hir, HirId, Item, ItemKind}; use rustc_hir::{HirId, Item, ItemKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf; 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_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym; use rustc_span::sym;
@ -52,7 +52,10 @@ declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION])
impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { 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( span_lint_and_help(
cx, cx,
DEFAULT_UNION_REPRESENTATION, 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 /// if there is only one field left after ignoring ZST fields then the offset
/// of that field does not matter either.) /// of that field does not matter either.)
fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool { fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
if let ItemKind::Union(data, _) = &item.kind { if let ItemKind::Union(..) = &item.kind
data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2 && 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 { } else {
false false
} }
} }
fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool { fn is_zst<'tcx>(cx: &LateContext<'tcx>, field: &FieldDef, args: &'tcx List<GenericArg<'tcx>>) -> bool {
if hir_ty.span.from_expansion() { let ty = field.ty(cx.tcx, args);
return false;
}
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
if let Ok(layout) = cx.layout_of(ty) { if let Ok(layout) = cx.layout_of(ty) {
layout.is_zst() layout.is_zst()
} else { } else {

View File

@ -1,41 +1,24 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; 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::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_copy, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, peel_mid_ty_refs};
use clippy_utils::{ use clippy_utils::{
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, 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_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch};
use rustc_errors::Applicability; 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::intravisit::{walk_ty, Visitor};
use rustc_hir::{ use rustc_hir::{
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
Path, QPath, TyKind, UnOp, Pat, PatKind, Path, QPath, TyKind, UnOp,
}; };
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::{Rvalue, StatementKind};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{ use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, TypeVisitableExt, TypeckResults};
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamEnv, ParamTy, ProjectionPredicate, Ty,
TyCtxt, TypeVisitableExt, TypeckResults,
};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol}; 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! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -183,24 +166,6 @@ pub struct Dereferencing<'tcx> {
/// ///
/// e.g. `m!(x) | Foo::Bar(ref x)` /// e.g. `m!(x) | Foo::Bar(ref x)`
ref_locals: FxIndexMap<HirId, Option<RefPat>>, 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)] #[derive(Debug)]
@ -355,52 +320,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
)); ));
}, },
(Some(use_cx), RefOp::AddrOf(mutability)) => { (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. // Find the number of times the borrow is auto-derefed.
let mut iter = use_cx.adjustments.iter(); let mut iter = use_cx.adjustments.iter();
let mut deref_count = 0usize; 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()) TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
}); });
let can_auto_borrow = match use_cx.node { 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<'_>) { 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 { if Some(body.id()) == self.current_body {
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
let replacements = pat.replacements; let replacements = pat.replacements;
@ -729,8 +642,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.current_body = None; self.current_body = None;
} }
} }
extract_msrv_attr!(LateContext);
} }
fn try_parse_ref_op<'tcx>( 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 { fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
if let Some(parent) = get_parent_expr(cx, e) if let Some(parent) = get_parent_expr(cx, e)
&& parent.span.ctxt() == e.span.ctxt() && parent.span.ctxt() == e.span.ctxt()
@ -980,274 +884,6 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
v.0 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 { fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
if let ty::Adt(adt, _) = *ty.kind() { if let ty::Adt(adt, _) = *ty.kind() {
adt.is_struct() && adt.all_fields().any(|f| f.name == name) adt.is_struct() && adt.all_fields().any(|f| f.name == name)

View File

@ -167,7 +167,10 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
return; 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 pre = camel_case_split(first);
let mut post = pre.clone(); let mut post = pre.clone();
post.reverse(); post.reverse();

View File

@ -3,7 +3,6 @@ use clippy_utils::path_res;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Visibility; use rustc_middle::ty::Visibility;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -42,9 +41,10 @@ impl<'tcx> LateLintPass<'tcx> for ErrorImplError {
}; };
match item.kind { match item.kind {
ItemKind::TyAlias(ty, _) if implements_trait(cx, hir_ty_to_ty(cx.tcx, ty), error_def_id, &[]) ItemKind::TyAlias(..) if item.ident.name == sym::Error
&& item.ident.name == sym::Error && is_visible_outside_module(cx, item.owner_id.def_id)
&& 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( span_lint(
cx, cx,

View File

@ -57,54 +57,52 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
} else { } else {
None None
} }
&& let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root())
{ {
find_format_args(cx, write_arg, ExpnId::root(), |format_args| { // ordering is important here, since `writeln!` uses `write!` internally
let calling_macro = let calling_macro = if is_expn_of(write_call.span, "writeln").is_some() {
// ordering is important here, since `writeln!` uses `write!` internally Some("writeln")
if is_expn_of(write_call.span, "writeln").is_some() { } else if is_expn_of(write_call.span, "write").is_some() {
Some("writeln") Some("write")
} else if is_expn_of(write_call.span, "write").is_some() { } else {
Some("write") None
} else { };
None let prefix = if dest_name == "stderr" {
}; "e"
let prefix = if dest_name == "stderr" { } else {
"e" ""
} else { };
""
};
// We need to remove the last trailing newline from the string because the // 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 // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
// used. // used.
let (used, sugg_mac) = if let Some(macro_name) = calling_macro { let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
( (
format!("{macro_name}!({dest_name}(), ...)"), format!("{macro_name}!({dest_name}(), ...)"),
macro_name.replace("write", "print"), macro_name.replace("write", "print"),
) )
} else { } else {
( (
format!("{dest_name}().write_fmt(...)"), format!("{dest_name}().write_fmt(...)"),
"print".into(), "print".into(),
) )
}; };
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let inputs_snippet = snippet_with_applicability( let inputs_snippet = snippet_with_applicability(
cx, cx,
format_args_inputs_span(format_args), format_args_inputs_span(&format_args),
"..", "..",
&mut applicability, &mut applicability,
); );
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
EXPLICIT_WRITE, EXPLICIT_WRITE,
expr.span, expr.span,
&format!("use of `{used}.unwrap()`"), &format!("use of `{used}.unwrap()`"),
"try", "try",
format!("{prefix}{sugg_mac}!({inputs_snippet})"), format!("{prefix}{sugg_mac}!({inputs_snippet})"),
applicability, applicability,
); );
});
} }
} }
} }

View File

@ -246,8 +246,13 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
{ {
self.ty_params.remove(&def_id); 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 { for bound in predicate.bounds {
walk_param_bound(self, bound); walk_param_bound(self, bound);
} }

View File

@ -43,14 +43,10 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat { impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { if let Some(macro_call) = root_macro_call_first_node(cx, expr)
return; && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
}; && let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) { {
return;
}
find_format_args(cx, expr, macro_call.expn, |format_args| {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let call_site = macro_call.span; let call_site = macro_call.span;
@ -91,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
}, },
_ => {}, _ => {},
} }
}); }
} }
} }

View File

@ -186,15 +186,10 @@ impl FormatArgs {
impl<'tcx> LateLintPass<'tcx> for FormatArgs { impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { if let Some(macro_call) = root_macro_call_first_node(cx, expr)
return; && is_format_macro(cx, macro_call.def_id)
}; && let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
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| {
for piece in &format_args.template { for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index && let Ok(index) = placeholder.argument.index
@ -206,12 +201,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
if placeholder.format_trait != FormatTrait::Display if placeholder.format_trait != FormatTrait::Display
|| placeholder.format_options != FormatOptions::default() || placeholder.format_options != FormatOptions::default()
|| is_aliased(format_args, index) || is_aliased(&format_args, index)
{ {
continue; continue;
} }
if let Ok(arg_hir_expr) = arg_expr { 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_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
check_to_string_in_format_args(cx, 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) { 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); extract_msrv_attr!(LateContext);

View File

@ -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) if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
&& let macro_def_id = outer_macro.def_id && let macro_def_id = outer_macro.def_id
&& is_format_macro(cx, 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 {
for piece in &format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece
if let FormatArgsPiece::Placeholder(placeholder) = piece && let trait_name = match placeholder.format_trait {
&& let trait_name = match placeholder.format_trait { FormatTrait::Display => sym::Display,
FormatTrait::Display => sym::Display, FormatTrait::Debug => sym::Debug,
FormatTrait::Debug => sym::Debug, FormatTrait::LowerExp => sym!(LowerExp),
FormatTrait::LowerExp => sym!(LowerExp), FormatTrait::UpperExp => sym!(UpperExp),
FormatTrait::UpperExp => sym!(UpperExp), FormatTrait::Octal => sym!(Octal),
FormatTrait::Octal => sym!(Octal), FormatTrait::Pointer => sym::Pointer,
FormatTrait::Pointer => sym::Pointer, FormatTrait::Binary => sym!(Binary),
FormatTrait::Binary => sym!(Binary), FormatTrait::LowerHex => sym!(LowerHex),
FormatTrait::LowerHex => sym!(LowerHex), FormatTrait::UpperHex => sym!(UpperHex),
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);
} }
&& 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);
} }
}); }
} }
} }

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, ConstKind}; 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<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! { if_chain! {
if !item.span.from_expansion(); 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. // Since static items may not have generics, skip generic const items.
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it // FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
// doesn't account for empty where-clauses that only consist of keyword `where` IINM. // doesn't account for empty where-clauses that only consist of keyword `where` IINM.
if generics.params.is_empty() && !generics.has_where_clause_predicates; 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 ty::Array(element_type, cst) = ty.kind();
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.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); if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);

View File

@ -17,26 +17,20 @@ declare_clippy_lint! {
/// ///
/// ### Example /// ### Example
/// ```rust /// ```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 trigger() {
/// /// large_future([0u8; 16 * 1024]).await;
/// pub async fn test() {
/// let fut = big_fut([0u8; 1024]);
/// wait(fut).await;
/// } /// }
/// ``` /// ```
/// ///
/// `Box::pin` the big future instead. /// `Box::pin` the big future instead.
/// ///
/// ```rust /// ```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 trigger() {
/// /// Box::pin(large_future([0u8; 16 * 1024])).await;
/// pub async fn test() {
/// let fut = Box::pin(big_fut([0u8; 1024]));
/// wait(fut).await;
/// } /// }
/// ``` /// ```
#[clippy::version = "1.70.0"] #[clippy::version = "1.70.0"]

View File

@ -424,6 +424,14 @@ fn check_for_is_empty(
item_name: Symbol, item_name: Symbol,
item_kind: &str, 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 = Symbol::intern("is_empty");
let is_empty = cx let is_empty = cx
.tcx .tcx

View File

@ -230,6 +230,7 @@ mod mutex_atomic;
mod needless_arbitrary_self_type; mod needless_arbitrary_self_type;
mod needless_bool; mod needless_bool;
mod needless_borrowed_ref; mod needless_borrowed_ref;
mod needless_borrows_for_generic_args;
mod needless_continue; mod needless_continue;
mod needless_else; mod needless_else;
mod needless_for_each; mod needless_for_each;
@ -331,6 +332,7 @@ mod unit_return_expecting_ord;
mod unit_types; mod unit_types;
mod unnamed_address; mod unnamed_address;
mod unnecessary_box_returns; mod unnecessary_box_returns;
mod unnecessary_map_on_constructor;
mod unnecessary_owned_empty_strings; mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports; mod unnecessary_self_imports;
mod unnecessary_struct_initialization; mod unnecessary_struct_initialization;
@ -610,7 +612,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
.collect(), .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::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author)); store.register_late_pass(|_| Box::new(utils::author::Author));
let await_holding_invalid_types = conf.await_holding_invalid_types.clone(); 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::NeedlessBool));
store.register_late_pass(|_| Box::new(needless_bool::BoolComparison)); store.register_late_pass(|_| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); 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(eta_reduction::EtaReduction));
store.register_late_pass(|_| Box::new(mut_mut::MutMut)); store.register_late_pass(|_| Box::new(mut_mut::MutMut));
store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed)); 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 allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const; let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const;
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv()))); 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 |_| { store.register_late_pass(move |_| {
Box::new(methods::Methods::new( Box::new(methods::Methods::new(
avoid_breaking_exported_api, avoid_breaking_exported_api,
msrv(), msrv(),
allow_expect_in_tests, allow_expect_in_tests,
allow_unwrap_in_tests, allow_unwrap_in_tests,
allowed_dotfiles.clone(),
)) ))
}); });
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv()))); 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(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::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress)); 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(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend)); store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
let future_size_threshold = conf.future_size_threshold; 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::<reserve_after_initialization::ReserveAfterInitialization>::default());
store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)); 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(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` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View File

@ -5,7 +5,6 @@ use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor}; 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::{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_lint::LateContext;
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty}; 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 l.pat.hir_id == self.var_id;
if let PatKind::Binding(.., ident, _) = l.pat.kind; if let PatKind::Binding(.., ident, _) = l.pat.kind;
then { 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| { self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized { InitializeVisitorState::Initialized {

View File

@ -36,7 +36,8 @@ struct PathAndSpan {
span: Span, 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)] #[derive(Debug, Clone)]
pub struct MacroRefData { pub struct MacroRefData {
name: String, name: String,

View File

@ -8,8 +8,7 @@ use clippy_utils::{
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath}; use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, Guard, ItemKind, Node, Pat, PatKind, Path, QPath};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::sym; 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)); 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` // compare match_expr ty with RetTy in `fn foo() -> RetTy`
Node::Item(..) => { Node::Item(item) => {
if let Some(fn_decl) = p_node.fn_decl() { if let ItemKind::Fn(..) = item.kind {
if let FnRetTy::Return(ret_ty) = fn_decl.output { let output = cx
return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr)); .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 {..} }` // check the parent expr for this whole block `{ match match_expr {..} }`

View File

@ -2,11 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local; use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::{for_each_expr, is_local_used}; 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_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::symbol::Ident;
use rustc_span::Span; use rustc_span::Span;
use std::ops::ControlFlow; use std::ops::ControlFlow;
@ -34,32 +35,45 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
], ],
MatchSource::Normal, MatchSource::Normal,
) = if_expr.kind ) = 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( emit_redundant_guards(
cx, cx,
outer_arm, outer_arm,
if_expr.span, if_expr.span,
scrutinee, pat_span,
arm.pat.span, &binding,
arm.guard, arm.guard,
); );
} }
// `Some(x) if let Some(2) = x` // `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( emit_redundant_guards(
cx, cx,
outer_arm, outer_arm,
let_expr.span, let_expr.span,
let_expr.init, pat_span,
let_expr.pat.span, &binding,
None, None,
); );
} }
// `Some(x) if x == Some(2)` // `Some(x) if x == Some(2)`
// `Some(x) if Some(2) == x`
else if let Guard::If(if_expr) = guard else if let Guard::If(if_expr) = guard
&& let ExprKind::Binary(bin_op, local, pat) = if_expr.kind && let ExprKind::Binary(bin_op, local, pat) = if_expr.kind
&& matches!(bin_op.node, BinOpKind::Eq) && 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 // 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 // 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 // `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. // 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) && 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( emit_redundant_guards(
cx, cx,
outer_arm, outer_arm,
if_expr.span, if_expr.span,
local, pat_span,
pat.span, &binding,
None, 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) { if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) {
let mut span = None; let mut span = None;
let mut byref_ident = None;
let mut multiple_bindings = false; let mut multiple_bindings = false;
// `each_binding` gives the `HirId` of the `Pat` itself, not the binding // `each_binding` gives the `HirId` of the `Pat` itself, not the binding
outer_arm.pat.walk(|pat| { 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 && hir_id == local
&& span.replace(pat.span).is_some()
{ {
multiple_bindings = true; if matches!(bind_annot.0, rustc_ast::ByRef::Yes) {
return false; 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 true
}); });
// Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)` // Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)`
if !multiple_bindings { if !multiple_bindings {
return span.map(|span| { return span.map(|span| PatBindingInfo {
( span,
span, byref_ident,
!matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)), is_field: matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
)
}); });
} }
} }
@ -115,14 +154,11 @@ fn emit_redundant_guards<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
outer_arm: &Arm<'tcx>, outer_arm: &Arm<'tcx>,
guard_span: Span, guard_span: Span,
local: &Expr<'_>,
pat_span: Span, pat_span: Span,
pat_binding: &PatBindingInfo,
inner_guard: Option<Guard<'_>>, inner_guard: Option<Guard<'_>>,
) { ) {
let mut app = Applicability::MaybeIncorrect; 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( span_lint_and_then(
cx, cx,
@ -131,14 +167,21 @@ fn emit_redundant_guards<'tcx>(
"redundant guard", "redundant guard",
|diag| { |diag| {
let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app); 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( diag.multipart_suggestion_verbose(
"try", "try",
vec![ vec![
if can_use_shorthand { suggestion_span,
(pat_binding, binding_replacement.into_owned())
} else {
(pat_binding.shrink_to_hi(), format!(": {binding_replacement}"))
},
( (
guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()), guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()),
inner_guard.map_or_else(String::new, |guard| { inner_guard.map_or_else(String::new, |guard| {

View File

@ -131,13 +131,12 @@ pub(super) fn check<'tcx>(
let mut applicability = Applicability::MachineApplicable; 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 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) { if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
return; && let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn)
} {
find_format_args(cx, arg_root, macro_call.expn, |format_args| { let span = format_args_inputs_span(&format_args);
let span = format_args_inputs_span(format_args);
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -148,7 +147,7 @@ pub(super) fn check<'tcx>(
format!("unwrap_or_else({closure_args} panic!({sugg}))"), format!("unwrap_or_else({closure_args} panic!({sugg}))"),
applicability, applicability,
); );
}); }
return; return;
} }

View File

@ -8,6 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::Binder; use rustc_middle::ty::Binder;
use rustc_span::{sym, Span}; 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) && let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& match_def_path(cx, def_id, &BOOL_THEN) && match_def_path(cx, def_id, &BOOL_THEN)
&& !is_from_proc_macro(cx, expr) && !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(param_snippet) = snippet_opt(cx, param.span)
&& let Some(filter) = snippet_opt(cx, recv.span) && let Some(filter) = snippet_opt(cx, recv.span)
&& let Some(map) = snippet_opt(cx, then_body.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, call_span,
"usage of `bool::then` in `filter_map`", "usage of `bool::then` in `filter_map`",
"use `filter` then `map` instead", "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, Applicability::MachineApplicable,
); );
} }

View File

@ -74,9 +74,11 @@ mod option_map_unwrap_or;
mod or_fun_call; mod or_fun_call;
mod or_then_unwrap; mod or_then_unwrap;
mod path_buf_push_overwrite; mod path_buf_push_overwrite;
mod path_ends_with_ext;
mod range_zip_with_len; mod range_zip_with_len;
mod read_line_without_trim; mod read_line_without_trim;
mod readonly_write_lock; mod readonly_write_lock;
mod redundant_as_str;
mod repeat_once; mod repeat_once;
mod search_is_some; mod search_is_some;
mod seek_from_current; 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::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 clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
use if_chain::if_chain; 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 as hir;
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; 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_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_middle::ty::{self, TraitRef, Ty};
@ -3563,11 +3566,77 @@ declare_clippy_lint! {
"calls to `.take()` or `.skip()` that are out of bounds" "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 { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Msrv, msrv: Msrv,
allow_expect_in_tests: bool, allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool, allow_unwrap_in_tests: bool,
allowed_dotfiles: FxHashSet<String>,
} }
impl Methods { impl Methods {
@ -3577,12 +3646,14 @@ impl Methods {
msrv: Msrv, msrv: Msrv,
allow_expect_in_tests: bool, allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool, allow_unwrap_in_tests: bool,
allowed_dotfiles: FxHashSet<String>,
) -> Self { ) -> Self {
Self { Self {
avoid_breaking_exported_api, avoid_breaking_exported_api,
msrv, msrv,
allow_expect_in_tests, allow_expect_in_tests,
allow_unwrap_in_tests, allow_unwrap_in_tests,
allowed_dotfiles,
} }
} }
} }
@ -3703,6 +3774,8 @@ impl_lint_pass!(Methods => [
FILTER_MAP_BOOL_THEN, FILTER_MAP_BOOL_THEN,
READONLY_WRITE_LOCK, READONLY_WRITE_LOCK,
ITER_OUT_OF_BOUNDS, ITER_OUT_OF_BOUNDS,
PATH_ENDS_WITH_EXT,
REDUNDANT_AS_STR,
]); ]);
/// Extracts a method call name, args, and `Span` of the method name. /// 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_chain! {
if let TraitItemKind::Fn(ref sig, _) = item.kind; if let TraitItemKind::Fn(ref sig, _) = item.kind;
if sig.decl.implicit_self.has_implicit_self(); 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 { 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(); let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
wrong_self_convention::check( wrong_self_convention::check(
cx, cx,
item.ident.name.as_str(), item.ident.name.as_str(),
self_ty, self_ty,
first_arg_ty, first_arg_ty,
first_arg_span, first_arg_hir_ty.span,
false, false,
true, true,
); );
@ -3929,6 +4004,7 @@ impl Methods {
("as_deref" | "as_deref_mut", []) => { ("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name); 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_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
@ -3978,6 +4054,7 @@ impl Methods {
if let ExprKind::MethodCall(.., span) = expr.kind { if let ExprKind::MethodCall(.., span) = expr.kind {
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg); 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", [_]) => { ("expect", [_]) => {
match method_call(recv) { match method_call(recv) {

View 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
);
}
}

View 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,
);
}
}

View File

@ -6,7 +6,8 @@ use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext; 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::sym;
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use std::iter; use std::iter;

View File

@ -1,24 +1,22 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::source::{snippet, snippet_opt, snippet_with_context}; use clippy_utils::source::{snippet, 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::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{ 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, 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; use crate::ref_patterns::REF_PATTERNS;
@ -56,6 +54,7 @@ declare_clippy_lint! {
style, style,
"an entire binding declared as `ref`, in a function argument or a `let` statement" "an entire binding declared as `ref`, in a function argument or a `let` statement"
} }
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for the use of bindings with a single leading /// 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" "using a short circuit boolean condition as a statement"
} }
declare_clippy_lint! { declare_lint_pass!(LintPass => [
/// ### 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 => [
TOPLEVEL_REF_ARG, TOPLEVEL_REF_ARG,
USED_UNDERSCORE_BINDING, USED_UNDERSCORE_BINDING,
SHORT_CIRCUIT_STATEMENT, SHORT_CIRCUIT_STATEMENT,
ZERO_PTR,
]); ]);
impl<'tcx> LateLintPass<'tcx> for LintPass { 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( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
@ -253,50 +214,56 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Cast(e, ty) = expr.kind { if in_external_macro(cx.sess(), expr.span)
self.check_cast(cx, expr.span, e, ty); || expr.span.desugaring_kind().is_some()
|| any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
{
return; return;
} }
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { let (definition_hir_id, ident) = match expr.kind {
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring ExprKind::Path(ref qpath) => {
return; if let QPath::Resolved(None, path) = qpath
} && let Res::Local(id) = path.res
let sym; && is_used(cx, expr)
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))
{ {
Some(binding) (id, last_path_segment(qpath).ident)
} else { } else {
None return;
} }
}, },
ExprKind::Field(_, ident) => { ExprKind::Field(recv, ident) => {
sym = ident.name; if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
let name = sym.as_str(); && let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
if name.starts_with('_') && !name.starts_with("__") { && let Some(local_did) = field.did.as_local()
Some(name) && 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 { } 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, cx,
USED_UNDERSCORE_BINDING, USED_UNDERSCORE_BINDING,
expr.span, expr.span,
&format!( &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" 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), _ => 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);
}
}
}
}

View File

@ -7,7 +7,6 @@ use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID; use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Constness, FnDecl, GenericParamKind}; use rustc_hir::{Body, Constness, FnDecl, GenericParamKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -124,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
FnKind::Method(_, sig, ..) => { FnKind::Method(_, sig, ..) => {
if trait_ref_of_method(cx, def_id).is_some() if trait_ref_of_method(cx, def_id).is_some()
|| already_const(sig.header) || already_const(sig.header)
|| method_accepts_droppable(cx, sig.decl.inputs) || method_accepts_droppable(cx, def_id)
{ {
return; 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 /// 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. /// 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 // If any of the params are droppable, return true
param_tys.iter().any(|hir_ty| { sig.inputs().iter().any(|&ty| has_drop(cx, ty))
let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
has_drop(cx, ty_ty)
})
} }
// We don't have to lint on something that's already `const` // We don't have to lint on something that's already `const`

View File

@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true; self.found = true;
return; return;
}, },
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => { ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj if adj

View 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
}

View File

@ -1,6 +1,7 @@
use super::needless_pass_by_value::requires_exact_signature; use super::needless_pass_by_value::requires_exact_signature;
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet; 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 clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability; 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, 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_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_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::associated_body; use rustc_middle::hir::map::associated_body;
use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::hir::nested_filter::OnlyBodies;
@ -21,6 +22,8 @@ use rustc_span::symbol::kw;
use rustc_span::Span; use rustc_span::Span;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use core::ops::ControlFlow;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Check if a `&mut` function argument is actually used mutably. /// Check if a `&mut` function argument is actually used mutably.
@ -95,6 +98,30 @@ fn should_skip<'tcx>(
is_from_proc_macro(cx, &input) 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> { impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
fn check_fn( fn check_fn(
&mut self, &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); euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
if is_async { if is_async {
let mut checked_closures = FxHashSet::default(); let mut checked_closures = FxHashSet::default();
while !ctx.async_closures.is_empty() {
let closures = ctx.async_closures.clone(); // We retrieve all the closures declared in the async function because they will
ctx.async_closures.clear(); // not be found by `euv::Delegate`.
let hir = cx.tcx.hir(); let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
for closure in closures { for_each_expr_with_closures(cx, body, |expr| {
if !checked_closures.insert(closure) { if let ExprKind::Closure(closure) = expr.kind {
continue; closures.insert(closure.def_id);
}
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);
}
} }
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 ctx
@ -244,6 +268,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
struct MutablyUsedVariablesCtxt<'tcx> { struct MutablyUsedVariablesCtxt<'tcx> {
mutably_used_vars: HirIdSet, mutably_used_vars: HirIdSet,
prev_bind: Option<HirId>, 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, prev_move_to_closure: HirIdSet,
aliases: HirIdMap<HirId>, aliases: HirIdMap<HirId>,
async_closures: FxHashSet<LocalDefId>, 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) { fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
self.prev_bind = None; self.prev_bind = None;
if let euv::Place { 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, base_ty,
.. ..
} = &cmt.place } = &cmt.place

View File

@ -5,10 +5,8 @@ use clippy_utils::{get_parent_node, is_lint_allowed, peel_blocks};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{ use rustc_hir::{
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, FnRetTy, ItemKind, Node, PatKind, Stmt, StmtKind, is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, ItemKind, Node, PatKind, Stmt, StmtKind, UnsafeSource,
UnsafeSource,
}; };
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt as _; use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -99,14 +97,13 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|diag| { |diag| {
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) { for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
if let Node::Item(item) = parent.1 if let Node::Item(item) = parent.1
&& let ItemKind::Fn(sig, ..) = item.kind && let ItemKind::Fn(..) = item.kind
&& let FnRetTy::Return(ret_ty) = sig.decl.output
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) && let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
&& let [.., final_stmt] = block.stmts && let [.., final_stmt] = block.stmts
&& final_stmt.hir_id == stmt.hir_id && final_stmt.hir_id == stmt.hir_id
{ {
let expr_ty = cx.typeck_results().expr_ty(expr); 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` // Remove `impl Future<Output = T>` to get `T`
if cx.tcx.ty_is_opaque_future(ret_ty) && 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; ret_ty = true_ret_ty;
} }
if ret_ty == expr_ty { if !ret_ty.is_unit() && ret_ty == expr_ty {
diag.span_suggestion( diag.span_suggestion(
stmt.span.shrink_to_lo(), stmt.span.shrink_to_lo(),
"did you mean to return it?", "did you mean to return it?",

View File

@ -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 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_errors::Applicability;
use rustc_hir::def_id::LocalDefId; 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::EarlyBinder; use rustc_middle::ty::EarlyBinder;
use rustc_session::{declare_lint_pass, declare_tool_lint}; 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()) { if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
return; 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 { let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
return; return;
}; };
@ -180,17 +177,8 @@ impl LateLintPass<'_> for NonCanonicalImpls {
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id) if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id)
&& impl_item.ident.name == sym::partial_cmp && impl_item.ident.name == sym::partial_cmp
&& let Some(ord_def_id) = cx && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
.tcx && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
.diagnostic_items(trait_impl.def_id.krate)
.name_to_id
.get(&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 // 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 // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't

View File

@ -13,7 +13,6 @@ use rustc_hir::def_id::DefId;
use rustc_hir::{ use rustc_hir::{
BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp, 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_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId}; use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
use rustc_middle::ty::adjustment::Adjust; 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 { impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
if let ItemKind::Const(hir_ty, _generics, body_id) = it.kind { if let ItemKind::Const(.., body_id) = it.kind {
let ty = hir_ty_to_ty(cx.tcx, hir_ty); 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) { if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
lint(cx, Source::Item { item: it.span }); 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<'_>) { 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 { if let TraitItemKind::Const(_, body_id_opt) = &trait_item.kind {
let ty = hir_ty_to_ty(cx.tcx, hir_ty); let ty = cx.tcx.type_of(trait_item.owner_id).instantiate_identity();
// Normalize assoc types because ones originated from generic params // Normalize assoc types because ones originated from generic params
// bounded other traits could have their bound. // 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<'_>) { 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_def_id = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id;
let item = cx.tcx.hir().expect_item(item_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 // we should use here as a frozen variant is a potential to be frozen
// similar to unknown layouts. // similar to unknown layouts.
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);` // 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); let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, normalized); if is_unfrozen(cx, normalized);
if is_value_unfrozen_poly(cx, *body_id, 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, .. }) => { 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. // Normalize assoc types originated from generic params.
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);

View File

@ -16,7 +16,6 @@ use rustc_hir::{
ImplItemKind, ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, ImplItemKind, ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind,
TyKind, Unsafety, TyKind, Unsafety,
}; };
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{Obligation, ObligationCause}; use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -172,13 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
for arg in check_fn_args( for arg in check_fn_args(
cx, cx,
cx.tcx cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(),
.fn_sig(item.owner_id)
.instantiate_identity()
.skip_binder()
.inputs(),
sig.decl.inputs, sig.decl.inputs,
&sig.decl.output,
&[], &[],
) )
.filter(|arg| arg.mutability() == Mutability::Not) .filter(|arg| arg.mutability() == Mutability::Not)
@ -237,7 +231,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
let decl = sig.decl; let decl = sig.decl;
let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); 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) .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
.collect(); .collect();
let results = check_ptr_arg_usage(cx, body, &lint_args); let results = check_ptr_arg_usage(cx, body, &lint_args);
@ -443,12 +437,13 @@ impl<'tcx> DerefTy<'tcx> {
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn check_fn_args<'cx, 'tcx: 'cx>( fn check_fn_args<'cx, 'tcx: 'cx>(
cx: &'cx LateContext<'tcx>, cx: &'cx LateContext<'tcx>,
tys: &'tcx [Ty<'tcx>], fn_sig: ty::FnSig<'tcx>,
hir_tys: &'tcx [hir::Ty<'tcx>], hir_tys: &'tcx [hir::Ty<'tcx>],
ret_ty: &'tcx FnRetTy<'tcx>,
params: &'tcx [Param<'tcx>], params: &'tcx [Param<'tcx>],
) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx { ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
tys.iter() fn_sig
.inputs()
.iter()
.zip(hir_tys.iter()) .zip(hir_tys.iter())
.enumerate() .enumerate()
.filter_map(move |(i, (ty, hir_ty))| { .filter_map(move |(i, (ty, hir_ty))| {
@ -494,9 +489,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
}) })
{ {
if !lifetime.is_anonymous() if !lifetime.is_anonymous()
&& let FnRetTy::Return(ret_ty) = ret_ty && fn_sig.output()
&& let ret_ty = hir_ty_to_ty(cx.tcx, ret_ty)
&& ret_ty
.walk() .walk()
.filter_map(|arg| { .filter_map(|arg| {
arg.as_region().and_then(|lifetime| { arg.as_region().and_then(|lifetime| {

View File

@ -105,8 +105,9 @@ impl EarlyLintPass for RawStrings {
} }
}, },
); );
if !matches!(cx.get_lint_level(NEEDLESS_RAW_STRINGS), rustc_lint::Allow) {
return; return;
}
} }
let req = { let req = {

View File

@ -3,11 +3,9 @@ use clippy_utils::is_from_proc_macro;
use clippy_utils::ty::needs_ordered_drop; use clippy_utils::ty::needs_ordered_drop;
use rustc_ast::Mutability; use rustc_ast::Mutability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::{ use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath,
};
use rustc_lint::{LateContext, LateLintPass, LintContext}; 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_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use rustc_span::DesugaringKind; use rustc_span::DesugaringKind;
@ -72,9 +70,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
// the local is user-controlled // the local is user-controlled
if !in_external_macro(cx.sess(), local.span); if !in_external_macro(cx.sess(), local.span);
if !is_from_proc_macro(cx, expr); 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 { then {
span_lint_and_help( span_lint_and_help(
cx, 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. /// Check if a rebinding of a local affects the code's drop behavior.
fn affects_drop_behavior<'tcx>( fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool {
cx: &LateContext<'tcx>,
bind: HirId,
rebind: HirId,
rebind_expr: &Expr<'tcx>,
) -> bool {
let hir = cx.tcx.hir(); let hir = cx.tcx.hir();
// the rebinding is in a different scope than the original binding // the rebinding is in a different scope than the original binding

View File

@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. }) if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
&& let item = cx.tcx.hir().item(id) && let item = cx.tcx.hir().item(id)
&& let ItemKind::Impl(Impl { && let ItemKind::Impl(Impl {
items, items,
of_trait, of_trait,
self_ty, self_ty,
.. ..
}) = &item.kind }) = &item.kind
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
{ {
if !map.contains_key(res) { if !map.contains_key(res) {

View File

@ -28,35 +28,43 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
return false; return false;
} }
match arg.kind { let casts_peeled = peel_casts(arg);
match casts_peeled.kind {
// Catching: // Catching:
// transmute over constants that resolve to `null`. // 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); lint_expr(cx, expr);
true 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: // Catching:
// `std::mem::transmute(std::ptr::null::<i32>())` // `std::mem::transmute(std::ptr::null::<i32>())`
ExprKind::Call(func1, []) if is_path_diagnostic_item(cx, func1, sym::ptr_null) => { ExprKind::Call(func1, []) if is_path_diagnostic_item(cx, func1, sym::ptr_null) => {
lint_expr(cx, expr); lint_expr(cx, expr);
true true
}, },
_ => { _ => {
// FIXME: // FIXME:
// Also catch transmutations of variables which are known nulls. // Also catch transmutations of variables which are known nulls.
// To do this, MIR const propagation seems to be the better tool. // To do this, MIR const propagation seems to be the better tool.
// Whenever MIR const prop routines are more developed, this will // Whenever MIR const prop routines are more developed, this will
// become available. As of this writing (25/03/19) it is not yet. // 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 false
}, },
} }
} }
fn peel_casts<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
match &expr.kind {
ExprKind::Cast(inner_expr, _) => peel_casts(inner_expr),
_ => expr,
}
}

View File

@ -315,7 +315,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
fn check_fn( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'_>, cx: &LateContext<'_>,
_: FnKind<'_>, fn_kind: FnKind<'_>,
decl: &FnDecl<'_>, decl: &FnDecl<'_>,
_: &Body<'_>, _: &Body<'_>,
_: Span, _: Span,
@ -340,6 +340,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
CheckTyContext { CheckTyContext {
is_in_trait_impl, is_in_trait_impl,
is_exported, is_exported,
in_body: matches!(fn_kind, FnKind::Closure),
..CheckTyContext::default() ..CheckTyContext::default()
}, },
); );
@ -427,7 +428,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
cx, cx,
ty, ty,
CheckTyContext { CheckTyContext {
is_local: true, in_body: true,
..CheckTyContext::default() ..CheckTyContext::default()
}, },
); );
@ -481,7 +482,7 @@ impl Types {
} }
match hir_ty.kind { 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 hir_id = hir_ty.hir_id;
let res = cx.qpath_res(qpath, hir_id); let res = cx.qpath_res(qpath, hir_id);
if let Some(def_id) = res.opt_def_id() { if let Some(def_id) = res.opt_def_id() {
@ -581,8 +582,8 @@ impl Types {
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
struct CheckTyContext { struct CheckTyContext {
is_in_trait_impl: bool, is_in_trait_impl: bool,
/// `true` for types on local variables. /// `true` for types on local variables and in closure signatures.
is_local: bool, in_body: bool,
/// `true` for types that are part of the public API. /// `true` for types that are part of the public API.
is_exported: bool, is_exported: bool,
is_nested_call: bool, is_nested_call: bool,

View File

@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind}; use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind};
use rustc_lint::{LateContext, LintContext}; 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 rustc_middle::ty;
use super::LET_UNIT_VALUE; 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 if let Some(init) = local.init
&& !local.pat.span.from_expansion() && !local.pat.span.from_expansion()
&& !in_external_macro(cx.sess(), local.span) && !in_external_macro(cx.sess(), local.span)
&& !is_from_async_await(local.span)
&& cx.typeck_results().pat_ty(local.pat).is_unit() && cx.typeck_results().pat_ty(local.pat).is_unit()
{ {
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer)) if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))

View 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,
);
}
}
}
}

View File

@ -8,10 +8,14 @@ use rustc_errors::Applicability;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind}; 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_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_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span}; use rustc_span::{sym, Span};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### 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` /// 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> { /// iff all of the bounds also hold for the type of the `.into_iter()` receiver.
cx.tcx /// ```ignore
.predicates_of(fn_did) /// pub fn foo<I>(i: I)
.predicates /// where I: IntoIterator<Item=i32> + ExactSizeIterator
.iter() /// ^^^^^^^^^^^^^^^^^ this extra bound stops us from suggesting to remove `.into_iter()` ...
.find_map(|&(ref pred, span)| { /// {
if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() /// assert_eq!(i.len(), 3);
&& tr.def_id() == into_iter_did /// }
&& tr.self_ty().is_param(param_index) ///
{ /// pub fn bar() {
Some(span) /// foo([1, 2, 3].into_iter());
} else { /// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
None /// }
/// ```
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. /// 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) // `fn_sig` does not ICE. (see #11065)
&& cx.tcx.opt_def_kind(did).is_some_and(DefKind::is_fn_like) => && 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, _) => { ExprKind::MethodCall(.., args, _) => {
cx.typeck_results().type_dependent_def_id(parent.hir_id) 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, _ => 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 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 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(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 Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
&& let ty::Param(param) = into_iter_param.kind() && 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 && self.expn_depth == 0
{ {
// Get the "innermost" `.into_iter()` call, e.g. given this expression: // Get the "innermost" `.into_iter()` call, e.g. given this expression:

View File

@ -542,11 +542,11 @@ define_Conf! {
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS. /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
/// ///
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block /// 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. /// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
/// ///
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block /// 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. /// Lint: UNNECESSARY_RAW_STRING_HASHES.
/// ///
/// Whether to allow `r#""#` when `r""` can be used /// Whether to allow `r#""#` when `r""` can be used
@ -561,6 +561,11 @@ define_Conf! {
/// Which crates to allow absolute paths from /// Which crates to allow absolute paths from
(absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> = (absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> =
rustc_data_structures::fx::FxHashSet::default()), 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 /// Lint: EXPLICIT_ITER_LOOP
/// ///
/// Whether to recommend using implicit into iter for reborrowed values. /// Whether to recommend using implicit into iter for reborrowed values.

View File

@ -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 clippy_utils::source::snippet_opt;
use itertools::Itertools; 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_lexer::{tokenize, TokenKind};
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::hygiene; use rustc_span::{hygiene, Span};
use std::iter::once; use std::iter::once;
use std::mem;
use std::rc::Rc;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -17,7 +20,12 @@ declare_clippy_lint! {
"collects `format_args` AST nodes for use in later lints" "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 { impl EarlyLintPass for FormatArgsCollector {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
@ -26,9 +34,17 @@ impl EarlyLintPass for FormatArgsCollector {
return; 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 /// Detects if the format string or an argument has its span set by a proc macro to something inside

View File

@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::ConstValue; use rustc_middle::mir::ConstValue;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;

View File

@ -5,10 +5,9 @@ use if_chain::if_chain;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_hir::Item; use rustc_hir::Item;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::fast_reject::SimplifiedType; 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_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol; 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()); let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! { if_chain! {
if mod_name.as_str() == "paths"; if mod_name.as_str() == "paths";
if let hir::ItemKind::Const(ty, _, body_id) = item.kind; if let hir::ItemKind::Const(.., 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();
let body = cx.tcx.hir().body(body_id); let body = cx.tcx.hir().body(body_id);
let typeck_results = cx.tcx.typeck_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); if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
let path: Vec<&str> = path if let Some(path) = path
.iter() .iter()
.map(|x| { .map(|x| {
if let Constant::Str(s) = x { if let Constant::Str(s) = x {
s.as_str() Some(s.as_str())
} else { } else {
// We checked the type of the constant above None
unreachable!()
} }
}) })
.collect(); .collect::<Option<Vec<&str>>>();
if !check_path(cx, &path[..]); if !check_path(cx, &path[..]);
then { then {
span_lint(cx, INVALID_PATHS, item.span, "invalid path"); span_lint(cx, INVALID_PATHS, item.span, "invalid path");

View File

@ -31,7 +31,7 @@ use serde::{Serialize, Serializer};
use std::collections::{BTreeSet, BinaryHeap}; use std::collections::{BTreeSet, BinaryHeap};
use std::fmt; use std::fmt;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::fs::{self, OpenOptions}; use std::fs::{self, File};
use std::io::prelude::*; use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
@ -229,25 +229,10 @@ impl Drop for MetadataCollector {
collect_renames(&mut lints); collect_renames(&mut lints);
// Outputting json // Outputting json
if Path::new(JSON_OUTPUT_FILE).exists() { fs::write(JSON_OUTPUT_FILE, serde_json::to_string_pretty(&lints).unwrap()).unwrap();
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();
// Outputting markdown // Outputting markdown
if Path::new(MARKDOWN_OUTPUT_FILE).exists() { let mut file = File::create(MARKDOWN_OUTPUT_FILE).unwrap();
fs::remove_file(MARKDOWN_OUTPUT_FILE).unwrap();
}
let mut file = OpenOptions::new()
.write(true)
.create(true)
.open(MARKDOWN_OUTPUT_FILE)
.unwrap();
writeln!( writeln!(
file, file,
"<!-- "<!--
@ -261,17 +246,15 @@ Please use that command to update the file and do not edit it by hand.
.unwrap(); .unwrap();
// Write configuration links to CHANGELOG.md // Write configuration links to CHANGELOG.md
let mut changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap(); let changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap();
let mut changelog_file = OpenOptions::new().read(true).write(true).open(CHANGELOG_PATH).unwrap(); let mut changelog_file = File::create(CHANGELOG_PATH).unwrap();
let position = changelog
if let Some(position) = changelog.find("<!-- begin autogenerated links to configuration documentation -->") { .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 .unwrap();
// we can do AFAIK.
changelog = changelog[..position].to_string();
}
writeln!( writeln!(
changelog_file, 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) self.configs_to_markdown(ClippyConfiguration::to_markdown_link)
) )
.unwrap(); .unwrap();

View File

@ -5,9 +5,8 @@ use clippy_utils::{match_def_path, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext}; 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}; use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! { declare_clippy_lint! {
@ -25,16 +24,14 @@ impl LateLintPass<'_> for MsrvAttrImpl {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! { if_chain! {
if let hir::ItemKind::Impl(hir::Impl { if let hir::ItemKind::Impl(hir::Impl {
of_trait: Some(lint_pass_trait_ref), of_trait: Some(_),
self_ty,
items, items,
.. ..
}) = &item.kind; }) = &item.kind;
if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id(); 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, lint_pass_trait_def_id, &paths::LATE_LINT_PASS); let is_late_pass = match_def_path(cx, trait_ref.def_id, &paths::LATE_LINT_PASS);
if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS); if is_late_pass || match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS);
let self_ty = hir_ty_to_ty(cx.tcx, self_ty); if let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind();
if let ty::Adt(self_ty_def, _) = self_ty.kind();
if self_ty_def.is_struct(); if self_ty_def.is_struct();
if self_ty_def.all_fields().any(|f| { if self_ty_def.all_fields().any(|f| {
cx.tcx cx.tcx

View File

@ -10,7 +10,8 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, Local, Mutability, Node}; use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
use rustc_lint::{LateContext, LateLintPass}; 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_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol; 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(), cx.tcx.type_of(def_id).instantiate_identity(),
), ),
Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? { 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()) read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity())
}, },
_ => None, _ => None,

View File

@ -304,7 +304,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
_ => return, _ => 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!())` // ignore `writeln!(w)` and `write!(v, some_macro!())`
if format_args.span.from_expansion() { if format_args.span.from_expansion() {
return; return;
@ -312,15 +312,15 @@ impl<'tcx> LateLintPass<'tcx> for Write {
match diag_name { match diag_name {
sym::print_macro | sym::eprint_macro | sym::write_macro => { sym::print_macro | sym::eprint_macro | sym::write_macro => {
check_newline(cx, format_args, &macro_call, name); check_newline(cx, &format_args, &macro_call, name);
}, },
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
check_empty_string(cx, format_args, &macro_call, name); check_empty_string(cx, &format_args, &macro_call, name);
}, },
_ => {}, _ => {},
} }
check_literal(cx, format_args, name); check_literal(cx, &format_args, name);
if !self.in_debug_impl { if !self.in_debug_impl {
for piece in &format_args.template { for piece in &format_args.template {
@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
} }
} }
} }
}); }
} }
} }

View File

@ -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()))), ty::RawPtr(_) => Some(Constant::RawPtr(int.assert_bits(int.size()))),
_ => None, _ => 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)?; let data = cv.try_get_slice_bytes_for_diagnostics(lcx.tcx)?;
String::from_utf8(data.to_owned()).ok().map(Constant::Str) String::from_utf8(data.to_owned()).ok().map(Constant::Str)
} },
mir::Const::Val(ConstValue::Indirect { alloc_id, offset: _ }, _) => { mir::Const::Val(ConstValue::Indirect { alloc_id, offset: _ }, _) => {
let alloc = lcx.tcx.global_alloc(alloc_id).unwrap_memory(); let alloc = lcx.tcx.global_alloc(alloc_id).unwrap_memory();
match result.ty().kind() { match result.ty().kind() {

View File

@ -83,9 +83,9 @@ pub fn span_lint_and_help<T: LintContext>(
cx.struct_span_lint(lint, span, msg.to_string(), |diag| { cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
let help = help.to_string(); let help = help.to_string();
if let Some(help_span) = help_span { if let Some(help_span) = help_span {
diag.span_help(help_span, help.to_string()); diag.span_help(help_span, help);
} else { } else {
diag.help(help.to_string()); diag.help(help);
} }
docs_link(diag, lint); docs_link(diag, lint);
diag diag

View File

@ -1785,6 +1785,33 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
None 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 /// Returns `true` if the lint is allowed in the current context. This is useful for
/// skipping long running code when it's unnecessary /// 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. /// Checks if the given function kind is an async function.
pub fn is_async_fn(kind: FnKind<'_>) -> bool { pub fn is_async_fn(kind: FnKind<'_>) -> bool {
match kind { 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::Method(_, sig) => sig.header.asyncness.is_async(),
FnKind::Closure => false, FnKind::Closure => false,
} }

View File

@ -10,8 +10,9 @@ use rustc_lint::LateContext;
use rustc_span::def_id::DefId; use rustc_span::def_id::DefId;
use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol}; 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::ops::ControlFlow;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ 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 /// 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 /// assumption that the early pass that populates the map and the later late passes will all be
/// running on the same thread. /// 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); static CALLED: AtomicBool = AtomicBool::new(false);
debug_assert!( debug_assert!(
!CALLED.swap(true, Ordering::SeqCst), !CALLED.swap(true, Ordering::SeqCst),
"incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread", "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 /// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
/// `FormatArgsCollector` /// `expn_id`
pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) { pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<Rc<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)) {
let format_args_expr = for_each_expr(start, |expr| { let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt(); let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) { 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 { } else {
ControlFlow::Continue(Descend::No) ControlFlow::Continue(Descend::No)
} }
}); })?;
if let Some(expr) = format_args_expr { AST_FORMAT_ARGS.with(|ast_format_args| {
AST_FORMAT_ARGS.with(|ast_format_args| { ast_format_args
ast_format_args.borrow().get(&expr.span).map(callback); .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 /// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if

View File

@ -1,7 +1,8 @@
use rustc_hir::{Expr, HirId}; use rustc_hir::{Expr, HirId};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{ 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; 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`. /// 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( visit_local_usage(
&[local], &[local],
mir, mir,
@ -91,11 +116,14 @@ pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle:
) )
.map(|mut vec| { .map(|mut vec| {
let LocalUsage { local_use_locs, .. } = vec.remove(0); let LocalUsage { local_use_locs, .. } = vec.remove(0);
local_use_locs let mut locations = local_use_locs
.into_iter() .into_iter()
.filter(|location| !is_local_assignment(mir, local, *location)) .filter(|&location| !is_local_assignment(mir, local, location));
.count() if let Some(location) = locations.next() {
== 1 locations.next().is_none() && !block_in_cycle(mir, location.block)
} else {
false
}
}) })
} }

View File

@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
use rustc_session::Session; use rustc_session::Session;
use rustc_span::source_map::{original_sp, SourceMap}; 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::borrow::Cow;
use std::ops::Range; use std::ops::Range;

View File

@ -13,7 +13,8 @@ use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext; 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::traits::EvaluationResult;
use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{ use rustc_middle::ty::{

View File

@ -208,8 +208,7 @@ fn path_segment_certainty(
if cx.tcx.res_generics_def_id(path_segment.res).is_some() { if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
let generics = cx.tcx.generics_of(def_id); let generics = cx.tcx.generics_of(def_id);
let count = generics.params.len() - generics.host_effect_index.is_some() as usize; 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) Certainty::Certain(None)
} else { } else {
Certainty::Uncertain 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. // Check that all type parameters appear in the functions input types.
(0..(generics.parent_count + generics.params.len()) as u32).all(|index| { (0..(generics.parent_count + generics.params.len()) as u32).all(|index| {
Some(index as usize) == generics.host_effect_index || fn_sig Some(index as usize) == generics.host_effect_index
.inputs() || fn_sig
.iter() .inputs()
.any(|input_ty| contains_param(*input_ty.skip_binder(), index)) .iter()
.any(|input_ty| contains_param(*input_ty.skip_binder(), index))
}) })
} }

View File

@ -18,7 +18,6 @@ use test_utils::IS_RUSTC_TEST_SUITE;
// in the depinfo file (otherwise cargo thinks they are unused) // in the depinfo file (otherwise cargo thinks they are unused)
extern crate clippy_lints; extern crate clippy_lints;
extern crate clippy_utils; extern crate clippy_utils;
extern crate derive_new;
extern crate futures; extern crate futures;
extern crate if_chain; extern crate if_chain;
extern crate itertools; extern crate itertools;
@ -33,7 +32,6 @@ mod test_utils;
static TEST_DEPENDENCIES: &[&str] = &[ static TEST_DEPENDENCIES: &[&str] = &[
"clippy_lints", "clippy_lints",
"clippy_utils", "clippy_utils",
"derive_new",
"futures", "futures",
"if_chain", "if_chain",
"itertools", "itertools",

View File

@ -0,0 +1 @@
literal-representation-threshold = 0xFFFFFF

View File

@ -0,0 +1,6 @@
#![warn(clippy::decimal_literal_representation)]
fn main() {
let _ = 8388608;
let _ = 0x00FF_FFFF;
//~^ ERROR: integer literal has a better hexadecimal representation
}

View File

@ -0,0 +1,6 @@
#![warn(clippy::decimal_literal_representation)]
fn main() {
let _ = 8388608;
let _ = 16777215;
//~^ ERROR: integer literal has a better hexadecimal representation
}

View File

@ -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

View File

@ -0,0 +1 @@
allowed-scripts = ["Cyrillic"]

View File

@ -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
}

View File

@ -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

View File

@ -0,0 +1 @@
enum-variant-name-threshold = 5

View 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() {}

View 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

View File

@ -0,0 +1 @@
enum-variant-size-threshold = 500

View 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() {}

View 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() {}

View 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

View File

@ -0,0 +1 @@
enum-variant-name-threshold = 0

View File

@ -0,0 +1,3 @@
enum Actions {}
fn main() {}

View File

@ -0,0 +1 @@
enforce-iter-loop-reborrow = true

View 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
}

View 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
}

View 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

View File

@ -0,0 +1 @@
stack-size-threshold = 1000

View 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() {}

View 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

View File

@ -0,0 +1 @@
pass-by-value-size-limit = 512

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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

View File

@ -0,0 +1 @@
matches-for-let-else = "AllTypes"

View 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 };
}

View 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,
};
}

View 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

View File

@ -0,0 +1 @@
allowed-dotfiles = ["dot"]

View 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