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

Clippy subtree update

r? `@Manishearth`
This commit is contained in:
bors 2025-09-05 02:01:50 +00:00
commit 91edc3ebcc
202 changed files with 4541 additions and 1646 deletions

View file

@ -23,3 +23,10 @@ split-debuginfo = "unpacked"
rustflags = ["--remap-path-prefix", "=clippy_dev"]
[profile.dev.package.lintcheck]
rustflags = ["--remap-path-prefix", "=lintcheck"]
# quine-mc_cluskey makes up a significant part of the runtime in dogfood
# due to the number of conditions in the clippy_lints crate
# and enabling optimizations for that specific dependency helps a bit
# without increasing total build times.
[profile.dev.package.quine-mc_cluskey]
opt-level = 3

View file

@ -30,6 +30,8 @@ Current stable, released 2025-08-07
* Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`,
`transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc)
[#14703](https://github.com/rust-lang/rust-clippy/pull/14703)
* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default)
[#15287](https://github.com/rust-lang/rust-clippy/pull/15287)
### Enhancements
@ -74,6 +76,9 @@ Current stable, released 2025-08-07
[#14719](https://github.com/rust-lang/rust-clippy/pull/14719)
* [`unnecessary_to_owned`] fixed FP when map key is a reference
[#14834](https://github.com/rust-lang/rust-clippy/pull/14834)
* [`swap_with_temporary`]: fix false positive leading to different semantics
being suggested, and use the right number of dereferences in suggestion
[#15172](https://github.com/rust-lang/rust-clippy/pull/15172)
### ICE Fixes
@ -6707,6 +6712,7 @@ Released 2018-09-13
[`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
[`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods
[`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names

View file

@ -485,6 +485,16 @@ The maximum cognitive complexity a function can have
* [`cognitive_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity)
## `const-literal-digits-threshold`
The minimum digits a const float literal must have to supress the `excessive_precicion` lint
**Default Value:** `30`
---
**Affected lints:**
* [`excessive_precision`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision)
## `disallowed-macros`
The list of disallowed macros, written as fully qualified paths.
@ -555,7 +565,7 @@ default configuration of Clippy. By default, any configuration will replace the
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
---
**Affected lints:**
@ -873,7 +883,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics)
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or)
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
* [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark)
* [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
@ -881,7 +890,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`repeat_vec_with_capacity`](https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity)
* [`same_item_push`](https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push)
* [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current)
* [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind)
* [`to_digit_is_some`](https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some)
* [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref)
* [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions)

View file

@ -6,12 +6,12 @@ lint-commented-code = true
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead"
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead"
[[disallowed-methods]]
path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"
reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"

View file

@ -33,6 +33,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"GPLv2", "GPLv3",
"GitHub", "GitLab",
"IPv4", "IPv6",
"InfiniBand", "RoCE",
"ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
"PowerPC", "WebAssembly",
"NaN", "NaNs",
@ -569,6 +570,9 @@ define_Conf! {
/// The maximum cognitive complexity a function can have
#[lints(cognitive_complexity)]
cognitive_complexity_threshold: u64 = 25,
/// The minimum digits a const float literal must have to supress the `excessive_precicion` lint
#[lints(excessive_precision)]
const_literal_digits_threshold: usize = 30,
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
///
/// Use the Cognitive Complexity lint instead.
@ -775,7 +779,6 @@ define_Conf! {
needless_borrow,
non_std_lazy_statics,
option_as_ref_deref,
option_map_unwrap_or,
ptr_as_ptr,
question_mark,
redundant_field_names,
@ -783,7 +786,6 @@ define_Conf! {
repeat_vec_with_capacity,
same_item_push,
seek_from_current,
seek_rewind,
to_digit_is_some,
transmute_ptr_to_ref,
tuple_array_conversions,

View file

@ -27,6 +27,10 @@ url = "2.2"
[dev-dependencies]
walkdir = "2.3"
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(bootstrap)']
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]
rustc_private = true

View file

@ -3,10 +3,10 @@ use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_no
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item};
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::{is_expr_final_block_expr, path_res, sym};
use clippy_utils::{path_res, sym};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::declare_lint_pass;
@ -77,17 +77,20 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
_ => return,
};
span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| {
let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" };
let mut app = Applicability::MachineApplicable;
diag.span_suggestion(
macro_call.span,
"replace with",
format!(
"{}.{replacement}(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
let recv = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0;
// `assert!` doesn't return anything, but `Result::unwrap(_err)` does, so we might need to add a
// semicolon to the suggestion to avoid leaking the type
let sugg = match cx.tcx.parent_hir_node(e.hir_id) {
// trailing expr of a block
Node::Block(..) => format!("{recv}.{replacement}();"),
// already has a trailing semicolon
Node::Stmt(..) => format!("{recv}.{replacement}()"),
// this is the last-resort option, because it's rather verbose
_ => format!("{{ {recv}.{replacement}(); }}"),
};
diag.span_suggestion(macro_call.span, "replace with", sugg, app);
});
}
}

View file

@ -1,8 +1,12 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use clippy_utils::is_expr_async_block;
use clippy_utils::source::walk_span_to_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath};
use rustc_hir::{
Block, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -87,31 +91,37 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
let expr_ty = typeck_results.expr_ty(body_expr);
if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
let return_expr_span = match &body_expr.kind {
// XXXkhuey there has to be a better way.
ExprKind::Block(block, _) => block.expr.map(|e| e.span),
ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
_ => None,
let (return_expr, return_expr_span) = match &body_expr.kind {
ExprKind::Block(Block { expr: Some(e), .. }, _) => (*e, e.span),
ExprKind::Path(QPath::Resolved(_, path)) => (body_expr, path.span),
_ => return,
};
if let Some(return_expr_span) = return_expr_span {
span_lint_hir_and_then(
cx,
ASYNC_YIELDS_ASYNC,
body_expr.hir_id,
return_expr_span,
"an async construct yields a type which is itself awaitable",
|db| {
db.span_label(body_expr.span, "outer async construct");
db.span_label(return_expr_span, "awaitable value not awaited");
db.span_suggestion(
return_expr_span,
"consider awaiting this value",
format!("{}.await", snippet(cx, return_expr_span, "..")),
Applicability::MaybeIncorrect,
);
},
);
let return_expr_span = walk_span_to_context(return_expr_span, expr.span.ctxt()).unwrap_or(return_expr_span);
let mut applicability = Applicability::MaybeIncorrect;
let mut return_expr_snip =
Sugg::hir_with_context(cx, return_expr, expr.span.ctxt(), "..", &mut applicability);
if !is_expr_async_block(return_expr) {
return_expr_snip = return_expr_snip.maybe_paren();
}
span_lint_hir_and_then(
cx,
ASYNC_YIELDS_ASYNC,
body_expr.hir_id,
return_expr_span,
"an async construct yields a type which is itself awaitable",
|db| {
db.span_label(body_expr.span, "outer async construct");
db.span_label(return_expr_span, "awaitable value not awaited");
db.span_suggestion(
return_expr_span,
"consider awaiting this value",
format!("{return_expr_snip}.await"),
applicability,
);
},
);
}
}
}

View file

@ -0,0 +1,179 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{is_expn_of, peel_blocks, sym};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions of the form `x == true`,
/// `x != true` and order comparisons such as `x < true` (or vice versa) and
/// suggest using the variable directly.
///
/// ### Why is this bad?
/// Unnecessary code.
///
/// ### Example
/// ```rust,ignore
/// if x == true {}
/// if y == false {}
/// ```
/// use `x` directly:
/// ```rust,ignore
/// if x {}
/// if !y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub BOOL_COMPARISON,
complexity,
"comparing a variable to a boolean, e.g., `if x == true` or `if x != true`"
}
declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]);
impl<'tcx> LateLintPass<'tcx> for BoolComparison {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
return;
}
if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind
&& is_expn_of(left_side.span, sym::cfg).is_none()
&& is_expn_of(right_side.span, sym::cfg).is_none()
&& cx.typeck_results().expr_ty(left_side).is_bool()
&& cx.typeck_results().expr_ty(right_side).is_bool()
{
let ignore_case = None::<(fn(_) -> _, &str)>;
let ignore_no_literal = None::<(fn(_, _) -> _, &str)>;
match node {
BinOpKind::Eq => {
let true_case = Some((|h| h, "equality checks against true are unnecessary"));
let false_case = Some((
|h: Sugg<'tcx>| !h,
"equality checks against false can be replaced by a negation",
));
check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
},
BinOpKind::Ne => {
let true_case = Some((
|h: Sugg<'tcx>| !h,
"inequality checks against true can be replaced by a negation",
));
let false_case = Some((|h| h, "inequality checks against false are unnecessary"));
check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
},
BinOpKind::Lt => check_comparison(
cx,
e,
ignore_case,
Some((|h| h, "greater than checks against false are unnecessary")),
Some((
|h: Sugg<'tcx>| !h,
"less than comparison against true can be replaced by a negation",
)),
ignore_case,
Some((
|l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r),
"order comparisons between booleans can be simplified",
)),
),
BinOpKind::Gt => check_comparison(
cx,
e,
Some((
|h: Sugg<'tcx>| !h,
"less than comparison against true can be replaced by a negation",
)),
ignore_case,
ignore_case,
Some((|h| h, "greater than checks against false are unnecessary")),
Some((
|l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)),
"order comparisons between booleans can be simplified",
)),
),
_ => (),
}
}
}
}
fn check_comparison<'a, 'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>,
) {
if let ExprKind::Binary(_, left_side, right_side) = e.kind {
let mut applicability = Applicability::MachineApplicable;
// Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs,
// calling `source_callsite` make sure macros are handled correctly, see issue #9907
let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite());
match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) {
(Some(true), None) => left_true.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h);
}),
(None, Some(true)) => right_true.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h);
}),
(Some(false), None) => left_false.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h);
}),
(None, Some(false)) => right_false.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h);
}),
(None, None) => no_literal.map_or((), |(h, m)| {
let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability);
let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability);
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
binop_span,
m,
"try",
h(left_side, right_side).into_string(),
applicability,
);
}),
_ => (),
}
}
}
fn suggest_bool_comparison<'a, 'tcx>(
cx: &LateContext<'tcx>,
span: Span,
expr: &Expr<'_>,
mut app: Applicability,
message: &'static str,
conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>,
) {
let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app);
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
span,
message,
"try",
conv_hint(hint).into_string(),
app,
);
}
fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> {
if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind
&& let LitKind::Bool(value) = lit_ptr.node
{
return Some(value);
}
None
}

View file

@ -8,17 +8,12 @@ use rustc_middle::ty::{self, Ty};
use super::CAST_PTR_ALIGNMENT;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
if is_hir_ty_cfg_dependant(cx, cast_to) {
return;
}
let (cast_from, cast_to) = (
cx.typeck_results().expr_ty(cast_expr),
cx.typeck_results().expr_ty(expr),
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
pub(super) fn check_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind
&& method_path.ident.name == sym::cast
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
@ -74,14 +69,13 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
if let ExprKind::Path(path) = &func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
&& matches!(
cx.tcx.get_diagnostic_name(def_id),
Some(
sym::ptr_write_unaligned
| sym::ptr_read_unaligned
| sym::intrinsics_unaligned_volatile_load
| sym::intrinsics_unaligned_volatile_store
)
name,
sym::ptr_write_unaligned
| sym::ptr_read_unaligned
| sym::intrinsics_unaligned_volatile_load
| sym::intrinsics_unaligned_volatile_store
)
{
true

View file

@ -1,10 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::{get_parent_expr, is_no_std_crate};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::{self, Ty};
use rustc_span::sym;
@ -42,13 +44,52 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
let mut applicability = Applicability::MachineApplicable;
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
let krate = if is_no_std_crate(cx) { "core" } else { "std" };
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
span,
format!("casting the result of `{func}` to {cast_to}"),
"replace with",
format!("core::ptr::slice_{func}({ptr}, {len})"),
format!("{krate}::ptr::slice_{func}({ptr}, {len})"),
applicability,
);
}
}
/// Checks for implicit cast from slice reference to raw slice pointer.
pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Call(fun, [ptr_arg, len_arg]) = expr.peel_blocks().kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
&& let Some(rpk) = raw_parts_kind(cx, fun_def_id)
&& !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..)))
&& let [deref, borrow] = cx.typeck_results().expr_adjustments(expr)
&& matches!(deref.kind, Adjust::Deref(..))
&& let Adjustment {
kind: Adjust::Borrow(AutoBorrow::RawPtr(..)),
target,
} = borrow
&& let ty::RawPtr(pointee_ty, _) = target.kind()
&& pointee_ty.is_slice()
&& !expr.span.from_expansion()
{
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
RawPartsKind::Mutable => "from_raw_parts_mut",
};
let mut applicability = Applicability::MachineApplicable;
let ctxt = expr.span.ctxt();
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
let krate = if is_no_std_crate(cx) { "core" } else { "std" };
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
expr.span,
format!("implicitly casting the result of `{func}` to `{target}`"),
"replace_with",
format!("{krate}::ptr::slice_{func}({ptr}, {len})"),
applicability,
);
}

View file

@ -873,7 +873,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
}
char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to);
cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv);
cast_ptr_alignment::check(cx, expr, cast_from, cast_to);
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
@ -911,8 +913,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
if self.msrv.meets(cx, msrvs::RAW_REF_OP) {
borrow_as_ptr::check_implicit_cast(cx, expr);
}
cast_ptr_alignment::check(cx, expr);
ptr_as_ptr::check(cx, expr, self.msrv);
if self.msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
cast_slice_from_raw_parts::check_implicit_cast(cx, expr);
}
cast_ptr_alignment::check_cast_method(cx, expr);
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
}

View file

@ -4,9 +4,9 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind};
use rustc_hir::{self as hir, Expr, ExprKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::{self, Ty};
use rustc_span::{Span, sym};
use super::PTR_AS_PTR;
@ -26,13 +26,18 @@ impl OmitFollowedCastReason<'_> {
}
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) {
if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind
&& let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr))
&& let ty::RawPtr(_, from_mutbl) = cast_from.kind()
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
cast_from_expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to_hir: &hir::Ty<'_>,
cast_to: Ty<'tcx>,
msrv: Msrv,
) {
if let ty::RawPtr(_, from_mutbl) = cast_from.kind()
&& let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind()
&& matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut))
&& from_mutbl == to_mutbl
// The `U` in `pointer::cast` have to be `Sized`
// as explained here: https://github.com/rust-lang/rust/issues/60602.
&& to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
@ -40,7 +45,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
&& !is_from_proc_macro(cx, expr)
{
let mut app = Applicability::MachineApplicable;
let turbofish = match &cast_to_hir_ty.kind {
let turbofish = match &cast_to_hir.kind {
TyKind::Infer(()) => String::new(),
TyKind::Ptr(mut_ty) => {
if matches!(mut_ty.ty.kind, TyKind::Infer(())) {
@ -58,16 +63,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
// following `cast` does not compile because it fails to infer what type is expected
// as type argument to `std::ptr::ptr_null` or `std::ptr::ptr_null_mut`, so
// we omit following `cast`:
let omit_cast = if let ExprKind::Call(func, []) = cast_expr.kind
let omit_cast = if let ExprKind::Call(func, []) = cast_from_expr.kind
&& let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind
&& let Some(method_defid) = path.res.opt_def_id()
{
if cx.tcx.is_diagnostic_item(sym::ptr_null, method_defid) {
OmitFollowedCastReason::Null(qpath)
} else if cx.tcx.is_diagnostic_item(sym::ptr_null_mut, method_defid) {
OmitFollowedCastReason::NullMut(qpath)
} else {
OmitFollowedCastReason::None
match cx.tcx.get_diagnostic_name(method_defid) {
Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath),
Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath),
_ => OmitFollowedCastReason::None,
}
} else {
OmitFollowedCastReason::None
@ -78,7 +81,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app);
("try call directly", format!("{method}{turbofish}()"))
} else {
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
let cast_expr_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app);
(
"try `pointer::cast`, a safer alternative",

View file

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{std_or_core, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, QPath};
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
@ -12,26 +13,23 @@ use super::PTR_CAST_CONSTNESS;
pub(super) fn check<'tcx>(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_expr: &Expr<'_>,
cast_from_expr: &Expr<'_>,
cast_from: Ty<'tcx>,
cast_to: Ty<'tcx>,
msrv: Msrv,
) {
if let ty::RawPtr(from_ty, from_mutbl) = cast_from.kind()
&& let ty::RawPtr(to_ty, to_mutbl) = cast_to.kind()
&& matches!(
(from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not)
)
&& from_mutbl != to_mutbl
&& from_ty == to_ty
&& !from_ty.has_erased_regions()
{
if let ExprKind::Call(func, []) = cast_expr.kind
if let ExprKind::Call(func, []) = cast_from_expr.kind
&& let ExprKind::Path(QPath::Resolved(None, path)) = func.kind
&& let Some(defid) = path.res.opt_def_id()
&& let Some(prefix) = std_or_core(cx)
&& let mut app = Applicability::MachineApplicable
&& let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app))
&& let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app)
&& let Some((_, after_lt)) = sugg.split_once("::<")
&& let Some((source, target, target_func)) = match cx.tcx.get_diagnostic_name(defid) {
Some(sym::ptr_null) => Some(("const", "mutable", "null_mut")),
@ -53,11 +51,17 @@ pub(super) fn check<'tcx>(
if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) {
let mut app = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app);
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
let sugg = if let ExprKind::Cast(nested_from, nested_hir_ty) = cast_from_expr.kind
&& let hir::TyKind::Ptr(ptr_ty) = nested_hir_ty.kind
&& let hir::TyKind::Infer(()) = ptr_ty.ty.kind
{
// `(foo as *const _).cast_mut()` fails method name resolution
// avoid this by `as`-ing the full type
Sugg::hir_with_context(cx, nested_from, expr.span.ctxt(), "_", &mut app).as_ty(cast_from)
} else {
Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app)
};
let constness = to_mutbl.ptr_str();
span_lint_and_sugg(
cx,
@ -73,8 +77,8 @@ pub(super) fn check<'tcx>(
}
pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::MethodCall(method, cast_expr, [], _) = expr.kind
&& let ExprKind::Call(func, []) = cast_expr.kind
if let ExprKind::MethodCall(method, cast_from_expr, [], _) = expr.kind
&& let ExprKind::Call(func, []) = cast_from_expr.kind
&& let ExprKind::Path(QPath::Resolved(None, path)) = func.kind
&& let Some(defid) = path.res.opt_def_id()
&& let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.name) {
@ -84,7 +88,7 @@ pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>)
}
&& let Some(prefix) = std_or_core(cx)
&& let mut app = Applicability::MachineApplicable
&& let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app))
&& let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app)
&& let Some((_, after_lt)) = sugg.split_once("::<")
{
span_lint_and_sugg(

View file

@ -23,8 +23,8 @@ declare_clippy_lint! {
///
/// ### Known problems
/// The true Cognitive Complexity of a method is not something we can
/// calculate using modern technology. This lint has been left in the
/// `nursery` so as to not mislead users into using this lint as a
/// calculate using modern technology. This lint has been left in
/// `restriction` so as to not mislead users into using this lint as a
/// measurement tool.
///
/// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793)

View file

@ -35,6 +35,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO,
crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO,
crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO,
crate::bool_comparison::BOOL_COMPARISON_INFO,
crate::bool_to_int_with_if::BOOL_TO_INT_WITH_IF_INFO,
crate::booleans::NONMINIMAL_BOOL_INFO,
crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO,
@ -538,7 +539,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::mutex_atomic::MUTEX_ATOMIC_INFO,
crate::mutex_atomic::MUTEX_INTEGER_INFO,
crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO,
crate::needless_bool::BOOL_COMPARISON_INFO,
crate::needless_bool::NEEDLESS_BOOL_INFO,
crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO,
crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO,

View file

@ -10,7 +10,7 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, PointerCoercion};
use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults};
use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults, VariantDef};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
@ -85,6 +85,13 @@ fn contains_trait_object(ty: Ty<'_>) -> bool {
}
}
fn determine_derive_macro(cx: &LateContext<'_>, is_const: bool) -> Option<&'static str> {
(!is_const)
.then_some("derive")
.or_else(|| cx.tcx.features().enabled(sym::derive_const).then_some("derive_const"))
}
#[expect(clippy::too_many_arguments)]
fn check_struct<'tcx>(
cx: &LateContext<'tcx>,
item: &'tcx Item<'_>,
@ -93,6 +100,7 @@ fn check_struct<'tcx>(
adt_def: AdtDef<'_>,
ty_args: GenericArgsRef<'_>,
typeck_results: &'tcx TypeckResults<'tcx>,
is_const: bool,
) {
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
&& let Some(PathSegment { args, .. }) = p.segments.last()
@ -125,14 +133,18 @@ fn check_struct<'tcx>(
ExprKind::Tup(fields) => fields.iter().all(is_default_without_adjusts),
ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(is_default_without_adjusts),
ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_without_adjusts(ef.expr)),
_ => false,
_ => return,
};
if should_emit {
if should_emit && let Some(derive_snippet) = determine_derive_macro(cx, is_const) {
let struct_span = cx.tcx.def_span(adt_def.did());
let indent_enum = indent_of(cx, struct_span).unwrap_or(0);
let suggestions = vec![
(item.span, String::new()), // Remove the manual implementation
(struct_span.shrink_to_lo(), "#[derive(Default)]\n".to_string()), // Add the derive attribute
(
struct_span.shrink_to_lo(),
format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)),
), // Add the derive attribute
];
span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| {
@ -145,11 +157,41 @@ fn check_struct<'tcx>(
}
}
fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) {
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
&& let variant_id = cx.tcx.parent(id)
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id)
fn extract_enum_variant<'tcx>(
cx: &LateContext<'tcx>,
func_expr: &'tcx Expr<'tcx>,
adt_def: AdtDef<'tcx>,
) -> Option<&'tcx VariantDef> {
match &peel_blocks(func_expr).kind {
ExprKind::Path(QPath::Resolved(None, p))
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
&& let variant_id = cx.tcx.parent(id)
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) =>
{
Some(variant_def)
},
ExprKind::Path(QPath::TypeRelative(ty, segment))
if let TyKind::Path(QPath::Resolved(None, p)) = &ty.kind
&& let Res::SelfTyAlias {
is_trait_impl: true, ..
} = p.res
&& let variant_ident = segment.ident
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.ident(cx.tcx) == variant_ident) =>
{
Some(variant_def)
},
_ => None,
}
}
fn check_enum<'tcx>(
cx: &LateContext<'tcx>,
item: &'tcx Item<'tcx>,
func_expr: &'tcx Expr<'tcx>,
adt_def: AdtDef<'tcx>,
is_const: bool,
) {
if let Some(variant_def) = extract_enum_variant(cx, func_expr, adt_def)
&& variant_def.fields.is_empty()
&& !variant_def.is_field_list_non_exhaustive()
{
@ -158,11 +200,15 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex
let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
let Some(derive_snippet) = determine_derive_macro(cx, is_const) else {
return;
};
let suggestions = vec![
(item.span, String::new()), // Remove the manual implementation
(
enum_span.shrink_to_lo(),
format!("#[derive(Default)]\n{}", " ".repeat(indent_enum)),
format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)),
), // Add the derive attribute
(
variant_span.shrink_to_lo(),
@ -201,10 +247,20 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
&& !attrs.iter().any(|attr| attr.doc_str().is_some())
&& cx.tcx.hir_attrs(impl_item_hir).is_empty()
{
let is_const = of_trait.constness == hir::Constness::Const;
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
check_struct(
cx,
item,
self_ty,
func_expr,
adt_def,
args,
cx.tcx.typeck_body(*b),
is_const,
);
} else if adt_def.is_enum() && self.msrv.meets(cx, msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
check_enum(cx, item, func_expr, adt_def, is_const);
}
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_expr;
@ -194,7 +194,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
if let Some(sugg) = sugg {
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
} else {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
span_lint_and_help(
cx,
MAP_ENTRY,
expr.span,
lint_msg,
None,
format!(
"consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.{}.html#entry-api",
map_ty.name()
),
);
}
}
}
@ -254,35 +264,28 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio
_ => None,
});
match expr.kind {
ExprKind::MethodCall(
_,
if let ExprKind::MethodCall(_, map, [arg], _) = expr.kind
&& let Expr {
kind: ExprKind::AddrOf(_, _, key),
span: key_span,
..
} = arg
&& key_span.eq_ctxt(expr.span)
{
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
let expr = ContainsExpr {
negated,
map,
[
Expr {
kind: ExprKind::AddrOf(_, _, key),
span: key_span,
..
},
],
_,
) if key_span.eq_ctxt(expr.span) => {
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
let expr = ContainsExpr {
negated,
map,
key,
call_ctxt: expr.span.ctxt(),
};
if cx.tcx.is_diagnostic_item(sym::btreemap_contains_key, id) {
Some((MapType::BTree, expr))
} else if cx.tcx.is_diagnostic_item(sym::hashmap_contains_key, id) {
Some((MapType::Hash, expr))
} else {
None
}
},
_ => None,
key,
call_ctxt: expr.span.ctxt(),
};
match cx.tcx.get_diagnostic_name(id) {
Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)),
Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)),
_ => None,
}
} else {
None
}
}
@ -311,7 +314,9 @@ struct InsertExpr<'tcx> {
fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind {
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
if cx.tcx.is_diagnostic_item(sym::btreemap_insert, id) || cx.tcx.is_diagnostic_item(sym::hashmap_insert, id) {
if let Some(insert) = cx.tcx.get_diagnostic_name(id)
&& matches!(insert, sym::btreemap_insert | sym::hashmap_insert)
{
Some(InsertExpr { map, key, value })
} else {
None

View file

@ -12,6 +12,7 @@ use rustc_hir::attrs::AttributeKind;
use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{
self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults,
};
@ -148,10 +149,9 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
{
return;
}
let callee_ty_adjusted = typeck
.expr_adjustments(callee)
.last()
.map_or(callee_ty, |a| a.target.peel_refs());
let callee_ty_adjustments = typeck.expr_adjustments(callee);
let callee_ty_adjusted = callee_ty_adjustments.last().map_or(callee_ty, |a| a.target);
let sig = match callee_ty_adjusted.kind() {
ty::FnDef(def, _) => {
@ -230,7 +230,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
},
_ => (),
}
} else if let n_refs =
callee_ty_adjustments
.iter()
.rev()
.fold(0, |acc, adjustment| match adjustment.kind {
Adjust::Deref(Some(_)) => acc + 1,
Adjust::Deref(_) if acc > 0 => acc + 1,
_ => acc,
})
&& n_refs > 0
{
snippet = format!("{}{snippet}", "*".repeat(n_refs));
}
let replace_with = match callee_ty_adjusted.kind() {
ty::FnDef(def, _) => cx.tcx.def_descr(*def),
_ => "function",

View file

@ -1,11 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::numeric_literal;
use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal};
use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FloatTy};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use std::fmt;
declare_clippy_lint! {
@ -13,6 +14,8 @@ declare_clippy_lint! {
/// Checks for float literals with a precision greater
/// than that supported by the underlying type.
///
/// The lint is suppressed for literals with over `const_literal_digits_threshold` digits.
///
/// ### Why is this bad?
/// Rust will truncate the literal silently.
///
@ -58,7 +61,21 @@ declare_clippy_lint! {
"lossy whole number float literals"
}
declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
pub struct FloatLiteral {
const_literal_digits_threshold: usize,
}
impl_lint_pass!(FloatLiteral => [
EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL
]);
impl FloatLiteral {
pub fn new(conf: &'static Conf) -> Self {
Self {
const_literal_digits_threshold: conf.const_literal_digits_threshold,
}
}
}
impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
@ -126,13 +143,25 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
},
);
}
} else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) {
} else if digits > max as usize && count_digits(&float_str) < digits {
if digits >= self.const_literal_digits_threshold
&& matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_))
{
// If a big enough number of digits is specified and it's a constant
// we assume the user is definining a constant, and excessive precision is ok
return;
}
span_lint_and_then(
cx,
EXCESSIVE_PRECISION,
expr.span,
"float has excessive precision",
|diag| {
if digits >= self.const_literal_digits_threshold
&& let Some(let_stmt) = maybe_let_stmt(cx, expr)
{
diag.span_note(let_stmt.span, "consider making it a `const` item");
}
diag.span_suggestion_verbose(
expr.span,
"consider changing the type or truncating it to",
@ -196,3 +225,11 @@ impl FloatFormat {
}
}
}
fn maybe_let_stmt<'a>(cx: &LateContext<'a>, expr: &hir::Expr<'_>) -> Option<&'a hir::LetStmt<'a>> {
let parent = cx.tcx.parent_hir_node(expr.hir_id);
match parent {
hir::Node::LetStmt(let_stmt) => Some(let_stmt),
_ => None,
}
}

View file

@ -535,7 +535,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
def_id: LocalDefId,
) {
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
too_many_arguments::check_fn(cx, kind, decl, hir_id, def_id, self.too_many_arguments_threshold);
too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold);
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id);
misnamed_getters::check_fn(cx, kind, decl, body, span);

View file

@ -1,5 +1,7 @@
use rustc_abi::ExternAbi;
use rustc_hir::{self as hir, intravisit};
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_lint::LateContext;
use rustc_span::Span;
@ -10,39 +12,18 @@ use super::TOO_MANY_ARGUMENTS;
pub(super) fn check_fn(
cx: &LateContext<'_>,
kind: intravisit::FnKind<'_>,
kind: FnKind<'_>,
decl: &hir::FnDecl<'_>,
span: Span,
hir_id: hir::HirId,
def_id: LocalDefId,
too_many_arguments_threshold: u64,
) {
// don't warn for implementations, it's not their fault
if !is_trait_impl_item(cx, hir_id) {
if !is_trait_impl_item(cx, hir_id)
// don't lint extern functions decls, it's not their fault either
match kind {
intravisit::FnKind::Method(
_,
&hir::FnSig {
header: hir::FnHeader {
abi: ExternAbi::Rust, ..
},
..
},
)
| intravisit::FnKind::ItemFn(
_,
_,
hir::FnHeader {
abi: ExternAbi::Rust, ..
},
) => check_arg_number(
cx,
decl,
span.with_hi(decl.output.span().hi()),
too_many_arguments_threshold,
),
_ => {},
}
&& kind.header().is_some_and(|header| header.abi == ExternAbi::Rust)
{
check_arg_number(cx, decl, cx.tcx.def_span(def_id), too_many_arguments_threshold);
}
}

View file

@ -2,16 +2,16 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::{
contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor,
path_res, peel_blocks,
path_res, peel_blocks, sym,
};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
@ -71,21 +71,21 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& let ExprKind::Block(then_block, _) = then.kind
&& let Some(then_expr) = then_block.expr
&& let ExprKind::Call(then_call, [then_arg]) = then_expr.kind
&& let ctxt = expr.span.ctxt()
&& then_expr.span.ctxt() == ctxt
&& !expr.span.from_expansion()
&& !then_expr.span.from_expansion()
&& is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
&& is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
&& !is_else_clause(cx.tcx, expr)
&& !is_in_const_context(cx)
&& !expr.span.in_external_macro(cx.sess().source_map())
&& self.msrv.meets(cx, msrvs::BOOL_THEN)
&& !contains_return(then_block.stmts)
{
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
"then_some"
sym::then_some
} else {
"then"
sym::then
};
let ctxt = expr.span.ctxt();
span_lint_and_then(
cx,
@ -98,16 +98,18 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
}
let mut app = Applicability::MachineApplicable;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
let cond_snip = Sugg::hir_with_context(cx, cond, ctxt, "[condition]", &mut app)
.maybe_paren()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let method_body = if let Some(first_stmt) = then_block.stmts.first() {
let (block_snippet, _) =
snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app);
let closure = if method_name == "then" { "|| " } else { "" };
format!("{closure} {{ {block_snippet}; {arg_snip} }}")
} else if method_name == "then" {
let method_body = if let Some(first_stmt) = then_block.stmts.first()
&& let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt)
{
let block_snippet =
snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app);
let closure = if method_name == sym::then { "|| " } else { "" };
format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end())
} else if method_name == sym::then {
(std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned()
} else {
arg_snip.into_owned()

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty;
use clippy_utils::{is_path_diagnostic_item, ty};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -107,8 +107,7 @@ impl LateLintPass<'_> for InstantSubtraction {
fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
if let ExprKind::Call(fn_expr, []) = expr_block.kind
&& let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
&& cx.tcx.is_diagnostic_item(sym::instant_now, fn_id)
&& is_path_diagnostic_item(cx, fn_expr, sym::instant_now)
{
true
} else {

View file

@ -64,8 +64,8 @@ impl LateLintPass<'_> for LargeIncludeFile {
}
&& len as u64 > self.max_file_size
&& let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
&& let Some(macro_name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
&& matches!(macro_name, sym::include_bytes_macro | sym::include_str_macro)
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(

View file

@ -355,12 +355,15 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le
return Some(LenOutput::Integral);
}
if let Res::Def(_, def_id) = res {
if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_first_generic_integral(segment) {
return Some(LenOutput::Option(def_id));
} else if cx.tcx.is_diagnostic_item(sym::Result, def_id) && is_first_generic_integral(segment) {
return Some(LenOutput::Result(def_id));
if let Res::Def(_, def_id) = res
&& let Some(res) = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::Option) => Some(LenOutput::Option(def_id)),
Some(sym::Result) => Some(LenOutput::Result(def_id)),
_ => None,
}
&& is_first_generic_integral(segment)
{
return Some(res);
}
return None;
@ -368,11 +371,10 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did()))
},
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did()))
ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => Some(LenOutput::Option(adt.did())),
Some(sym::Result) => Some(LenOutput::Result(adt.did())),
_ => None,
},
_ => None,
}

View file

@ -84,6 +84,7 @@ mod attrs;
mod await_holding_invalid;
mod blocks_in_conditions;
mod bool_assert_comparison;
mod bool_comparison;
mod bool_to_int_with_if;
mod booleans;
mod borrow_deref_ref;
@ -474,10 +475,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(move |_| Box::new(types::Types::new(conf)));
store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf)));
store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant));
store.register_late_pass(|_| Box::new(float_literal::FloatLiteral));
store.register_late_pass(move |_| Box::new(float_literal::FloatLiteral::new(conf)));
store.register_late_pass(|_| Box::new(ptr::Ptr));
store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|_| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison));
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
store.register_late_pass(|_| Box::new(misc::LintPass));
store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
@ -655,7 +656,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf)));
store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default());
store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch));
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::<unwrap_in_result::UnwrapInResult>::default());
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
let attrs = attr_storage.clone();

View file

@ -4,7 +4,8 @@ use hir::intravisit::{Visitor, walk_expr};
use rustc_ast::Label;
use rustc_errors::Applicability;
use rustc_hir::{
self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnRetTy,
FnSig, Node, TyKind,
};
use rustc_lint::{LateContext, LintContext};
use rustc_span::sym;
@ -73,7 +74,11 @@ fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> boo
if let Node::Expr(Expr {
kind:
ExprKind::Closure(Closure {
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
kind:
ClosureKind::Coroutine(CoroutineKind::Desugared(
CoroutineDesugaring::Async,
CoroutineSource::Block | CoroutineSource::Closure,
)),
..
}),
..

View file

@ -1,10 +1,10 @@
use super::MISSING_SPIN_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::std_or_core;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::sym;
fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
@ -39,8 +39,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'
) = body.kind
&& let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind
&& [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name)
&& let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind()
&& cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did())
&& let callee_ty = cx.typeck_results().expr_ty(callee)
&& is_type_diagnostic_item(cx, callee_ty, sym::AtomicBool)
&& let Some(std_or_core) = std_or_core(cx)
{
span_lint_and_sugg(

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg};
use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, peel_hir_expr_while, sugg};
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
@ -253,12 +253,38 @@ struct VarVisitor<'a, 'tcx> {
impl<'tcx> VarVisitor<'_, 'tcx> {
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let mut used_cnt = 0;
// It is `true` if all indices are direct
let mut index_used_directly = true;
// Handle initial index
if is_local_used(self.cx, idx, self.var) {
used_cnt += 1;
index_used_directly &= matches!(idx.kind, ExprKind::Path(_));
}
// Handle nested indices
let seqexpr = peel_hir_expr_while(seqexpr, |e| {
if let ExprKind::Index(e, idx, _) = e.kind {
if is_local_used(self.cx, idx, self.var) {
used_cnt += 1;
index_used_directly &= matches!(idx.kind, ExprKind::Path(_));
}
Some(e)
} else {
None
}
});
match used_cnt {
0 => return true,
n if n > 1 => self.nonindex = true, // Optimize code like `a[i][i]`
_ => {},
}
if let ExprKind::Path(ref seqpath) = seqexpr.kind
// the indexed container is referenced by a name
&& let QPath::Resolved(None, seqvar) = *seqpath
&& seqvar.segments.len() == 1
&& is_local_used(self.cx, idx, self.var)
{
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
@ -312,7 +338,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> {
impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind
// a range index op
&& let Some(trait_id) = self
.cx
.typeck_results()

View file

@ -1,12 +1,12 @@
use super::UNUSED_ENUMERATE_INDEX;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{pat_is_wild, sugg};
use rustc_errors::Applicability;
use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::sym;
/// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
@ -17,8 +17,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_
&& let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind
&& let ty = cx.typeck_results().expr_ty(arg)
&& pat_is_wild(cx, &index.kind, body)
&& let ty::Adt(base, _) = *ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Enumerate, base.did())
&& is_type_diagnostic_item(cx, ty, sym::Enumerate)
&& let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id)
&& cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id)
{

View file

@ -97,11 +97,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
return;
}
if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) {
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
let range = check_pat(&arm.pat.kind);
check_is_ascii(cx, macro_call.span, recv, &range, None);
}
let (arg, span, range) = if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro)
&& let ExprKind::Match(recv, [arm, ..], _) = expr.kind
{
let recv = peel_ref_operators(cx, recv);
let range = check_pat(&arm.pat.kind);
(recv, macro_call.span, range)
} else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind
&& path.ident.name == sym::contains
&& let Some(higher::Range {
@ -112,10 +113,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
&& !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_))
{
let arg = peel_ref_operators(cx, arg);
let ty_sugg = get_ty_sugg(cx, arg);
let range = check_expr_range(start, end);
check_is_ascii(cx, expr.span, arg, &range, ty_sugg);
}
(arg, expr.span, range)
} else {
return;
};
let ty_sugg = get_ty_sugg(cx, arg);
check_is_ascii(cx, span, arg, &range, ty_sugg);
}
}
@ -146,9 +151,8 @@ fn check_is_ascii(
CharRange::HexDigit => "is_ascii_hexdigit",
CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return,
};
let default_snip = "..";
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren();
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), "_", &mut app).maybe_paren();
let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))];
if let Some((ty_span, ty)) = ty_sugg {
suggestion.push((ty_span, format!("{recv}: {ty}")));
@ -182,7 +186,7 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
CharRange::Otherwise
}
},
PatKind::Range(Some(start), Some(end), kind) if *kind == RangeEnd::Included => check_range(start, end),
PatKind::Range(Some(start), Some(end), RangeEnd::Included) => check_range(start, end),
_ => CharRange::Otherwise,
}
}

View file

@ -123,8 +123,8 @@ fn check_iter(
) {
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
&& let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::iter_copied, copied_def_id)
|| cx.tcx.is_diagnostic_item(sym::iter_cloned, copied_def_id))
&& let Some(copied_name) = cx.tcx.get_diagnostic_name(copied_def_id)
&& matches!(copied_name, sym::iter_copied | sym::iter_cloned)
&& let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind
&& let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::iter_filter, filter_def_id)
@ -243,9 +243,9 @@ fn make_sugg(
}
fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool {
ACCEPTABLE_METHODS
.iter()
.any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id))
cx.tcx
.get_diagnostic_name(collect_def_id)
.is_some_and(|collect_name| ACCEPTABLE_METHODS.contains(&collect_name))
}
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool {

View file

@ -75,12 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
&& let ExprKind::Path(target_path) = &target_arg.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
{
let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) {
StripKind::Prefix
} else if cx.tcx.is_diagnostic_item(sym::str_ends_with, method_def_id) {
StripKind::Suffix
} else {
return;
let strip_kind = match cx.tcx.get_diagnostic_name(method_def_id) {
Some(sym::str_starts_with) => StripKind::Prefix,
Some(sym::str_ends_with) => StripKind::Suffix,
_ => return,
};
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {

View file

@ -116,8 +116,10 @@ fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
/// The expression inside a closure may or may not have surrounding braces and
/// semicolons, which causes problems when generating a suggestion. Given an
/// expression that evaluates to '()' or '!', recursively remove useless braces
/// and semi-colons until is suitable for including in the suggestion template
fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Span> {
/// and semi-colons until is suitable for including in the suggestion template.
/// The `bool` is `true` when the resulting `span` needs to be enclosed in an
/// `unsafe` block.
fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<(Span, bool)> {
if !is_unit_expression(cx, expr) {
return None;
}
@ -125,22 +127,24 @@ fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<
match expr.kind {
hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(..) => {
// Calls can't be reduced any more
Some(expr.span)
Some((expr.span, false))
},
hir::ExprKind::Block(block, _) => {
let is_unsafe = matches!(block.rules, hir::BlockCheckMode::UnsafeBlock(_));
match (block.stmts, block.expr.as_ref()) {
([], Some(inner_expr)) => {
// If block only contains an expression,
// reduce `{ X }` to `X`
reduce_unit_expression(cx, inner_expr)
.map(|(span, inner_is_unsafe)| (span, inner_is_unsafe || is_unsafe))
},
([inner_stmt], None) => {
// If block only contains statements,
// reduce `{ X; }` to `X` or `X;`
match inner_stmt.kind {
hir::StmtKind::Let(local) => Some(local.span),
hir::StmtKind::Expr(e) => Some(e.span),
hir::StmtKind::Semi(..) => Some(inner_stmt.span),
hir::StmtKind::Let(local) => Some((local.span, is_unsafe)),
hir::StmtKind::Expr(e) => Some((e.span, is_unsafe)),
hir::StmtKind::Semi(..) => Some((inner_stmt.span, is_unsafe)),
hir::StmtKind::Item(..) => None,
}
},
@ -228,10 +232,11 @@ fn lint_map_unit_fn(
let msg = suggestion_msg("closure", map_type);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
if let Some((reduced_expr_span, is_unsafe)) = reduce_unit_expression(cx, closure_expr) {
let mut applicability = Applicability::MachineApplicable;
let (prefix_is_unsafe, suffix_is_unsafe) = if is_unsafe { ("unsafe { ", " }") } else { ("", "") };
let suggestion = format!(
"if let {0}({1}) = {2} {{ {3} }}",
"if let {0}({1}) = {2} {{ {prefix_is_unsafe}{3}{suffix_is_unsafe} }}",
variant,
snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability),
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),

View file

@ -4,20 +4,22 @@ use clippy_utils::msrvs::Msrv;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{
SpanlessEq, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators,
SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt,
peel_ref_operators,
};
use rustc_ast::BorrowKind;
use rustc_errors::MultiSpan;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind};
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or};
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) {
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
for arm in arms {
check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv);
check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv);
}
}
}
@ -27,15 +29,18 @@ pub(super) fn check_if_let<'tcx>(
pat: &'tcx Pat<'_>,
body: &'tcx Expr<'_>,
else_expr: Option<&'tcx Expr<'_>>,
let_expr: &'tcx Expr<'_>,
msrv: Msrv,
) {
check_arm(cx, false, pat, body, None, else_expr, msrv);
check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv);
}
#[allow(clippy::too_many_arguments)]
fn check_arm<'tcx>(
cx: &LateContext<'tcx>,
outer_is_match: bool,
outer_pat: &'tcx Pat<'tcx>,
outer_cond: &'tcx Expr<'tcx>,
outer_then_body: &'tcx Expr<'tcx>,
outer_guard: Option<&'tcx Expr<'tcx>>,
outer_else_body: Option<&'tcx Expr<'tcx>>,
@ -82,6 +87,9 @@ fn check_arm<'tcx>(
},
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
}
// Check if the inner expression contains any borrows/dereferences
&& let ref_types = get_ref_operators(cx, inner_scrutinee)
&& let Some(method) = build_ref_method_chain(ref_types)
{
let msg = format!(
"this `{}` can be collapsed into the outer `{}`",
@ -103,6 +111,10 @@ fn check_arm<'tcx>(
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
help_span.push_span_label(binding_span, "replace this binding");
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
if !method.is_empty() {
let outer_cond_msg = format!("use: `{}{}`", snippet(cx, outer_cond.span, ".."), method);
help_span.push_span_label(outer_cond.span, outer_cond_msg);
}
diag.span_help(
help_span,
"the outer pattern can be modified to include the inner pattern",
@ -148,3 +160,30 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi
});
(span, is_innermost_parent_pat_struct)
}
/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`,
/// `.copied()`) based on reference operators
fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option<String> {
let mut req_method_calls = String::new();
for ref_operator in expr {
match ref_operator.kind {
ExprKind::AddrOf(BorrowKind::Raw, _, _) => {
return None;
},
ExprKind::AddrOf(_, m, _) if m.is_mut() => {
req_method_calls.push_str(".as_mut()");
},
ExprKind::AddrOf(_, _, _) => {
req_method_calls.push_str(".as_ref()");
},
// Deref operator is the only operator that this function should have received
ExprKind::Unary(_, _) => {
req_method_calls.push_str(".copied()");
},
_ => (),
}
}
Some(req_method_calls)
}

View file

@ -1073,7 +1073,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source);
}
collapsible_match::check_match(cx, arms, self.msrv);
collapsible_match::check_match(cx, ex, arms, self.msrv);
if !from_expansion {
// These don't depend on a relationship between multiple arms
match_wild_err_arm::check(cx, ex, arms);
@ -1137,7 +1137,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
}
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv);
collapsible_match::check_if_let(
cx,
if_let.let_pat,
if_let.if_then,
if_let.if_else,
if_let.let_expr,
self.msrv,
);
significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else);
if !from_expansion {
if let Some(else_expr) = if_let.if_else {

View file

@ -226,11 +226,12 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
enum SigDropHolder {
/// No values with significant drop present in this expression.
///
/// Expressions that we've emitted lints do not count.
#[default]
None,
/// Some field in this expression references to values with significant drop.
///
@ -244,12 +245,6 @@ enum SigDropHolder {
Moved,
}
impl Default for SigDropHolder {
fn default() -> Self {
Self::None
}
}
struct SigDropHelper<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
parent_expr: Option<&'tcx Expr<'tcx>>,

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::option_arg_ty;
use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res};
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
@ -28,25 +28,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
&& is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr)
&& let Some(return_ty) = find_return_type(cx, &expr.kind)
{
let prefix;
let suffix;
let err_ty;
if let Some(ty) = result_error_type(cx, return_ty) {
prefix = "Err(";
suffix = ")";
err_ty = ty;
let (prefix, suffix, err_ty) = if let Some(ty) = result_error_type(cx, return_ty) {
("Err(", ")", ty)
} else if let Some(ty) = poll_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Err(";
suffix = "))";
err_ty = ty;
("Poll::Ready(Err(", "))", ty)
} else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Some(Err(";
suffix = ")))";
err_ty = ty;
("Poll::Ready(Some(Err(", ")))", ty)
} else {
return;
}
};
span_lint_and_then(
cx,
@ -88,8 +78,8 @@ fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> O
/// Extracts the error type from Result<T, E>.
fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if let ty::Adt(_, subst) = ty.kind()
&& is_type_diagnostic_item(cx, ty, sym::Result)
if let ty::Adt(def, subst) = ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Result, def.did())
{
Some(subst.type_at(1))
} else {
@ -101,11 +91,9 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if let ty::Adt(def, subst) = ty.kind()
&& cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did())
&& let ready_ty = subst.type_at(0)
&& let ty::Adt(ready_def, ready_subst) = ready_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Result, ready_def.did())
{
Some(ready_subst.type_at(1))
let ready_ty = subst.type_at(0);
result_error_type(cx, ready_ty)
} else {
None
}
@ -116,13 +104,9 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) ->
if let ty::Adt(def, subst) = ty.kind()
&& cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did())
&& let ready_ty = subst.type_at(0)
&& let ty::Adt(ready_def, ready_subst) = ready_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Option, ready_def.did())
&& let some_ty = ready_subst.type_at(0)
&& let ty::Adt(some_def, some_subst) = some_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Result, some_def.did())
&& let Some(some_ty) = option_arg_ty(cx, ready_ty)
{
Some(some_subst.type_at(1))
result_error_type(cx, some_ty)
} else {
None
}

View file

@ -215,7 +215,8 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
&& let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
{
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
let repl_name = cx.tcx.get_diagnostic_name(repl_def_id);
if repl_name == Some(sym::mem_uninitialized) {
let Some(top_crate) = std_or_core(cx) else { return };
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
@ -230,9 +231,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
),
applicability,
);
} else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id)
&& !cx.typeck_results().expr_ty(src).is_primitive()
{
} else if repl_name == Some(sym::mem_zeroed) && !cx.typeck_results().expr_ty(src).is_primitive() {
span_lint_and_help(
cx,
MEM_REPLACE_WITH_UNINIT,

View file

@ -24,9 +24,10 @@ pub(super) fn check(
&& let Some(name) = cx.tcx.get_diagnostic_name(adt.did())
{
let caller_type = match name {
sym::Rc => "Rc",
sym::Arc => "Arc",
sym::RcWeak | sym::ArcWeak => "Weak",
sym::Rc => "std::rc::Rc",
sym::Arc => "std::sync::Arc",
sym::RcWeak => "std::rc::Weak",
sym::ArcWeak => "std::sync::Weak",
_ => return,
};
span_lint_and_then(

View file

@ -233,18 +233,16 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
// the latter only calls `effect` once
let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span);
if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym::is_some {
Some(Self::IsSome {
match (cx.tcx.get_diagnostic_name(recv_ty.did()), path.ident.name) {
(Some(sym::Option), sym::is_some) => Some(Self::IsSome {
receiver,
side_effect_expr_span,
})
} else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym::is_ok {
Some(Self::IsOk {
}),
(Some(sym::Result), sym::is_ok) => Some(Self::IsOk {
receiver,
side_effect_expr_span,
})
} else {
None
}),
_ => None,
}
} else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some()
// we know for a fact that the wildcard pattern is the second arm

View file

@ -24,32 +24,29 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) ->
let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else {
return None;
};
let did = adt.did();
if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) {
// For array::IntoIter<T, const N: usize>, the length is the second generic
// parameter.
substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from)
} else if cx.tcx.is_diagnostic_item(sym::SliceIter, did)
&& let ExprKind::MethodCall(_, recv, ..) = iter.kind
{
if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
// For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
len.try_to_target_usize(cx.tcx).map(u128::from)
} else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
match args {
VecArgs::Vec(vec) => vec.len().try_into().ok(),
VecArgs::Repeat(_, len) => expr_as_u128(cx, len),
match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::ArrayIntoIter) => {
// For array::IntoIter<T, const N: usize>, the length is the second generic
// parameter.
substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from)
},
Some(sym::SliceIter) if let ExprKind::MethodCall(_, recv, ..) = iter.kind => {
if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
// For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
len.try_to_target_usize(cx.tcx).map(u128::from)
} else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
match args {
VecArgs::Vec(vec) => vec.len().try_into().ok(),
VecArgs::Repeat(_, len) => expr_as_u128(cx, len),
}
} else {
None
}
} else {
None
}
} else if cx.tcx.is_diagnostic_item(sym::IterEmpty, did) {
Some(0)
} else if cx.tcx.is_diagnostic_item(sym::IterOnce, did) {
Some(1)
} else {
None
},
Some(sym::IterEmpty) => Some(0),
Some(sym::IterOnce) => Some(1),
_ => None,
}
}

View file

@ -50,10 +50,10 @@ fn try_get_caller_ty_name_and_method_name(
}
} else {
if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
return Some(("Option", "and_then"));
} else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
return Some(("Result", "and_then"));
match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => return Some(("Option", "and_then")),
Some(sym::Result) => return Some(("Result", "and_then")),
_ => {},
}
}
None

View file

@ -1,14 +1,16 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method, path_to_local};
use rustc_ast::BindingMode;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Node, PatKind};
use rustc_lint::LateContext;
use rustc_hir::{self as hir, ExprKind, Node, PatKind};
use rustc_lint::{LateContext, LintContext};
use rustc_span::{Span, Symbol, sym};
use super::MAP_IDENTITY;
const MSG: &str = "unnecessary map of the identity function";
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
@ -23,26 +25,70 @@ pub(super) fn check(
|| is_type_diagnostic_item(cx, caller_ty, sym::Result)
|| is_type_diagnostic_item(cx, caller_ty, sym::Option))
&& is_expr_untyped_identity_function(cx, map_arg)
&& let Some(sugg_span) = expr.span.trim_start(caller.span)
&& let Some(call_span) = expr.span.trim_start(caller.span)
{
// If the result of `.map(identity)` is used as a mutable reference,
// the caller must not be an immutable binding.
if cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr()
&& let Some(hir_id) = path_to_local(caller)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& !matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
{
return;
}
let main_sugg = (call_span, String::new());
let mut app = if is_copy(cx, caller_ty) {
// there is technically a behavioral change here for `Copy` iterators, where
// `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and
// changing it to `iter.next()` mutates iter directly
Applicability::Unspecified
} else {
Applicability::MachineApplicable
};
span_lint_and_sugg(
cx,
MAP_IDENTITY,
sugg_span,
"unnecessary map of the identity function",
format!("remove the call to `{name}`"),
String::new(),
Applicability::MachineApplicable,
);
let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr();
if needs_to_be_mutable && !is_mutable(cx, caller) {
if let Some(hir_id) = path_to_local_with_projections(caller)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& let PatKind::Binding(_, _, ident, _) = pat.kind
{
// We can reach the binding -- suggest making it mutable
let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))];
let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app);
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
diag.multipart_suggestion(
format!("remove the call to `{name}`, and make `{ident}` mutable"),
suggs,
app,
);
});
} else {
// If we can't make the binding mutable, prevent the suggestion from being automatically applied,
// and add a complementary help message.
app = Applicability::Unspecified;
let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::MethodCall(method, ..) = expr.kind
{
Some(method.ident)
} else {
None
};
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app);
let note = if let Some(method_requiring_mut) = method_requiring_mut {
format!("this must be made mutable to use `{method_requiring_mut}`")
} else {
"this must be made mutable".to_string()
};
diag.span_note(caller.span, note);
});
}
} else {
span_lint_and_sugg(
cx,
MAP_IDENTITY,
main_sugg.0,
MSG,
format!("remove the call to `{name}`"),
main_sugg.1,
app,
);
}
}
}

View file

@ -38,17 +38,13 @@ pub(super) fn check(
];
let is_deref = match map_arg.kind {
hir::ExprKind::Path(ref expr_qpath) => {
cx.qpath_res(expr_qpath, map_arg.hir_id)
.opt_def_id()
.is_some_and(|fun_def_id| {
cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id)
|| cx.tcx.is_diagnostic_item(sym::deref_mut_method, fun_def_id)
|| deref_aliases
.iter()
.any(|&sym| cx.tcx.is_diagnostic_item(sym, fun_def_id))
})
},
hir::ExprKind::Path(ref expr_qpath) => cx
.qpath_res(expr_qpath, map_arg.hir_id)
.opt_def_id()
.and_then(|fun_def_id| cx.tcx.get_diagnostic_name(fun_def_id))
.is_some_and(|fun_name| {
matches!(fun_name, sym::deref_method | sym::deref_mut_method) || deref_aliases.contains(&fun_name)
}),
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
let closure_body = cx.tcx.hir_body(body);
let closure_expr = peel_blocks(closure_body.value);
@ -63,13 +59,11 @@ pub(super) fn check(
.map(|x| &x.kind)
.collect::<Box<[_]>>()
&& let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj
&& let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap()
&& let Some(method_name) = cx.tcx.get_diagnostic_name(method_did)
{
let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
cx.tcx.is_diagnostic_item(sym::deref_method, method_did)
|| cx.tcx.is_diagnostic_item(sym::deref_mut_method, method_did)
|| deref_aliases
.iter()
.any(|&sym| cx.tcx.is_diagnostic_item(sym, method_did))
matches!(method_name, sym::deref_method | sym::deref_mut_method)
|| deref_aliases.contains(&method_name)
} else {
false
}

View file

@ -62,7 +62,7 @@ fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: Lang
if let ExprKind::Call(some_expr, [arg]) = expr.kind
&& is_res_lang_ctor(cx, path_res(cx, some_expr), item)
{
Some(arg.span)
Some(arg.span.source_callsite())
} else {
None
}

View file

@ -31,8 +31,8 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool {
}
pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
if let Some(recv_adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& cx.tcx.is_diagnostic_item(sym::Stdin, recv_adt.did())
let recv_ty = cx.typeck_results().expr_ty(recv);
if is_type_diagnostic_item(cx, recv_ty, sym::Stdin)
&& let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind
&& let Res::Local(local_id) = path.res
{

View file

@ -5,10 +5,10 @@ use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) {
single_char_push_string::check(cx, expr, receiver, args);
} else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) {
single_char_insert_string::check(cx, expr, receiver, args);
match cx.tcx.get_diagnostic_name(fn_def_id) {
Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args),
Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args),
_ => {},
}
}
}

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_diag_trait_item;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_middle::ty::{self};
use rustc_span::sym;
use super::SUSPICIOUS_TO_OWNED;
@ -14,8 +14,7 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& is_diag_trait_item(cx, method_def_id, sym::ToOwned)
&& let input_type = cx.typeck_results().expr_ty(expr)
&& let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind()
&& cx.tcx.is_diagnostic_item(sym::Cow, adt.did())
&& is_type_diagnostic_item(cx, input_type, sym::Cow)
{
let mut app = Applicability::MaybeIncorrect;
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;

View file

@ -22,7 +22,8 @@ pub(super) fn check<'tcx>(
let typeck_results = cx.typeck_results();
let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results);
if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id))
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(id)
&& matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max)
{
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)

View file

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::{SpanRangeExt, snippet};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::AdtDef;
use rustc_span::{Span, sym};
use crate::loops::UNUSED_ENUMERATE_INDEX;
@ -39,9 +39,8 @@ use crate::loops::UNUSED_ENUMERATE_INDEX;
/// * `closure_arg`: The argument to the map function call containing the closure/function to apply
pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
let recv_ty = cx.typeck_results().expr_ty(recv);
if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
// If we call a method on a `std::iter::Enumerate` instance
&& cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid)
// If we call a method on a `std::iter::Enumerate` instance
if is_type_diagnostic_item(cx, recv_ty, sym::Enumerate)
// If we are calling a method of the `Iterator` trait
&& is_trait_method(cx, call_expr, sym::Iterator)
// And the map argument is a closure

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::{span_lint, span_lint_hir};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def_id::DefId;
use rustc_hir::{self as hir, Attribute, find_attr};
@ -64,14 +64,20 @@ declare_clippy_lint! {
"detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)"
}
fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) {
fn check_missing_inline_attrs(
cx: &LateContext<'_>,
attrs: &[Attribute],
sp: Span,
desc: &'static str,
hir_id: Option<hir::HirId>,
) {
if !find_attr!(attrs, AttributeKind::Inline(..)) {
span_lint(
cx,
MISSING_INLINE_IN_PUBLIC_ITEMS,
sp,
format!("missing `#[inline]` for {desc}"),
);
let msg = format!("missing `#[inline]` for {desc}");
if let Some(hir_id) = hir_id {
span_lint_hir(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, hir_id, sp, msg);
} else {
span_lint(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, sp, msg);
}
}
}
@ -103,17 +109,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
let desc = "a function";
let attrs = cx.tcx.hir_attrs(it.hir_id());
check_missing_inline_attrs(cx, attrs, it.span, desc);
check_missing_inline_attrs(cx, attrs, it.span, desc, None);
},
hir::ItemKind::Trait(
ref _constness,
ref _is_auto,
ref _unsafe,
_ident,
_generics,
_bounds,
trait_items,
) => {
hir::ItemKind::Trait(.., trait_items) => {
// note: we need to check if the trait is exported so we can't use
// `LateLintPass::check_trait_item` here.
for &tit in trait_items {
@ -127,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
let desc = "a default trait method";
let item = cx.tcx.hir_trait_item(tit);
let attrs = cx.tcx.hir_attrs(item.hir_id());
check_missing_inline_attrs(cx, attrs, item.span, desc);
check_missing_inline_attrs(cx, attrs, item.span, desc, Some(tit.hir_id()));
}
},
}
@ -182,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
}
let attrs = cx.tcx.hir_attrs(impl_item.hir_id());
check_missing_inline_attrs(cx, attrs, impl_item.span, desc);
check_missing_inline_attrs(cx, attrs, impl_item.span, desc, None);
}
}

View file

@ -1,4 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
@ -83,13 +85,18 @@ fn check_arguments<'tcx>(
let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
for (argument, parameter) in iter::zip(arguments, parameters) {
if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind()
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind
{
span_lint(
let mut applicability = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr();
span_lint_and_sugg(
cx,
UNNECESSARY_MUT_PASSED,
argument.span,
format!("the {fn_kind} `{name}` doesn't need a mutable reference"),
"remove this `mut`",
sugg.to_string(),
applicability,
);
}
}

View file

@ -2,16 +2,14 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt,
is_receiver_of_method_call, peel_blocks, peel_blocks_with_stmt, span_contains_comment, sym,
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call,
peel_blocks, peel_blocks_with_stmt, span_contains_comment,
};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
@ -50,31 +48,6 @@ declare_clippy_lint! {
"if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions of the form `x == true`,
/// `x != true` and order comparisons such as `x < true` (or vice versa) and
/// suggest using the variable directly.
///
/// ### Why is this bad?
/// Unnecessary code.
///
/// ### Example
/// ```rust,ignore
/// if x == true {}
/// if y == false {}
/// ```
/// use `x` directly:
/// ```rust,ignore
/// if x {}
/// if !y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub BOOL_COMPARISON,
complexity,
"comparing a variable to a boolean, e.g., `if x == true` or `if x != true`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions of the form `if c { x = true } else { x = false }`
@ -224,201 +197,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
}
}
declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]);
impl<'tcx> LateLintPass<'tcx> for BoolComparison {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
return;
}
if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind {
let ignore_case = None::<(fn(_) -> _, &str)>;
let ignore_no_literal = None::<(fn(_, _) -> _, &str)>;
match node {
BinOpKind::Eq => {
let true_case = Some((|h| h, "equality checks against true are unnecessary"));
let false_case = Some((
|h: Sugg<'tcx>| !h,
"equality checks against false can be replaced by a negation",
));
check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
},
BinOpKind::Ne => {
let true_case = Some((
|h: Sugg<'tcx>| !h,
"inequality checks against true can be replaced by a negation",
));
let false_case = Some((|h| h, "inequality checks against false are unnecessary"));
check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
},
BinOpKind::Lt => check_comparison(
cx,
e,
ignore_case,
Some((|h| h, "greater than checks against false are unnecessary")),
Some((
|h: Sugg<'tcx>| !h,
"less than comparison against true can be replaced by a negation",
)),
ignore_case,
Some((
|l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r),
"order comparisons between booleans can be simplified",
)),
),
BinOpKind::Gt => check_comparison(
cx,
e,
Some((
|h: Sugg<'tcx>| !h,
"less than comparison against true can be replaced by a negation",
)),
ignore_case,
ignore_case,
Some((|h| h, "greater than checks against false are unnecessary")),
Some((
|l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)),
"order comparisons between booleans can be simplified",
)),
),
_ => (),
}
}
}
}
struct ExpressionInfoWithSpan {
one_side_is_unary_not: bool,
left_span: Span,
right_span: Span,
}
fn is_unary_not(e: &Expr<'_>) -> (bool, Span) {
if let ExprKind::Unary(UnOp::Not, operand) = e.kind {
return (true, operand.span);
}
(false, e.span)
}
fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan {
let left = is_unary_not(left_side);
let right = is_unary_not(right_side);
ExpressionInfoWithSpan {
one_side_is_unary_not: left.0 != right.0,
left_span: left.1,
right_span: right.1,
}
}
fn check_comparison<'a, 'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>,
no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>,
) {
if let ExprKind::Binary(op, left_side, right_side) = e.kind {
let (l_ty, r_ty) = (
cx.typeck_results().expr_ty(left_side),
cx.typeck_results().expr_ty(right_side),
);
if is_expn_of(left_side.span, sym::cfg).is_some() || is_expn_of(right_side.span, sym::cfg).is_some() {
return;
}
if l_ty.is_bool() && r_ty.is_bool() {
let mut applicability = Applicability::MachineApplicable;
// Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs,
// calling `source_callsite` make sure macros are handled correctly, see issue #9907
let binop_span = left_side
.span
.source_callsite()
.with_hi(right_side.span.source_callsite().hi());
if op.node == BinOpKind::Eq {
let expression_info = one_side_is_unary_not(left_side, right_side);
if expression_info.one_side_is_unary_not {
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
binop_span,
"this comparison might be written more concisely",
"try simplifying it as shown",
format!(
"{} != {}",
snippet_with_applicability(
cx,
expression_info.left_span.source_callsite(),
"..",
&mut applicability
),
snippet_with_applicability(
cx,
expression_info.right_span.source_callsite(),
"..",
&mut applicability
)
),
applicability,
);
}
}
match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) {
(Some(true), None) => left_true.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h);
}),
(None, Some(true)) => right_true.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h);
}),
(Some(false), None) => left_false.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h);
}),
(None, Some(false)) => right_false.map_or((), |(h, m)| {
suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h);
}),
(None, None) => no_literal.map_or((), |(h, m)| {
let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability);
let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability);
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
binop_span,
m,
"try simplifying it as shown",
h(left_side, right_side).into_string(),
applicability,
);
}),
_ => (),
}
}
}
}
fn suggest_bool_comparison<'a, 'tcx>(
cx: &LateContext<'tcx>,
span: Span,
expr: &Expr<'_>,
mut app: Applicability,
message: &'static str,
conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>,
) {
let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app);
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
span,
message,
"try simplifying it as shown",
conv_hint(hint).into_string(),
app,
);
}
enum Expression {
Bool(bool),
RetBool(bool),

View file

@ -1,6 +1,6 @@
use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind};
use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -70,12 +70,24 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach {
&& has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some()
// Skip the lint if the body is not block because this is simpler than `for` loop.
// e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop.
&& let ExprKind::Closure(&Closure { body, .. }) = for_each_arg.kind
&& let ExprKind::Closure(&Closure { body, fn_decl, .. }) = for_each_arg.kind
&& let body = cx.tcx.hir_body(body)
// Skip the lint if the body is not safe, so as not to suggest `for … in … unsafe {}`
// and suggesting `for … in … { unsafe { } }` is a little ugly.
&& !matches!(body.value.kind, ExprKind::Block(Block { rules: BlockCheckMode::UnsafeBlock(_), .. }, ..))
{
let mut applicability = Applicability::MachineApplicable;
// If any closure parameter has an explicit type specified, applying the lint would necessarily
// remove that specification, possibly breaking type inference
if fn_decl
.inputs
.iter()
.any(|input| matches!(input.kind, TyKind::Infer(..)))
{
applicability = Applicability::MaybeIncorrect;
}
let mut ret_collector = RetCollector::default();
ret_collector.visit_expr(body.value);
@ -84,18 +96,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach {
return;
}
let (mut applicability, ret_suggs) = if ret_collector.spans.is_empty() {
(Applicability::MachineApplicable, None)
let ret_suggs = if ret_collector.spans.is_empty() {
None
} else {
(
Applicability::MaybeIncorrect,
Some(
ret_collector
.spans
.into_iter()
.map(|span| (span, "continue".to_string()))
.collect(),
),
applicability = Applicability::MaybeIncorrect;
Some(
ret_collector
.spans
.into_iter()
.map(|span| (span, "continue".to_string()))
.collect(),
)
};

View file

@ -134,7 +134,8 @@ impl LateLintPass<'_> for NonCanonicalImpls {
return;
}
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id)
let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id);
if trait_name == Some(sym::Clone)
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
{
@ -170,12 +171,8 @@ impl LateLintPass<'_> for NonCanonicalImpls {
String::new(),
Applicability::MaybeIncorrect,
);
return;
}
}
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id)
} else if trait_name == Some(sym::PartialOrd)
&& impl_item.ident.name == sym::partial_cmp
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])

View file

@ -43,13 +43,11 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
match &expr.kind {
ExprKind::MethodCall(path, func, [param], _) => {
if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def()
&& ((path.ident.name == sym::mode
&& matches!(
cx.tcx.get_diagnostic_name(adt.did()),
Some(sym::FsOpenOptions | sym::DirBuilder)
))
|| (path.ident.name == sym::set_mode
&& cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
&& matches!(
(cx.tcx.get_diagnostic_name(adt.did()), path.ident.name),
(Some(sym::FsOpenOptions | sym::DirBuilder), sym::mode)
| (Some(sym::FsPermissions), sym::set_mode)
)
&& let ExprKind::Lit(_) = param.kind
&& param.span.eq_ctxt(expr.span)
&& param

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::Msrv;
use clippy_utils::qualify_min_const_fn::is_stable_const_fn;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::for_each_expr_without_closures;
@ -20,7 +21,7 @@ pub(super) fn check<'tcx>(
expr: &'tcx hir::Expr<'_>,
assignee: &'tcx hir::Expr<'_>,
e: &'tcx hir::Expr<'_>,
_msrv: Msrv,
msrv: Msrv,
) {
if let hir::ExprKind::Binary(op, l, r) = &e.kind {
let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
@ -43,10 +44,28 @@ pub(super) fn check<'tcx>(
}
}
// Skip if the trait is not stable in const contexts
// FIXME: reintroduce a better check after this is merged back into Clippy
// Skip if the trait or the implementation is not stable in const contexts
if is_in_const_context(cx) {
return;
if cx
.tcx
.associated_item_def_ids(trait_id)
.first()
.is_none_or(|binop_id| !is_stable_const_fn(cx, *binop_id, msrv))
{
return;
}
let impls = cx.tcx.non_blanket_impls_for_ty(trait_id, rty).collect::<Vec<_>>();
if impls.is_empty()
|| impls.into_iter().any(|impl_id| {
cx.tcx
.associated_item_def_ids(impl_id)
.first()
.is_none_or(|fn_id| !is_stable_const_fn(cx, *fn_id, msrv))
})
{
return;
}
}
span_lint_and_then(

View file

@ -47,14 +47,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool)
(arg, arg.span)
},
ExprKind::Call(path, [arg])
if path_def_id(cx, path).is_some_and(|did| {
if cx.tcx.is_diagnostic_item(sym::from_str_method, did) {
true
} else if cx.tcx.is_diagnostic_item(sym::from_fn, did) {
!is_copy(cx, typeck.expr_ty(expr))
} else {
false
}
if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) {
Some(sym::from_str_method) => true,
Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)),
_ => false,
}) =>
{
(arg, arg.span)

View file

@ -34,10 +34,11 @@ pub(crate) fn check<'tcx>(
val_r,
) = lhs.kind
// right hand side matches either f32::EPSILON or f64::EPSILON
// right hand side matches _::EPSILON
&& let ExprKind::Path(ref epsilon_path) = rhs.kind
&& let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id)
&& ([sym::f32_epsilon, sym::f64_epsilon].into_iter().any(|sym| cx.tcx.is_diagnostic_item(sym, def_id)))
&& let Some(sym) = cx.tcx.get_diagnostic_name(def_id)
&& matches!(sym, sym::f16_epsilon | sym::f32_epsilon | sym::f64_epsilon | sym::f128_epsilon)
// values of the subtractions on the left hand side are of the type float
&& let t_val_l = cx.typeck_results().expr_ty(val_l)

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_in_test;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_in_test, is_inside_always_const_context};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
if is_panic(cx, macro_call.def_id) {
if cx.tcx.hir_is_inside_const_context(expr.hir_id)
if is_inside_always_const_context(cx.tcx, expr.hir_id)
|| self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id)
{
return;
@ -140,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
&& let Res::Def(DefKind::Fn, def_id) = expr_path.res
&& cx.tcx.is_diagnostic_item(sym::panic_any, def_id)
{
if cx.tcx.hir_is_inside_const_context(expr.hir_id)
if is_inside_always_const_context(cx.tcx, expr.hir_id)
|| self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id)
{
return;

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::fn_has_unsatisfiable_preds;
use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth};
use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth};
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl, LangItem, def_id};
@ -96,14 +96,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
let (fn_def_id, arg, arg_ty, clone_ret) =
unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id)
|| cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id)
|| (cx.tcx.is_diagnostic_item(sym::to_string_method, fn_def_id)
&& is_type_lang_item(cx, arg_ty, LangItem::String));
let fn_name = cx.tcx.get_diagnostic_name(fn_def_id);
let from_deref = !from_borrow
&& (cx.tcx.is_diagnostic_item(sym::path_to_pathbuf, fn_def_id)
|| cx.tcx.is_diagnostic_item(sym::os_str_to_os_string, fn_def_id));
let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id)
|| fn_name == Some(sym::to_owned_method)
|| (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String));
let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string));
if !from_borrow && !from_deref {
continue;
@ -148,8 +147,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
is_call_with_ref_arg(cx, mir, &pred_terminator.kind)
&& res == cloned
&& cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id)
&& (is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf)
|| is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString))
&& let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind()
&& let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did())
&& matches!(pred_arg_name, sym::PathBuf | sym::OsString)
{
(pred_arg, res)
} else {

View file

@ -155,6 +155,11 @@ impl LateLintPass<'_> for SemicolonBlock {
kind: ExprKind::Block(block, _),
..
}) if !block.span.from_expansion() => {
let attrs = cx.tcx.hir_attrs(stmt.hir_id);
if !attrs.is_empty() && !cx.tcx.features().stmt_expr_attributes() {
return;
}
if let Some(tail) = block.expr {
self.semicolon_inside_block(cx, block, tail, stmt.span);
}

View file

@ -126,6 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
&& !is_from_proc_macro(cx, &first_segment.ident)
&& !matches!(def_kind, DefKind::Macro(_))
&& let Some(last_segment) = path.segments.last()
&& let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res
&& crate_def_id.is_crate_root()
{
let (lint, used_mod, replace_with) = match first_segment.ident.name {
sym::std => match cx.tcx.crate_name(def_id.krate) {

View file

@ -457,7 +457,8 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace {
}
fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool {
cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id)
|| cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id)
|| cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id)
matches!(
cx.tcx.get_diagnostic_name(trim_def_id),
Some(sym::str_trim | sym::str_trim_start | sym::str_trim_end)
)
}

View file

@ -1,8 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{HasSession, SpanRangeExt as _};
use rustc_errors::Applicability;
use rustc_hir::{GenericArg, HirId, LetStmt, Node, Path, TyKind};
use rustc_hir::{Expr, GenericArg, HirId, LetStmt, Node, Path, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_middle::ty::{self, Ty};
use rustc_span::Span;
use crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS;
@ -38,6 +42,7 @@ fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool {
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
path: &Path<'tcx>,
arg: &Expr<'tcx>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
expr_hir_id: HirId,
@ -68,14 +73,48 @@ pub(super) fn check<'tcx>(
} else if is_function_block(cx, expr_hir_id) {
return false;
}
span_lint_and_sugg(
let span = last.ident.span.with_hi(path.span.hi());
span_lint_and_then(
cx,
MISSING_TRANSMUTE_ANNOTATIONS,
last.ident.span.with_hi(path.span.hi()),
span,
"transmute used without annotations",
"consider adding missing annotations",
format!("{}::<{from_ty}, {to_ty}>", last.ident),
Applicability::MaybeIncorrect,
|diag| {
let from_ty_no_name = ty_cannot_be_named(from_ty);
let to_ty_no_name = ty_cannot_be_named(to_ty);
if from_ty_no_name || to_ty_no_name {
let to_name = match (from_ty_no_name, to_ty_no_name) {
(true, false) => maybe_name_by_expr(cx, arg.span, "the origin type"),
(false, true) => "the destination type".into(),
_ => "the source and destination types".into(),
};
diag.help(format!(
"consider giving {to_name} a name, and adding missing type annotations"
));
} else {
diag.span_suggestion(
span,
"consider adding missing annotations",
format!("{}::<{from_ty}, {to_ty}>", last.ident),
Applicability::MaybeIncorrect,
);
}
},
);
true
}
fn ty_cannot_be_named(ty: Ty<'_>) -> bool {
matches!(
ty.kind(),
ty::Alias(ty::AliasTyKind::Opaque | ty::AliasTyKind::Inherent, _)
)
}
fn maybe_name_by_expr<'a>(sess: &impl HasSession, span: Span, default: &'a str) -> Cow<'a, str> {
span.with_source_text(sess, |name| {
(name.len() + 9 < default.len()).then_some(format!("`{name}`'s type").into())
})
.flatten()
.unwrap_or(default.into())
}

View file

@ -520,7 +520,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
| transmuting_null::check(cx, e, arg, to_ty)
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id)
| missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)

View file

@ -1,6 +1,6 @@
use super::TRANSMUTE_INT_TO_NON_ZERO;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
@ -16,35 +16,24 @@ pub(super) fn check<'tcx>(
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
) -> bool {
let tcx = cx.tcx;
let (ty::Int(_) | ty::Uint(_), ty::Adt(adt, substs)) = (&from_ty.kind(), to_ty.kind()) else {
return false;
};
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
return false;
if let ty::Int(_) | ty::Uint(_) = from_ty.kind()
&& let ty::Adt(adt, substs) = to_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::NonZero, adt.did())
&& let int_ty = substs.type_at(0)
&& from_ty == int_ty
{
let arg = Sugg::hir(cx, arg, "..");
span_lint_and_sugg(
cx,
TRANSMUTE_INT_TO_NON_ZERO,
e.span,
format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero),
"consider using",
format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked),
Applicability::Unspecified,
);
true
} else {
false
}
let int_ty = substs.type_at(0);
if from_ty != int_ty {
return false;
}
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_NON_ZERO,
e.span,
format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
diag.span_suggestion(
e.span,
"consider using",
format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked),
Applicability::Unspecified,
);
},
);
true
}

View file

@ -11,7 +11,8 @@ use super::RC_BUFFER;
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
let app = Applicability::Unspecified;
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
let name = cx.tcx.get_diagnostic_name(def_id);
if name == Some(sym::Rc) {
if let Some(alternate) = match_buffer_type(cx, qpath) {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
@ -56,7 +57,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
);
return true;
}
} else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) {
} else if name == Some(sym::Arc) {
if let Some(alternate) = match_buffer_type(cx, qpath) {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(

View file

@ -13,14 +13,11 @@ use super::{REDUNDANT_ALLOCATION, utils};
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: &QPath<'tcx>, def_id: DefId) -> bool {
let mut applicability = Applicability::MaybeIncorrect;
let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() {
"Box"
} else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
"Rc"
} else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) {
"Arc"
} else {
return false;
let outer_sym = match cx.tcx.get_diagnostic_name(def_id) {
_ if Some(def_id) == cx.tcx.lang_items().owned_box() => "Box",
Some(sym::Rc) => "Rc",
Some(sym::Arc) => "Arc",
_ => return false,
};
if let Some(span) = utils::match_borrows_parameter(cx, qpath) {

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sym;
use clippy_utils::{is_unit_expr, sym};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@ -16,10 +16,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail",
_ => return,
};
let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
let Some((lhs, rhs, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
return;
};
if !cx.typeck_results().expr_ty(left).is_unit() {
if is_unit_expr(lhs) || is_unit_expr(rhs) {
return;
}
if !cx.typeck_results().expr_ty(lhs).is_unit() {
return;
}
span_lint(

View file

@ -41,7 +41,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings {
&& let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind()
&& inner_str.is_str()
{
if cx.tcx.is_diagnostic_item(sym::string_new, fun_def_id) {
let fun_name = cx.tcx.get_diagnostic_name(fun_def_id);
if fun_name == Some(sym::string_new) {
span_lint_and_sugg(
cx,
UNNECESSARY_OWNED_EMPTY_STRINGS,
@ -51,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings {
"\"\"".to_owned(),
Applicability::MachineApplicable,
);
} else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id)
} else if fun_name == Some(sym::from_fn)
&& let [arg] = args
&& let ExprKind::Lit(spanned) = &arg.kind
&& let LitKind::Str(symbol, _) = spanned.node

View file

@ -107,12 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
// Get the wrapper and inner types, if can't, abort.
let (return_type_label, lang_item, inner_type) =
if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() {
if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) {
("Option", OptionSome, subst.type_at(0))
} else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) {
("Result", ResultOk, subst.type_at(0))
} else {
return;
match cx.tcx.get_diagnostic_name(adt_def.did()) {
Some(sym::Option) => ("Option", OptionSome, subst.type_at(0)),
Some(sym::Result) => ("Result", ResultOk, subst.type_at(0)),
_ => return,
}
} else {
return;

View file

@ -326,7 +326,11 @@ fn extend_with_struct_pat(
if idx_1 == idx {
// In the case of `k`, we merely require identical field names
// so that we will transform into `ident_k: p1_k | p2_k`.
let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident));
let pos = fps2.iter().position(|fp2| {
// Avoid `Foo { bar } | Foo { bar }` => `Foo { bar | bar }`
!(fp1.is_shorthand && fp2.is_shorthand)
&& eq_id(fp1.ident, fp2.ident)
});
pos_in_2.set(pos);
pos.is_some()
} else {

View file

@ -89,7 +89,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
{
// We don't want to lint inside io::Read or io::Write implementations, as the author has more
// information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836
if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) {
if let Some(trait_name) = cx.tcx.get_diagnostic_name(trait_id)
&& matches!(trait_name, sym::IoRead | sym::IoWrite)
{
return;
}

View file

@ -6,13 +6,13 @@ use rustc_errors::Applicability;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term,
Ty, TyKind,
AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, PolyTraitRef, Term, Ty,
TyKind,
};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::edition::Edition;
use rustc_span::{BytePos, Span, sym};
use rustc_span::{BytePos, Pos as _, Span, sym};
declare_clippy_lint! {
/// ### What it does
@ -49,19 +49,22 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit {
decl: &'tcx FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
span: Span,
def_id: LocalDefId,
_def_id: LocalDefId,
) {
if let FnRetTy::Return(hir_ty) = decl.output
&& is_unit_ty(hir_ty)
&& !hir_ty.span.from_expansion()
&& get_def(span) == get_def(hir_ty.span)
{
// implicit types in closure signatures are forbidden when `for<...>` is present
if let FnKind::Closure = kind
&& let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id)
&& let ExprKind::Closure(closure) = expr.kind
&& !closure.bound_generic_params.is_empty()
{
// The explicit `-> ()` in the closure signature might be necessary for multiple reasons:
// - Implicit types in closure signatures are forbidden when `for<...>` is present
// - If the closure body ends with a function call, and that function's return type is generic, the
// `-> ()` could be required for it to be inferred
//
// There could be more reasons to have it, and, in general, we shouldn't discourage the users from
// writing more type annotations than strictly necessary, because it can help readability and
// maintainability
if let FnKind::Closure = kind {
return;
}
@ -97,16 +100,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit {
}
fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) {
let segments = &poly.trait_ref.path.segments;
if segments.len() == 1
&& matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce)
&& let Some(args) = segments[0].args
if let [segment] = &poly.trait_ref.path.segments
&& matches!(segment.ident.name, sym::Fn | sym::FnMut | sym::FnOnce)
&& let Some(args) = segment.args
&& args.parenthesized == GenericArgsParentheses::ParenSugar
&& let constraints = &args.constraints
&& constraints.len() == 1
&& constraints[0].ident.name == sym::Output
&& let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind
&& let [constraint] = &args.constraints
&& constraint.ident.name == sym::Output
&& let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraint.kind
&& args.span_ext.hi() != poly.span.hi()
&& !hir_ty.span.from_expansion()
&& args.span_ext.hi() != hir_ty.span.hi()
@ -160,17 +160,15 @@ fn get_def(span: Span) -> Option<Span> {
fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) {
let (ret_span, appl) =
span.with_hi(ty_span.hi())
.get_source_text(cx)
.map_or((ty_span, Applicability::MaybeIncorrect), |src| {
position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| {
(
#[expect(clippy::cast_possible_truncation)]
ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)),
Applicability::MachineApplicable,
)
})
});
if let Some(Some(rpos)) = span.with_hi(ty_span.hi()).with_source_text(cx, position_before_rarrow) {
(
ty_span.with_lo(span.lo() + BytePos::from_usize(rpos)),
Applicability::MachineApplicable,
)
} else {
(ty_span, Applicability::MaybeIncorrect)
};
span_lint_and_sugg(
cx,
UNUSED_UNIT,

View file

@ -1,13 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{method_chain_args, return_ty};
use core::ops::ControlFlow;
use rustc_hir as hir;
use rustc_hir::ImplItemKind;
use clippy_utils::{return_ty, sym};
use rustc_hir::{
Body, BodyOwnerKind, Expr, ExprKind, FnSig, ImplItem, ImplItemKind, Item, ItemKind, OwnerId, PathSegment, QPath,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::{Span, sym};
use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass;
use rustc_span::{Ident, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -57,62 +56,165 @@ declare_clippy_lint! {
"functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
}
declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
impl_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
if let ImplItemKind::Fn(ref _signature, _) = impl_item.kind
// first check if it's a method or function
// checking if its return type is `result` or `option`
&& (is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result)
|| is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option))
{
lint_impl_body(cx, impl_item.span, impl_item);
#[derive(Clone, Copy, Eq, PartialEq)]
enum OptionOrResult {
Option,
Result,
}
impl OptionOrResult {
fn with_article(self) -> &'static str {
match self {
Self::Option => "an `Option`",
Self::Result => "a `Result`",
}
}
}
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
let body = cx.tcx.hir_body(body_id);
let typeck = cx.tcx.typeck(impl_item.owner_id.def_id);
let mut result = Vec::new();
let _: Option<!> = for_each_expr(cx, body.value, |e| {
// check for `expect`
if let Some(arglists) = method_chain_args(e, &[sym::expect]) {
let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(cx, receiver_ty, sym::Result)
{
result.push(e.span);
}
}
struct OptionOrResultFn {
kind: OptionOrResult,
return_ty_span: Option<Span>,
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(e, &[sym::unwrap]) {
let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(cx, receiver_ty, sym::Result)
{
result.push(e.span);
}
}
#[derive(Default)]
pub struct UnwrapInResult {
fn_stack: Vec<Option<OptionOrResultFn>>,
current_fn: Option<OptionOrResultFn>,
}
ControlFlow::Continue(())
impl UnwrapInResult {
fn enter_item(&mut self, cx: &LateContext<'_>, fn_def_id: OwnerId, sig: &FnSig<'_>) {
self.fn_stack.push(self.current_fn.take());
self.current_fn = is_option_or_result(cx, return_ty(cx, fn_def_id)).map(|kind| OptionOrResultFn {
kind,
return_ty_span: Some(sig.decl.output.span()),
});
}
// if we've found one, lint
if !result.is_empty() {
fn leave_item(&mut self) {
self.current_fn = self.fn_stack.pop().unwrap();
}
}
impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
if let ImplItemKind::Fn(sig, _) = &impl_item.kind {
self.enter_item(cx, impl_item.owner_id, sig);
}
}
fn check_impl_item_post(&mut self, _: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) {
if let ImplItemKind::Fn(..) = impl_item.kind {
self.leave_item();
}
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn {
has_body: true, sig, ..
} = &item.kind
{
self.enter_item(cx, item.owner_id, sig);
}
}
fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn { has_body: true, .. } = item.kind {
self.leave_item();
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let Some(OptionOrResultFn {
kind,
ref mut return_ty_span,
}) = self.current_fn
&& let Some((oor, fn_name)) = is_unwrap_or_expect_call(cx, expr)
&& oor == kind
{
span_lint_and_then(
cx,
UNWRAP_IN_RESULT,
impl_span,
"used unwrap or expect in a function that returns result or option",
move |diag| {
diag.help("unwrap and expect should not be used in a function that returns result or option");
diag.span_note(result, "potential non-recoverable error(s)");
expr.span,
format!("`{fn_name}` used in a function that returns {}", kind.with_article()),
|diag| {
// Issue the note and help only once per function
if let Some(span) = return_ty_span.take() {
diag.span_note(span, "in this function signature");
let complement = if kind == OptionOrResult::Result {
" or calling the `.map_err()` method"
} else {
""
};
diag.help(format!("consider using the `?` operator{complement}"));
}
},
);
}
}
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) {
let body_def_id = cx.tcx.hir_body_owner_def_id(body.id());
if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) {
// When entering a body which is not a function, mask the potential surrounding
// function to not apply the lint.
self.fn_stack.push(self.current_fn.take());
}
}
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) {
let body_def_id = cx.tcx.hir_body_owner_def_id(body.id());
if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) {
// Unmask the potential surrounding function.
self.current_fn = self.fn_stack.pop().unwrap();
}
}
}
fn is_option_or_result(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<OptionOrResult> {
match ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did())) {
Some(sym::Option) => Some(OptionOrResult::Option),
Some(sym::Result) => Some(OptionOrResult::Result),
_ => None,
}
}
fn is_unwrap_or_expect_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(OptionOrResult, Symbol)> {
if let ExprKind::Call(func, _) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(
hir_ty,
PathSegment {
ident:
Ident {
name: name @ (sym::unwrap | sym::expect),
..
},
..
},
)) = func.kind
{
is_option_or_result(cx, cx.typeck_results().node_type(hir_ty.hir_id)).map(|oor| (oor, *name))
} else if let ExprKind::MethodCall(
PathSegment {
ident: Ident {
name: name @ (sym::unwrap | sym::expect),
..
},
..
},
recv,
_,
_,
) = expr.kind
{
is_option_or_result(cx, cx.typeck_results().expr_ty_adjusted(recv)).map(|oor| (oor, *name))
} else {
None
}
}

View file

@ -386,12 +386,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
ExprKind::Call(path, [arg]) => {
if let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& !is_ty_alias(qpath)
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
{
let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(arg);
if cx.tcx.is_diagnostic_item(sym::try_from_fn, def_id)
if name == sym::try_from_fn
&& is_type_diagnostic_item(cx, a, sym::Result)
&& let ty::Adt(_, args) = a.kind()
&& let Some(a_type) = args.types().next()
@ -406,9 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
None,
hint,
);
}
if cx.tcx.is_diagnostic_item(sym::from_fn, def_id) && same_type_and_consts(a, b) {
} else if name == sym::from_fn && same_type_and_consts(a, b) {
let mut app = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_paren();
let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));

View file

@ -1,4 +1,6 @@
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::mem;
use std::ops::ControlFlow;
use clippy_config::Conf;
@ -20,15 +22,36 @@ use rustc_span::{DesugaringKind, Span};
pub struct UselessVec {
too_large_for_stack: u64,
msrv: Msrv,
span_to_lint_map: BTreeMap<Span, Option<(HirId, SuggestedType, String, Applicability)>>,
/// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can
/// emit a warning there or not).
///
/// The purpose of this is to buffer lints up until `check_crate_post` so that we can cancel a
/// lint while visiting, because a `vec![]` invocation span can appear multiple times when
/// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and
/// another time that does. Consider:
/// ```
/// macro_rules! m {
/// ($v:expr) => {
/// let a = $v;
/// $v.push(3);
/// }
/// }
/// m!(vec![1, 2]);
/// ```
/// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing
/// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would
/// work, we get a false positive warning on the `$v.push(3)` which really requires `$v` to
/// be a vector.
span_to_state: BTreeMap<Span, VecState>,
allow_in_test: bool,
}
impl UselessVec {
pub fn new(conf: &'static Conf) -> Self {
Self {
too_large_for_stack: conf.too_large_for_stack,
msrv: conf.msrv,
span_to_lint_map: BTreeMap::new(),
span_to_state: BTreeMap::new(),
allow_in_test: conf.allow_useless_vec_in_tests,
}
}
@ -62,17 +85,28 @@ declare_clippy_lint! {
impl_lint_pass!(UselessVec => [USELESS_VEC]);
impl<'tcx> LateLintPass<'tcx> for UselessVec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) else {
return;
};
if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) {
return;
}
// the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!`
let callsite = expr.span.parent_callsite().unwrap_or(expr.span);
/// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed.
enum VecState {
Change {
suggest_ty: SuggestedType,
vec_snippet: String,
expr_hir_id: HirId,
},
NoChange,
}
enum VecToArray {
/// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or
/// slice).
Possible,
/// Expression must be a `Vec<_>`. Type cannot change.
Impossible,
}
impl UselessVec {
/// Checks if the surrounding environment requires this expression to actually be of type
/// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors.
fn expr_usage_requires_vec(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) -> VecToArray {
match cx.tcx.parent_hir_node(expr.hir_id) {
// search for `let foo = vec![_]` expressions where all uses of `foo`
// adjust to slices or call a method that exist on slices (e.g. len)
@ -100,107 +134,123 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
.is_continue();
if only_slice_uses {
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, SuggestedType::Array);
VecToArray::Possible
} else {
self.span_to_lint_map.insert(callsite, None);
VecToArray::Impossible
}
},
// if the local pattern has a specified type, do not lint.
Node::LetStmt(LetStmt { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => {
self.span_to_lint_map.insert(callsite, None);
VecToArray::Impossible
},
// search for `for _ in vec![...]`
Node::Expr(Expr { span, .. })
if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) =>
{
let suggest_slice = suggest_type(expr);
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice);
VecToArray::Possible
},
// search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
_ => {
let suggest_slice = suggest_type(expr);
if adjusts_to_slice(cx, expr) {
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice);
VecToArray::Possible
} else {
self.span_to_lint_map.insert(callsite, None);
VecToArray::Impossible
}
},
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for (span, lint_opt) in &self.span_to_lint_map {
if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt {
let help_msg = format!("you can use {} directly", suggest_slice.desc());
span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| {
// If the `vec!` macro contains comment, better not make the suggestion machine
// applicable as it would remove them.
let applicability = if *applicability != Applicability::Unspecified
&& let source_map = cx.tcx.sess.source_map()
&& span_contains_comment(source_map, *span)
{
Applicability::Unspecified
} else {
*applicability
};
diag.span_suggestion(*span, help_msg, snippet, applicability);
});
}
}
}
}
impl UselessVec {
fn check_vec_macro<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
vec_args: &higher::VecArgs<'tcx>,
span: Span,
hir_id: HirId,
suggest_slice: SuggestedType,
) {
if span.from_expansion() {
return;
impl<'tcx> LateLintPass<'tcx> for UselessVec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows())
// The `vec![]` or `&vec![]` invocation span.
&& let vec_span = expr.span.parent_callsite().unwrap_or(expr.span)
&& !vec_span.from_expansion()
{
if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) {
return;
}
match self.expr_usage_requires_vec(cx, expr) {
VecToArray::Possible => {
let suggest_ty = suggest_type(expr);
// Size and `Copy` checks don't depend on the enclosing usage of the expression
// and don't need to be inserted into the state map.
let vec_snippet = match vec_args {
higher::VecArgs::Repeat(expr, len) => {
if is_copy(cx, cx.typeck_results().expr_ty(expr))
&& let Some(Constant::Int(length)) = ConstEvalCtxt::new(cx).eval(len)
&& let Ok(length) = u64::try_from(length)
&& size_of(cx, expr)
.checked_mul(length)
.is_some_and(|size| size <= self.too_large_for_stack)
{
suggest_ty.snippet(
cx,
Some(expr.span.source_callsite()),
Some(len.span.source_callsite()),
)
} else {
return;
}
},
higher::VecArgs::Vec(args) => {
if let Ok(length) = u64::try_from(args.len())
&& size_of(cx, expr)
.checked_mul(length)
.is_some_and(|size| size <= self.too_large_for_stack)
{
suggest_ty.snippet(
cx,
args.first().zip(args.last()).map(|(first, last)| {
first.span.source_callsite().to(last.span.source_callsite())
}),
None,
)
} else {
return;
}
},
};
if let Entry::Vacant(entry) = self.span_to_state.entry(vec_span) {
entry.insert(VecState::Change {
suggest_ty,
vec_snippet,
expr_hir_id: expr.hir_id,
});
}
},
VecToArray::Impossible => {
self.span_to_state.insert(vec_span, VecState::NoChange);
},
}
}
}
let snippet = match *vec_args {
higher::VecArgs::Repeat(elem, len) => {
if let Some(Constant::Int(len_constant)) = ConstEvalCtxt::new(cx).eval(len) {
// vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also
if !is_copy(cx, cx.typeck_results().expr_ty(elem)) {
return;
}
#[expect(clippy::cast_possible_truncation)]
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
return;
}
suggest_slice.snippet(cx, Some(elem.span), Some(len.span))
} else {
return;
}
},
higher::VecArgs::Vec(args) => {
let args_span = if let Some(last) = args.iter().last() {
if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
return;
}
Some(args[0].span.source_callsite().to(last.span.source_callsite()))
} else {
None
};
suggest_slice.snippet(cx, args_span, None)
},
};
self.span_to_lint_map.entry(span).or_insert(Some((
hir_id,
suggest_slice,
snippet,
Applicability::MachineApplicable,
)));
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for (span, state) in mem::take(&mut self.span_to_state) {
if let VecState::Change {
suggest_ty,
vec_snippet,
expr_hir_id,
} = state
{
span_lint_hir_and_then(cx, USELESS_VEC, expr_hir_id, span, "useless use of `vec!`", |diag| {
let help_msg = format!("you can use {} directly", suggest_ty.desc());
// If the `vec!` macro contains comment, better not make the suggestion machine applicable as it
// would remove them.
let applicability = if span_contains_comment(cx.tcx.sess.source_map(), span) {
Applicability::Unspecified
} else {
Applicability::MachineApplicable
};
diag.span_suggestion(span, help_msg, vec_snippet, applicability);
});
}
}
}
}
@ -221,11 +271,17 @@ impl SuggestedType {
}
fn snippet(self, cx: &LateContext<'_>, args_span: Option<Span>, len_span: Option<Span>) -> String {
// Invariant of the lint as implemented: all spans are from the root context (and as a result,
// always trivially crate-local).
assert!(args_span.is_none_or(|s| !s.from_expansion()));
assert!(len_span.is_none_or(|s| !s.from_expansion()));
let maybe_args = args_span
.and_then(|sp| sp.get_source_text(cx))
.map(|sp| sp.get_source_text(cx).expect("spans are always crate-local"))
.map_or(String::new(), |x| x.to_owned());
let maybe_len = len_span
.and_then(|sp| sp.get_source_text(cx).map(|s| format!("; {s}")))
.map(|sp| sp.get_source_text(cx).expect("spans are always crate-local"))
.map(|st| format!("; {st}"))
.unwrap_or_default();
match self {

View file

@ -537,7 +537,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span));
if let Some((_, index)) = positional_arg_piece_span(piece) {
if let Some((_, index)) = format_arg_piece_span(piece) {
replaced_position.push(index);
}
@ -569,16 +569,11 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
}
}
/// Extract Span and its index from the given `piece`, if it's positional argument.
fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
/// Extract Span and its index from the given `piece`
fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
match piece {
FormatArgsPiece::Placeholder(FormatPlaceholder {
argument:
FormatArgPosition {
index: Ok(index),
kind: FormatArgPositionKind::Number,
..
},
argument: FormatArgPosition { index: Ok(index), .. },
span: Some(span),
..
}) => Some((*span, *index)),

View file

@ -1,5 +1,6 @@
use ControlFlow::{Break, Continue};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{fn_def_id, get_enclosing_block, path_to_local_id};
use rustc_ast::Mutability;
use rustc_ast::visit::visit_opt;
@ -58,8 +59,8 @@ declare_lint_pass!(ZombieProcesses => [ZOMBIE_PROCESSES]);
impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind
&& let Some(child_adt) = cx.typeck_results().expr_ty(expr).ty_adt_def()
&& cx.tcx.is_diagnostic_item(sym::Child, child_adt.did())
&& let child_ty = cx.typeck_results().expr_ty(expr)
&& is_type_diagnostic_item(cx, child_ty, sym::Child)
{
match cx.tcx.parent_hir_node(expr.hir_id) {
Node::LetStmt(local)
@ -177,8 +178,8 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {},
Node::Expr(expr)
if let Some(fn_did) = fn_def_id(self.cx, expr)
&& (self.cx.tcx.is_diagnostic_item(sym::child_id, fn_did)
|| self.cx.tcx.is_diagnostic_item(sym::child_kill, fn_did)) => {},
&& let Some(fn_name) = self.cx.tcx.get_diagnostic_name(fn_did)
&& matches!(fn_name, sym::child_id | sym::child_kill) => {},
// Conservatively assume that all other kinds of nodes call `.wait()` somehow.
_ => return Break(MaybeWait(ex.span)),
@ -351,9 +352,14 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus
/// Checks if the given expression exits the process.
fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn_def_id(cx, expr).is_some_and(|fn_did| {
cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || cx.tcx.is_diagnostic_item(sym::process_abort, fn_did)
})
if let Some(fn_did) = fn_def_id(cx, expr)
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(fn_did)
&& matches!(fn_name, sym::process_exit | sym::process_abort)
{
true
} else {
false
}
}
#[derive(Debug)]

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-08-22
nightly-2025-09-04
```
<!-- end autogenerated nightly -->

View file

@ -21,7 +21,7 @@ pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
}
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
pub fn unordered_over<X, Y>(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool {
left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
}

View file

@ -287,23 +287,22 @@ impl<'a> VecArgs<'a> {
&& let ExprKind::Path(ref qpath) = fun.kind
&& is_expn_of(fun.span, sym::vec).is_some()
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
&& let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
{
return if cx.tcx.is_diagnostic_item(sym::vec_from_elem, fun_def_id) && args.len() == 2 {
// `vec![elem; size]` case
Some(VecArgs::Repeat(&args[0], &args[1]))
} else if cx.tcx.is_diagnostic_item(sym::slice_into_vec, fun_def_id) && args.len() == 1 {
// `vec![a, b, c]` case
if let ExprKind::Call(_, [arg]) = &args[0].kind
&& let ExprKind::Array(args) = arg.kind
return match (name, args) {
(sym::vec_from_elem, [elem, size]) => {
// `vec![elem; size]` case
Some(VecArgs::Repeat(elem, size))
},
(sym::slice_into_vec, [slice])
if let ExprKind::Call(_, [arg]) = slice.kind
&& let ExprKind::Array(args) = arg.kind =>
{
// `vec![a, b, c]` case
Some(VecArgs::Vec(args))
} else {
None
}
} else if cx.tcx.is_diagnostic_item(sym::vec_new, fun_def_id) && args.is_empty() {
Some(VecArgs::Vec(&[]))
} else {
None
},
(sym::vec_new, []) => Some(VecArgs::Vec(&[])),
_ => None,
};
}

View file

@ -757,7 +757,7 @@ pub fn both_some_and<X, Y>(l: Option<X>, r: Option<Y>, mut pred: impl FnMut(X, Y
}
/// Checks if two slices are equal as per `eq_fn`.
pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
pub fn over<X, Y>(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool {
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}

View file

@ -102,9 +102,9 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
use rustc_hir::{
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring,
CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs,
HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId,
OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr,
};
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
@ -126,6 +126,7 @@ use rustc_span::{InnerSpan, Span};
use source::{SpanRangeExt, walk_span_to_context};
use visitors::{Visitable, for_each_unconsumed_temporary};
use crate::ast_utils::unordered_over;
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
use crate::higher::Range;
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
@ -308,6 +309,7 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) ->
cx.tcx.lang_items().get(item) == Some(did)
}
/// Checks if `expr` is an empty block or an empty tuple.
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
@ -640,9 +642,8 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
&& let Some(impl_did) = cx.tcx.impl_of_assoc(def_id)
&& let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def()
{
return std_types_symbols.iter().any(|&symbol| {
cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string()
});
return Some(adt.did()) == cx.tcx.lang_items().string()
|| (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name));
}
false
}
@ -1992,7 +1993,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
{
zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir))
},
(PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
@ -2004,7 +2005,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
if let ExprKind::Path(ident) = &ident.kind
&& qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
// check fields
&& zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir))
&& over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir))
{
true
} else {
@ -2017,10 +2018,8 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
// check ident
qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
// check fields
&& field_pats.iter().all(|field_pat| {
fields.iter().any(|field| {
field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
})
&& unordered_over(field_pats, fields, |field_pat, field| {
field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
})
},
_ => false,
@ -2405,6 +2404,24 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>
expr
}
/// Returns a `Vec` of `Expr`s containing `AddrOf` operators (`&`) or deref operators (`*`) of a
/// given expression.
pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> {
let mut operators = Vec::new();
peel_hir_expr_while(expr, |expr| match expr.kind {
ExprKind::AddrOf(_, _, e) => {
operators.push(expr);
Some(e)
},
ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => {
operators.push(expr);
Some(e)
},
_ => None,
});
operators
}
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let Res::Def(_, def_id) = path.res
@ -3633,3 +3650,17 @@ pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>)
)
})
}
/// Checks if the expression is an async block (i.e., `async { ... }`).
pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Closure(Closure {
kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared(
CoroutineDesugaring::Async,
CoroutineSource::Block
)),
..
})
)
}

View file

@ -387,10 +387,7 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
/// Checks if the type is a reference equals to a diagnostic item
pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
match ty.kind() {
ty::Ref(_, ref_ty, _) => match ref_ty.kind() {
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
_ => false,
},
ty::Ref(_, ref_ty, _) => is_type_diagnostic_item(cx, *ref_ty, diag_item),
_ => false,
}
}
@ -1378,9 +1375,7 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'
/// Check if `ty` is slice-like, i.e., `&[T]`, `[T; N]`, or `Vec<T>`.
pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
ty.is_slice()
|| ty.is_array()
|| matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did()))
ty.is_slice() || ty.is_array() || is_type_diagnostic_item(cx, ty, sym::Vec)
}
pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {

View file

@ -144,11 +144,9 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.
pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
root_macro_call_first_node(cx, expr).is_some_and(|macro_call| {
[sym::todo_macro, sym::unimplemented_macro]
.iter()
.any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id))
})
root_macro_call_first_node(cx, expr)
.and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id))
.is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro))
}
/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-08-22"
channel = "nightly-2025-09-04"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -330,7 +330,8 @@ pub fn main() {
// Do not run Clippy for Cargo's info queries so that invalid CLIPPY_ARGS are not cached
// https://github.com/rust-lang/cargo/issues/14385
let info_query = has_arg(&orig_args, "-vV") || has_arg(&orig_args, "--print");
let info_query = has_arg(&orig_args, "-vV")
|| arg_value(&orig_args, "--print", |val| val != "crate-root-lint-levels").is_some();
let clippy_enabled = !cap_lints_allow && relevant_package && !info_query;
if clippy_enabled {

View file

@ -0,0 +1,30 @@
#![feature(rustc_private)]
// This test checks that all lints defined in `clippy_config::conf` in `#[lints]`
// attributes exist as Clippy lints.
//
// This test is a no-op if run as part of the compiler test suite
// and will always succeed.
use std::collections::HashSet;
#[test]
fn config_consistency() {
if option_env!("RUSTC_TEST_SUITE").is_some() {
return;
}
let lint_names: HashSet<String> = clippy_lints::declared_lints::LINTS
.iter()
.map(|lint_info| lint_info.lint.name.strip_prefix("clippy::").unwrap().to_lowercase())
.collect();
for conf in clippy_config::get_configuration_metadata() {
for lint in conf.lints {
assert!(
lint_names.contains(*lint),
"Configuration option {} references lint `{lint}` which does not exist",
conf.name
);
}
}
}

View file

@ -0,0 +1,34 @@
// Check that we do not have `profile.*` sections in our `Cargo.toml` files,
// as this causes warnings when run from the compiler repository which includes
// Clippy in a workspace.
//
// Those sections can be put into `.cargo/config.toml` which will be read
// when commands are issued from the top-level Clippy directory, outside of
// a workspace.
use std::fs::File;
use std::io::{self, BufRead as _};
use walkdir::WalkDir;
#[test]
fn no_profile_in_cargo_toml() {
// This check could parse `Cargo.toml` using a TOML deserializer, but in practice
// profile sections would be added at the beginning of a line as `[profile.*]`, so
// keep it fast and simple.
for entry in WalkDir::new(".")
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_name().to_str() == Some("Cargo.toml"))
{
for line in io::BufReader::new(File::open(entry.path()).unwrap())
.lines()
.map(Result::unwrap)
{
if line.starts_with("[profile.") {
eprintln!("Profile section `{line}` found in file `{}`.", entry.path().display());
eprintln!("Use `.cargo/config.toml` for profiles specific to the standalone Clippy repository.");
panic!("Profile section found in `Cargo.toml`");
}
}
}
}

View file

@ -4,7 +4,7 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
LL | cx.span_lint(lint, span, |lint| {
| ^^^^^^^^^
|
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead
= note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead
note: the lint level is defined here
--> tests/ui-internal/disallow_span_lint.rs:2:9
|
@ -17,7 +17,7 @@ error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_
LL | tcx.node_span_lint(lint, hir_id, span, |lint| {
| ^^^^^^^^^^^^^^
|
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead
= note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead
error: aborting due to 2 previous errors

View file

@ -0,0 +1 @@
const-literal-digits-threshold = 20

View file

@ -0,0 +1,38 @@
#![warn(clippy::excessive_precision)]
#![allow(
dead_code,
overflowing_literals,
unused_variables,
clippy::print_literal,
clippy::useless_vec
)]
fn main() {
// Overly specified constants
let _: f32 = 1.012_345_7;
//~^ excessive_precision
let _: f64 = 1.012_345_678_901_234_6;
//~^ excessive_precision
const _: f32 = 1.012345678901234567890;
const _: f64 = 1.012345678901234567890;
static STATIC1: f32 = 1.012345678901234567890;
static STATIC2: f64 = 1.012345678901234567890;
static mut STATIC_MUT1: f32 = 1.012345678901234567890;
static mut STATIC_MUT2: f64 = 1.012345678901234567890;
}
trait ExcessivelyPreciseTrait {
// Overly specified constants
const GOOD1: f32 = 1.012345678901234567890;
const GOOD2: f64 = 1.012345678901234567890;
}
struct ExcessivelyPreciseStruct;
impl ExcessivelyPreciseStruct {
// Overly specified constants
const GOOD1: f32 = 1.012345678901234567890;
const GOOD2: f64 = 1.012345678901234567890;
}

View file

@ -0,0 +1,38 @@
#![warn(clippy::excessive_precision)]
#![allow(
dead_code,
overflowing_literals,
unused_variables,
clippy::print_literal,
clippy::useless_vec
)]
fn main() {
// Overly specified constants
let _: f32 = 1.012345678901234567890;
//~^ excessive_precision
let _: f64 = 1.012345678901234567890;
//~^ excessive_precision
const _: f32 = 1.012345678901234567890;
const _: f64 = 1.012345678901234567890;
static STATIC1: f32 = 1.012345678901234567890;
static STATIC2: f64 = 1.012345678901234567890;
static mut STATIC_MUT1: f32 = 1.012345678901234567890;
static mut STATIC_MUT2: f64 = 1.012345678901234567890;
}
trait ExcessivelyPreciseTrait {
// Overly specified constants
const GOOD1: f32 = 1.012345678901234567890;
const GOOD2: f64 = 1.012345678901234567890;
}
struct ExcessivelyPreciseStruct;
impl ExcessivelyPreciseStruct {
// Overly specified constants
const GOOD1: f32 = 1.012345678901234567890;
const GOOD2: f64 = 1.012345678901234567890;
}

View file

@ -0,0 +1,38 @@
error: float has excessive precision
--> tests/ui-toml/excessive_precision/excessive_precision.rs:12:18
|
LL | let _: f32 = 1.012345678901234567890;
| ^^^^^^^^^^^^^^^^^^^^^^^
|
note: consider making it a `const` item
--> tests/ui-toml/excessive_precision/excessive_precision.rs:12:5
|
LL | let _: f32 = 1.012345678901234567890;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::excessive-precision` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::excessive_precision)]`
help: consider changing the type or truncating it to
|
LL - let _: f32 = 1.012345678901234567890;
LL + let _: f32 = 1.012_345_7;
|
error: float has excessive precision
--> tests/ui-toml/excessive_precision/excessive_precision.rs:14:18
|
LL | let _: f64 = 1.012345678901234567890;
| ^^^^^^^^^^^^^^^^^^^^^^^
|
note: consider making it a `const` item
--> tests/ui-toml/excessive_precision/excessive_precision.rs:14:5
|
LL | let _: f64 = 1.012345678901234567890;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: consider changing the type or truncating it to
|
LL - let _: f64 = 1.012345678901234567890;
LL + let _: f64 = 1.012_345_678_901_234_6;
|
error: aborting due to 2 previous errors

View file

@ -35,6 +35,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
check-inconsistent-struct-field-initializers
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-macros
disallowed-methods
disallowed-names
@ -129,6 +130,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
check-inconsistent-struct-field-initializers
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-macros
disallowed-methods
disallowed-names
@ -223,6 +225,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
check-inconsistent-struct-field-initializers
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-macros
disallowed-methods
disallowed-names

View file

@ -82,9 +82,18 @@ fn main() {
assert!(r.is_err());
}
#[allow(dead_code)]
fn issue9450() {
let res: Result<i32, i32> = Ok(1);
res.unwrap_err();
//~^ assertions_on_result_states
}
fn issue9916(res: Result<u32, u32>) {
let a = 0;
match a {
0 => {},
1 => { res.unwrap(); },
//~^ assertions_on_result_states
_ => todo!(),
}
}

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