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

This commit is contained in:
Philipp Krones 2024-12-15 16:48:56 +01:00
commit 12edfb82e5
147 changed files with 3698 additions and 961 deletions

View file

@ -126,7 +126,7 @@ declare_clippy_lint! {
///
/// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md
///
#[clippy::version = "1.82.0"]
#[clippy::version = "1.84.0"]
pub ARBITRARY_SOURCE_ITEM_ORDERING,
restriction,
"arbitrary source item ordering"

View file

@ -9,7 +9,11 @@ use rustc_span::sym;
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
if let AttrKind::Normal(normal_attr) = &attr.kind {
if let AttrArgs::Eq { value: AttrArgsEq::Ast(_), .. } = &normal_attr.item.args {
if let AttrArgs::Eq {
value: AttrArgsEq::Ast(_),
..
} = &normal_attr.item.args
{
// `#[should_panic = ".."]` found, good
return;
}

View file

@ -0,0 +1,19 @@
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
pub fn check<'tcx>(cx: &LateContext<'tcx>, ty_into: Ty<'_>, cast_to_hir: &'tcx rustc_hir::Ty<'tcx>) {
if let rustc_hir::TyKind::Ptr(rustc_hir::MutTy { ty, .. }) = cast_to_hir.kind
&& matches!(ty.kind, rustc_hir::TyKind::Infer)
{
clippy_utils::diagnostics::span_lint_and_sugg(
cx,
super::AS_POINTER_UNDERSCORE,
cast_to_hir.span,
"using inferred pointer cast",
"use explicit type",
ty_into.to_string(),
Applicability::MachineApplicable,
);
}
}

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::snippet_with_context;
use clippy_utils::std_or_core;
use clippy_utils::{is_lint_allowed, msrvs, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
@ -13,15 +14,12 @@ pub(super) fn check<'tcx>(
expr: &'tcx Expr<'_>,
cast_expr: &'tcx Expr<'_>,
cast_to: &'tcx Ty<'_>,
) {
msrv: &Msrv,
) -> bool {
if matches!(cast_to.kind, TyKind::Ptr(_))
&& let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
&& let Some(std_or_core) = std_or_core(cx)
&& !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id)
{
let macro_name = match mutability {
Mutability::Not => "addr_of",
Mutability::Mut => "addr_of_mut",
};
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
// Fix #9884
@ -31,17 +29,36 @@ pub(super) fn check<'tcx>(
.get(base.hir_id)
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
}) {
return;
return false;
}
let suggestion = if msrv.meets(msrvs::RAW_REF_OP) {
let operator_kind = match mutability {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
format!("&raw {operator_kind} {snip}")
} else {
let Some(std_or_core) = std_or_core(cx) else {
return false;
};
let macro_name = match mutability {
Mutability::Not => "addr_of",
Mutability::Mut => "addr_of_mut",
};
format!("{std_or_core}::ptr::{macro_name}!({snip})")
};
span_lint_and_sugg(
cx,
BORROW_AS_PTR,
expr.span,
"borrow as raw pointer",
"try",
format!("{std_or_core}::ptr::{macro_name}!({snip})"),
suggestion,
Applicability::MachineApplicable,
);
return true;
}
false
}

View file

@ -1,3 +1,4 @@
mod as_pointer_underscore;
mod as_ptr_cast_mut;
mod as_underscore;
mod borrow_as_ptr;
@ -574,13 +575,13 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of `&expr as *const T` or
/// `&mut expr as *mut T`, and suggest using `ptr::addr_of` or
/// `ptr::addr_of_mut` instead.
/// `&mut expr as *mut T`, and suggest using `&raw const` or
/// `&raw mut` instead.
///
/// ### Why is this bad?
/// This would improve readability and avoid creating a reference
/// that points to an uninitialized value or unaligned place.
/// Read the `ptr::addr_of` docs for more information.
/// Read the `&raw` explanation in the Reference for more information.
///
/// ### Example
/// ```no_run
@ -593,10 +594,10 @@ declare_clippy_lint! {
/// Use instead:
/// ```no_run
/// let val = 1;
/// let p = std::ptr::addr_of!(val);
/// let p = &raw const val;
///
/// let mut val_mut = 1;
/// let p_mut = std::ptr::addr_of_mut!(val_mut);
/// let p_mut = &raw mut val_mut;
/// ```
#[clippy::version = "1.60.0"]
pub BORROW_AS_PTR,
@ -726,6 +727,33 @@ declare_clippy_lint! {
"using `as` to cast a reference to pointer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type.
///
/// ### Why restrict this?
/// The conversion might include a dangerous cast that might go undetected due to the type being inferred.
///
/// ### Example
/// ```no_run
/// fn as_usize<T>(t: &T) -> usize {
/// // BUG: `t` is already a reference, so we will here
/// // return a dangling pointer to a temporary value instead
/// &t as *const _ as usize
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn as_usize<T>(t: &T) -> usize {
/// t as *const T as usize
/// }
/// ```
#[clippy::version = "1.81.0"]
pub AS_POINTER_UNDERSCORE,
restriction,
"detects `as *mut _` and `as *const _` conversion"
}
pub struct Casts {
msrv: Msrv,
}
@ -763,6 +791,7 @@ impl_lint_pass!(Casts => [
CAST_NAN_TO_INT,
ZERO_PTR,
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -805,11 +834,15 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
}
as_underscore::check(cx, expr, cast_to_hir);
as_pointer_underscore::check(cx, cast_to, cast_to_hir);
if self.msrv.meets(msrvs::PTR_FROM_REF) {
let was_borrow_as_ptr_emitted = if self.msrv.meets(msrvs::BORROW_AS_PTR) {
borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, &self.msrv)
} else {
false
};
if self.msrv.meets(msrvs::PTR_FROM_REF) && !was_borrow_as_ptr_emitted {
ref_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
}
}

View file

@ -1,6 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::{SpanlessEq, if_sequence, is_else_clause, is_in_const_context};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -120,13 +122,19 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
return;
}
}
span_lint_and_help(
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
unreachable!();
};
let lhs = Sugg::hir(cx, lhs, "..").maybe_par();
let rhs = Sugg::hir(cx, rhs, "..").addr();
span_lint_and_sugg(
cx,
COMPARISON_CHAIN,
expr.span,
"`if` chain can be rewritten with `match`",
None,
"consider rewriting the `if` chain to use `cmp` and `match`",
"consider rewriting the `if` chain with `match`",
format!("match {lhs}.cmp({rhs}) {{...}}"),
Applicability::HasPlaceholders,
);
}
}

View file

@ -75,6 +75,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
crate::cargo::WILDCARD_DEPENDENCIES_INFO,
crate::casts::AS_POINTER_UNDERSCORE_INFO,
crate::casts::AS_PTR_CAST_MUT_INFO,
crate::casts::AS_UNDERSCORE_INFO,
crate::casts::BORROW_AS_PTR_INFO,
@ -139,6 +140,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::doc::DOC_LAZY_CONTINUATION_INFO,
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
crate::doc::DOC_MARKDOWN_INFO,
crate::doc::DOC_NESTED_REFDEFS_INFO,
crate::doc::EMPTY_DOCS_INFO,
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
@ -277,6 +279,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO,
crate::literal_representation::UNREADABLE_LITERAL_INFO,
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
crate::loops::EMPTY_LOOP_INFO,
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,

View file

@ -26,7 +26,8 @@ declare_clippy_lint! {
/// To ensure that every numeric type is chosen explicitly rather than implicitly.
///
/// ### Known problems
/// This lint can only be allowed at the function level or above.
/// This lint is implemented using a custom algorithm independent of rustc's inference,
/// which results in many false positives and false negatives.
///
/// ### Example
/// ```no_run
@ -36,8 +37,8 @@ declare_clippy_lint! {
///
/// Use instead:
/// ```no_run
/// let i = 10i32;
/// let f = 1.23f64;
/// let i = 10_i32;
/// let f = 1.23_f64;
/// ```
#[clippy::version = "1.52.0"]
pub DEFAULT_NUMERIC_FALLBACK,

View file

@ -1003,7 +1003,10 @@ fn report<'tcx>(
let needs_paren = match cx.tcx.parent_hir_node(data.first_expr.hir_id) {
Node::Expr(e) => match e.kind {
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => false,
ExprKind::Call(..) => expr.precedence() < ExprPrecedence::Unambiguous || matches!(expr.kind, ExprKind::Field(..)),
ExprKind::Call(..) => {
expr.precedence() < ExprPrecedence::Unambiguous
|| matches!(expr.kind, ExprKind::Field(..))
},
_ => expr.precedence() < e.precedence(),
},
_ => false,
@ -1016,11 +1019,7 @@ fn report<'tcx>(
})
);
let sugg = if !snip_is_macro
&& needs_paren
&& !has_enclosing_paren(&snip)
&& !is_in_tuple
{
let sugg = if !snip_is_macro && needs_paren && !has_enclosing_paren(&snip) && !is_in_tuple {
format!("({snip})")
} else {
snip.into()

View file

@ -453,7 +453,7 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
&& !has_non_exhaustive_attr(cx.tcx, *adt)
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
&& let typing_env = typing_env_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
&& let Some(local_def_id) = adt.did().as_local()
// If all of our fields implement `Eq`, we can implement `Eq` too
&& adt
@ -484,7 +484,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
}
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
fn typing_env_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
// Initial map from generic index to param def.
// Vec<(param_def, needs_eq)>
let mut params = tcx

View file

@ -43,7 +43,6 @@ declare_clippy_lint! {
/// ```no_run
/// use serde::Serialize;
///
/// // Example code where clippy issues a warning
/// println!("warns");
///
/// // The diagnostic will contain the message "no serializing"

View file

@ -35,7 +35,6 @@ declare_clippy_lint! {
/// ```
///
/// ```rust,ignore
/// // Example code where clippy issues a warning
/// let xs = vec![1, 2, 3, 4];
/// xs.leak(); // Vec::leak is disallowed in the config.
/// // The diagnostic contains the message "no leaking memory".
@ -47,7 +46,6 @@ declare_clippy_lint! {
///
/// Use instead:
/// ```rust,ignore
/// // Example code which does not raise clippy warning
/// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.
/// xs.push(123); // Vec::push is _not_ disallowed in the config.
/// ```

View file

@ -5,7 +5,7 @@ mod too_long_first_doc_paragraph;
use clippy_config::Conf;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable;
@ -18,6 +18,7 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{AnonConst, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -564,6 +565,32 @@ declare_clippy_lint! {
"check if files included in documentation are behind `cfg(doc)`"
}
declare_clippy_lint! {
/// ### What it does
/// Warns if a link reference definition appears at the start of a
/// list item or quote.
///
/// ### Why is this bad?
/// This is probably intended as an intra-doc link. If it is really
/// supposed to be a reference definition, it can be written outside
/// of the list item or quote.
///
/// ### Example
/// ```no_run
/// //! - [link]: description
/// ```
/// Use instead:
/// ```no_run
/// //! - [link][]: description (for intra-doc link)
/// //!
/// //! [link]: destination (for link reference definition)
/// ```
#[clippy::version = "1.84.0"]
pub DOC_NESTED_REFDEFS,
suspicious,
"link reference defined in list item or quote"
}
pub struct Documentation {
valid_idents: FxHashSet<String>,
check_private_items: bool,
@ -581,6 +608,7 @@ impl Documentation {
impl_lint_pass!(Documentation => [
DOC_LINK_WITH_QUOTES,
DOC_MARKDOWN,
DOC_NESTED_REFDEFS,
MISSING_SAFETY_DOC,
MISSING_ERRORS_DOC,
MISSING_PANICS_DOC,
@ -832,6 +860,31 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
Start(BlockQuote(_)) => {
blockquote_level += 1;
containers.push(Container::Blockquote);
if let Some((next_event, next_range)) = events.peek() {
let next_start = match next_event {
End(TagEnd::BlockQuote) => next_range.end,
_ => next_range.start,
};
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
{
span_lint_and_then(
cx,
DOC_NESTED_REFDEFS,
refdefspan,
"link reference defined in quote",
|diag| {
diag.span_suggestion_short(
refdefspan.shrink_to_hi(),
"for an intra-doc link, add `[]` between the label and the colon",
"[]",
Applicability::MaybeIncorrect,
);
diag.help("link definitions are not shown in rendered documentation");
}
);
}
}
},
End(TagEnd::BlockQuote) => {
blockquote_level -= 1;
@ -870,11 +923,42 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
in_heading = true;
}
if let Start(Item) = event {
if let Some((_next_event, next_range)) = events.peek() {
containers.push(Container::List(next_range.start - range.start));
let indent = if let Some((next_event, next_range)) = events.peek() {
let next_start = match next_event {
End(TagEnd::Item) => next_range.end,
_ => next_range.start,
};
if let Some(refdefrange) = looks_like_refdef(doc, range.start..next_start) &&
let Some(refdefspan) = fragments.span(cx, refdefrange.clone())
{
span_lint_and_then(
cx,
DOC_NESTED_REFDEFS,
refdefspan,
"link reference defined in list item",
|diag| {
diag.span_suggestion_short(
refdefspan.shrink_to_hi(),
"for an intra-doc link, add `[]` between the label and the colon",
"[]",
Applicability::MaybeIncorrect,
);
diag.help("link definitions are not shown in rendered documentation");
}
);
refdefrange.start - range.start
} else {
let mut start = next_range.start;
if start > 0 && doc.as_bytes().get(start - 1) == Some(&b'\\') {
// backslashes aren't in the event stream...
start -= 1;
}
start - range.start
}
} else {
containers.push(Container::List(0));
}
0
};
containers.push(Container::List(indent));
}
ticks_unbalanced = false;
paragraph_range = range;
@ -1046,3 +1130,25 @@ impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
self.cx.tcx.hir()
}
}
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
let offset = range.start;
let mut iterator = doc.as_bytes()[range].iter().copied().enumerate();
let mut start = None;
while let Some((i, byte)) = iterator.next() {
match byte {
b'\\' => {
iterator.next();
},
b'[' => {
start = Some(i + offset);
},
b']' if let Some(start) = start => {
return Some(start..i + offset + 1);
},
_ => {},
}
}
None
}

View file

@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
// match call to write_fmt
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind)
&& let ExprKind::Call(write_recv_path, []) = write_recv.kind
&& write_fun.ident.name.as_str() == "write_fmt"
&& write_fun.ident.name == sym::write_fmt
&& let Some(def_id) = path_def_id(cx, write_recv_path)
{
// match calls to std::io::stdout() / std::io::stderr ()

View file

@ -135,7 +135,7 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
.map(|(index, _)| *index)
.collect::<FxIndexSet<_>>();
let value_name = |index| format!("{}_{index}", slice.ident.name);
let value_name = |index| format!("{}_{}", slice.ident.name, index);
if let Some(max_index) = used_indices.iter().max() {
let opt_ref = if slice.needs_ref { "ref " } else { "" };
@ -150,6 +150,18 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
.collect::<Vec<_>>();
let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", "));
let mut suggestions = Vec::new();
// Add the binding pattern suggestion
if !slice.pattern_spans.is_empty() {
suggestions.extend(slice.pattern_spans.iter().map(|span| (*span, pat_sugg.clone())));
}
// Add the index replacement suggestions
if !slice.index_use.is_empty() {
suggestions.extend(slice.index_use.iter().map(|(index, span)| (*span, value_name(*index))));
}
span_lint_and_then(
cx,
INDEX_REFUTABLE_SLICE,
@ -157,28 +169,10 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
"this binding can be a slice pattern to avoid indexing",
|diag| {
diag.multipart_suggestion(
"try using a slice pattern here",
slice
.pattern_spans
.iter()
.map(|span| (*span, pat_sugg.clone()))
.collect(),
"replace the binding and indexed access with a slice pattern",
suggestions,
Applicability::MaybeIncorrect,
);
diag.multipart_suggestion(
"and replace the index expressions here",
slice
.index_use
.iter()
.map(|(index, span)| (*span, value_name(*index)))
.collect(),
Applicability::MaybeIncorrect,
);
// The lint message doesn't contain a warning about the removed index expression,
// since `filter_lintable_slices` will only return slices where all access indices
// are known at compile time. Therefore, they can be removed without side effects.
},
);
}

View file

@ -42,39 +42,50 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of indexing or slicing. Arrays are special cases, this lint
/// does report on arrays if we can tell that slicing operations are in bounds and does not
/// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint.
/// Checks for usage of indexing or slicing that may panic at runtime.
///
/// This lint does not report on indexing or slicing operations
/// that always panic, clippy's `out_of_bound_indexing` already
/// handles those cases.
///
/// ### Why restrict this?
/// To avoid implicit panics from indexing and slicing.
///
/// There are “checked” alternatives which do not panic, and can be used with `unwrap()` to make
/// an explicit panic when it is desired.
///
/// ### Limitations
/// This lint does not check for the usage of indexing or slicing on strings. These are covered
/// by the more specific `string_slice` lint.
///
/// ### Example
/// ```rust,no_run
/// // Vector
/// let x = vec![0; 5];
/// let x = vec![0, 1, 2, 3];
///
/// x[2];
/// x[100];
/// &x[2..100];
///
/// // Array
/// let y = [0, 1, 2, 3];
///
/// &y[10..100];
/// &y[10..];
/// let i = 10; // Could be a runtime value
/// let j = 20;
/// &y[i..j];
/// ```
///
/// Use instead:
/// ```no_run
/// # let x = vec![0; 5];
/// # let y = [0, 1, 2, 3];
/// # let x = vec![0, 1, 2, 3];
/// x.get(2);
/// x.get(100);
/// x.get(2..100);
///
/// y.get(10);
/// y.get(10..100);
/// # let y = [0, 1, 2, 3];
/// let i = 10;
/// let j = 20;
/// y.get(i..j);
/// ```
#[clippy::version = "pre 1.29.0"]
pub INDEXING_SLICING,

View file

@ -17,7 +17,8 @@
clippy::missing_docs_in_private_items,
clippy::must_use_candidate,
rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic
rustc::untranslatable_diagnostic,
clippy::literal_string_with_formatting_args
)]
#![warn(
trivial_casts,
@ -49,6 +50,7 @@ extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_parse;
extern crate rustc_parse_format;
extern crate rustc_resolve;
extern crate rustc_session;
extern crate rustc_span;
@ -196,6 +198,7 @@ mod let_with_type_underscore;
mod lifetimes;
mod lines_filter_map_ok;
mod literal_representation;
mod literal_string_with_formatting_args;
mod loops;
mod macro_metavars_in_unsafe;
mod macro_use;
@ -957,6 +960,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));

View file

@ -643,8 +643,7 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'
// An `impl` lifetime is elidable if it satisfies the following conditions:
// - It is used exactly once.
// - That single use is not in a bounded type or `GenericArgs` in a `WherePredicate`. (Note that
// `GenericArgs` are different from `GenericParam`s.)
// - That single use is not in a `WherePredicate`.
fn report_elidable_impl_lifetimes<'tcx>(
cx: &LateContext<'tcx>,
impl_: &'tcx Impl<'_>,
@ -658,12 +657,6 @@ fn report_elidable_impl_lifetimes<'tcx>(
lifetime,
in_where_predicate: false,
..
}
| Usage {
lifetime,
in_bounded_ty: false,
in_generics_arg: false,
..
},
] = usages.as_slice()
{

View file

@ -0,0 +1,159 @@
use rustc_ast::{LitKind, StrStyle};
use rustc_hir::{Expr, ExprKind};
use rustc_lexer::is_ident;
use rustc_lint::{LateContext, LateLintPass};
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_session::declare_lint_pass;
use rustc_span::{BytePos, Span};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::mir::enclosing_mir;
declare_clippy_lint! {
/// ### What it does
/// Checks if string literals have formatting arguments outside of macros
/// using them (like `format!`).
///
/// ### Why is this bad?
/// It will likely not generate the expected content.
///
/// ### Example
/// ```no_run
/// let x: Option<usize> = None;
/// let y = "hello";
/// x.expect("{y:?}");
/// ```
/// Use instead:
/// ```no_run
/// let x: Option<usize> = None;
/// let y = "hello";
/// x.expect(&format!("{y:?}"));
/// ```
#[clippy::version = "1.83.0"]
pub LITERAL_STRING_WITH_FORMATTING_ARGS,
suspicious,
"Checks if string literals have formatting arguments"
}
declare_lint_pass!(LiteralStringWithFormattingArg => [LITERAL_STRING_WITH_FORMATTING_ARGS]);
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<String>)]) {
if !spans.is_empty()
&& let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id)
{
let spans = spans
.iter()
.filter_map(|(span, name)| {
if let Some(name) = name {
// We need to check that the name is a local.
if !mir
.var_debug_info
.iter()
.any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name)
{
return None;
}
}
Some(*span)
})
.collect::<Vec<_>>();
match spans.len() {
0 => {},
1 => {
span_lint(
cx,
LITERAL_STRING_WITH_FORMATTING_ARGS,
spans,
"this looks like a formatting argument but it is not part of a formatting macro",
);
},
_ => {
span_lint(
cx,
LITERAL_STRING_WITH_FORMATTING_ARGS,
spans,
"these look like formatting arguments but are not part of a formatting macro",
);
},
}
}
}
impl LateLintPass<'_> for LiteralStringWithFormattingArg {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Lit(lit) = expr.kind {
let (add, symbol) = match lit.node {
LitKind::Str(symbol, style) => {
let add = match style {
StrStyle::Cooked => 1,
StrStyle::Raw(nb) => nb as usize + 2,
};
(add, symbol)
},
_ => return,
};
let fmt_str = symbol.as_str();
let lo = expr.span.lo();
let mut current = fmt_str;
let mut diff_len = 0;
let mut parser = Parser::new(current, None, None, false, ParseMode::Format);
let mut spans = Vec::new();
while let Some(piece) = parser.next() {
if let Some(error) = parser.errors.last() {
// We simply ignore the errors and move after them.
if error.span.end >= current.len() {
break;
}
current = &current[error.span.end + 1..];
diff_len = fmt_str.len() - current.len();
parser = Parser::new(current, None, None, false, ParseMode::Format);
} else if let Piece::NextArgument(arg) = piece {
let mut pos = arg.position_span;
pos.start += diff_len;
pos.end += diff_len;
let start = fmt_str[..pos.start].rfind('{').unwrap_or(pos.start);
// If this is a unicode character escape, we don't want to lint.
if start > 1 && fmt_str[..start].ends_with("\\u") {
continue;
}
if fmt_str[start + 1..].trim_start().starts_with('}') {
// We ignore `{}`.
continue;
}
let end = fmt_str[start + 1..]
.find('}')
.map_or(pos.end, |found| start + 1 + found)
+ 1;
let ident_start = start + 1;
let colon_pos = fmt_str[ident_start..end].find(':');
let ident_end = colon_pos.unwrap_or(end - 1);
let mut name = None;
if ident_start < ident_end
&& let arg = &fmt_str[ident_start..ident_end]
&& !arg.is_empty()
&& is_ident(arg)
{
name = Some(arg.to_string());
} else if colon_pos.is_none() {
// Not a `{:?}`.
continue;
}
spans.push((
expr.span
.with_hi(lo + BytePos((start + add).try_into().unwrap()))
.with_lo(lo + BytePos((end + add).try_into().unwrap())),
name,
));
}
}
emit_lint(cx, expr, &spans);
}
}
}

View file

@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
if let Some(vis_snip) = vis_span.get_source_text(cx)
&& let Some(header_snip) = header_span.get_source_text(cx)
&& let Some(ret_pos) = position_before_rarrow(&header_snip)
&& let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
&& let Some((_, ret_snip)) = suggested_ret(cx, output)
{
let header_snip = if vis_snip.is_empty() {
format!("async {}", &header_snip[..ret_pos])
@ -82,19 +82,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable,
);
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)).to_string();
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip,
diag.multipart_suggestion(
"make the function `async` and return the output of the future directly",
vec![
(header_span, format!("{header_snip}{ret_snip}")),
(block.span, body_snip),
],
Applicability::MachineApplicable,
);
}

View file

@ -5,9 +5,9 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{is_from_proc_macro, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::TyCtxt;
@ -129,9 +129,7 @@ fn is_not_const(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
| DefKind::Ctor(..)
| DefKind::AssocConst => false,
DefKind::Fn
| DefKind::AssocFn
| DefKind::Closure => tcx.constness(def_id) == Constness::NotConst,
DefKind::Fn | DefKind::AssocFn | DefKind::Closure => tcx.constness(def_id) == Constness::NotConst,
}
}

View file

@ -43,21 +43,21 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
&& bin_op.node == BinOpKind::Eq
{
// a.count_ones() == 1
if let ExprKind::MethodCall(method_name, reciever, [], _) = left.kind
if let ExprKind::MethodCall(method_name, receiver, [], _) = left.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
&& check_lit(right, 1)
{
build_sugg(cx, expr, reciever, &mut applicability);
build_sugg(cx, expr, receiver, &mut applicability);
}
// 1 == a.count_ones()
if let ExprKind::MethodCall(method_name, reciever, [], _) = right.kind
if let ExprKind::MethodCall(method_name, receiver, [], _) = right.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
&& check_lit(left, 1)
{
build_sugg(cx, expr, reciever, &mut applicability);
build_sugg(cx, expr, receiver, &mut applicability);
}
// a & (a - 1) == 0
@ -115,8 +115,8 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
}
}
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, reciever: &Expr<'_>, applicability: &mut Applicability) {
let snippet = snippet_with_applicability(cx, reciever.span, "..", applicability);
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, applicability: &mut Applicability) {
let snippet = snippet_with_applicability(cx, receiver.span, "..", applicability);
span_lint_and_sugg(
cx,

View file

@ -27,7 +27,9 @@ mod wild_in_or_pats;
use clippy_config::Conf;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::walk_span_to_context;
use clippy_utils::{higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg};
use clippy_utils::{
higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments,
};
use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -1059,7 +1061,28 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
redundant_pattern_match::check_match(cx, expr, ex, arms);
single_match::check(cx, ex, arms, expr);
let source_map = cx.tcx.sess.source_map();
let mut match_comments = span_extract_comments(source_map, expr.span);
// We remove comments from inside arms block.
if !match_comments.is_empty() {
for arm in arms {
for comment in span_extract_comments(source_map, arm.body.span) {
if let Some(index) = match_comments
.iter()
.enumerate()
.find(|(_, cm)| **cm == comment)
.map(|(index, _)| index)
{
match_comments.remove(index);
}
}
}
}
// If there are still comments, it means they are outside of the arms, therefore
// we should not lint.
if match_comments.is_empty() {
single_match::check(cx, ex, arms, expr);
}
match_bool::check(cx, ex, arms, expr);
overlapping_arms::check(cx, ex, arms);
match_wild_enum::check(cx, ex, arms);

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{
eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
peel_blocks_with_stmt,
};
use rustc_errors::Applicability;
@ -90,7 +90,9 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
}
// Recursively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else)
&& SpanlessEq::new(cx).eq_expr(nested_if_let.let_expr, if_let.let_expr)
{
return check_if_let_inner(cx, nested_if_let);
}

View file

@ -129,7 +129,7 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
PatKind::Lit(Expr {
kind: ExprKind::Lit(lit),
..
}) if lit.node.is_str() => pat_ref_count + 1,
}) if lit.node.is_str() || lit.node.is_bytestr() => pat_ref_count + 1,
_ => pat_ref_count,
};
// References are only implicitly added to the pattern, so no overflow here.

View file

@ -19,7 +19,7 @@ fn extract_count_with_applicability(
) -> Option<String> {
let start = range.start?;
let end = range.end?;
// TODO: This doens't handle if either the start or end are negative literals, or if the start is
// TODO: This doesn't handle if either the start or end are negative literals, or if the start is
// not a literal. In the first case, we need to be careful about how we handle computing the
// count to avoid overflows. In the second, we may need to add parenthesis to make the
// suggestion correct.

View file

@ -1864,7 +1864,6 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// let opt: Option<u32> = None;
///
/// opt.unwrap_or_else(|| 42);
@ -3839,13 +3838,11 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// vec![Some(1)].into_iter().filter(Option::is_some);
///
/// ```
/// Use instead:
/// ```no_run
/// // example code which does not raise clippy warning
/// vec![Some(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.77.0"]
@ -3865,13 +3862,11 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// vec![Ok::<i32, String>(1)].into_iter().filter(Result::is_ok);
///
/// ```
/// Use instead:
/// ```no_run
/// // example code which does not raise clippy warning
/// vec![Ok::<i32, String>(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.77.0"]
@ -3969,7 +3964,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
///
/// In the aformentioned cases it is not necessary to call `min()` or `max()`
/// In the aforementioned cases it is not necessary to call `min()` or `max()`
/// to compare values, it may even cause confusion.
///
/// ### Example
@ -4982,6 +4977,10 @@ impl Methods {
}
map_identity::check(cx, expr, recv, m_arg, name, span);
manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv);
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
},
("map_break" | "map_continue", [m_arg]) => {
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
},
("map_or", [def, map]) => {
option_map_or_none::check(cx, expr, recv, def, map);

View file

@ -1,9 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::match_def_path;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_span::sym;
@ -11,20 +8,17 @@ use super::NEEDLESS_OPTION_TAKE;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
// Checks if expression type is equal to sym::Option and if the expr is not a syntactic place
if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_TAKE,
expr.span,
"called `Option::take()` on a temporary value",
"try",
format!(
"{}",
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
),
applicability,
);
if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) {
if let Some(function_name) = source_of_temporary_value(recv) {
span_lint_and_note(
cx,
NEEDLESS_OPTION_TAKE,
expr.span,
"called `Option::take()` on a temporary value",
None,
format!("`{function_name}` creates a temporary value, so calling take() has no effect"),
);
}
}
}
@ -33,9 +27,24 @@ fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
is_type_diagnostic_item(cx, expr_type, sym::Option)
}
fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]);
/// Returns the string of the function call that creates the temporary.
/// When this function is called, we are reasonably certain that the `ExprKind` is either
/// `Call` or `MethodCall` because we already checked that the expression is not
/// `is_syntactic_place_expr()`.
fn source_of_temporary_value<'a>(expr: &'a Expr<'_>) -> Option<&'a str> {
match expr.peel_borrows().kind {
ExprKind::Call(function, _) => {
if let ExprKind::Path(QPath::Resolved(_, func_path)) = function.kind {
if !func_path.segments.is_empty() {
return Some(func_path.segments[0].ident.name.as_str());
}
}
if let ExprKind::Path(QPath::TypeRelative(_, func_path_segment)) = function.kind {
return Some(func_path_segment.ident.name.as_str());
}
None
},
ExprKind::MethodCall(path_segment, ..) => Some(path_segment.ident.name.as_str()),
_ => None,
}
false
}

View file

@ -129,7 +129,7 @@ fn check_manual_split_once_indirect(
let ctxt = expr.span.ctxt();
let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
if let (_, Node::LetStmt(local)) = parents.next()?
&& let PatKind::Binding(BindingMode::MUT, iter_binding_id, iter_ident, None) = local.pat.kind
&& let PatKind::Binding(BindingMode::MUT, iter_binding_id, _, None) = local.pat.kind
&& let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
&& let (_, Node::Block(enclosing_block)) = parents.next()?
&& let mut stmts = enclosing_block
@ -162,16 +162,20 @@ fn check_manual_split_once_indirect(
UnwrapKind::Unwrap => ".unwrap()",
UnwrapKind::QuestionMark => "?",
};
diag.span_suggestion_verbose(
local.span,
format!("try `{r}split_once`"),
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
// Add a multipart suggestion
diag.multipart_suggestion(
format!("replace with `{r}split_once`"),
vec![
(
local.span,
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
),
(first.span, String::new()), // Remove the first usage
(second.span, String::new()), // Remove the second usage
],
app,
);
let remove_msg = format!("remove the `{iter_ident}` usages");
diag.span_suggestion(first.span, remove_msg.clone(), "", app);
diag.span_suggestion(second.span, remove_msg, "", app);
});
}

View file

@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty;
use rustc_middle::ty::GenericArgKind;
use rustc_span::sym;
use rustc_span::symbol::Ident;
use std::iter;

View file

@ -27,14 +27,12 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// thread_local! {
/// static BUF: String = String::new();
/// }
/// ```
/// Use instead:
/// ```no_run
/// // example code which does not raise clippy warning
/// thread_local! {
/// static BUF: String = const { String::new() };
/// }

View file

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

View file

@ -8,7 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{
BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind, Stmt, StmtKind,
UnsafeSource, StructTailExpr, is_range_literal,
StructTailExpr, UnsafeSource, is_range_literal,
};
use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext};

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
use rustc_ast::ast::{BinOpKind, Expr, ExprKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -12,6 +13,7 @@ declare_clippy_lint! {
/// and suggests to add parentheses. Currently it catches the following:
/// * mixed usage of arithmetic and bit shifting/combining operators without
/// parentheses
/// * mixed usage of bitmasking and bit shifting operators without parentheses
///
/// ### Why is this bad?
/// Not everyone knows the precedence of those operators by
@ -20,6 +22,7 @@ declare_clippy_lint! {
///
/// ### Example
/// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
/// * `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2
#[clippy::version = "pre 1.29.0"]
pub PRECEDENCE,
complexity,
@ -51,8 +54,13 @@ impl EarlyLintPass for Precedence {
return;
}
let mut applicability = Applicability::MachineApplicable;
match (is_arith_expr(left), is_arith_expr(right)) {
(true, true) => {
match (op, get_bin_opt(left), get_bin_opt(right)) {
(
BitAnd | BitOr | BitXor,
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
)
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), Some(Add | Div | Mul | Rem | Sub)) => {
let sugg = format!(
"({}) {} ({})",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
@ -61,7 +69,8 @@ impl EarlyLintPass for Precedence {
);
span_sugg(expr, sugg, applicability);
},
(true, false) => {
(BitAnd | BitOr | BitXor, Some(Shl | Shr | Add | Div | Mul | Rem | Sub), _)
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), _) => {
let sugg = format!(
"({}) {} {}",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
@ -70,7 +79,8 @@ impl EarlyLintPass for Precedence {
);
span_sugg(expr, sugg, applicability);
},
(false, true) => {
(BitAnd | BitOr | BitXor, _, Some(Shl | Shr | Add | Div | Mul | Rem | Sub))
| (Shl | Shr, _, Some(Add | Div | Mul | Rem | Sub)) => {
let sugg = format!(
"{} {} ({})",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
@ -79,27 +89,20 @@ impl EarlyLintPass for Precedence {
);
span_sugg(expr, sugg, applicability);
},
(false, false) => (),
_ => (),
}
}
}
}
fn is_arith_expr(expr: &Expr) -> bool {
fn get_bin_opt(expr: &Expr) -> Option<BinOpKind> {
match expr.kind {
ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
_ => false,
ExprKind::Binary(Spanned { node: op, .. }, _, _) => Some(op),
_ => None,
}
}
#[must_use]
fn is_bit_op(op: BinOpKind) -> bool {
use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
matches!(op, BitXor | BitAnd | BitOr | Shl | Shr)
}
#[must_use]
fn is_arith_op(op: BinOpKind) -> bool {
use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub};
matches!(op, Add | Sub | Mul | Div | Rem)
}

View file

@ -59,7 +59,7 @@ pub struct QuestionMark {
/// As for why we need this in the first place: <https://github.com/rust-lang/rust-clippy/issues/8628>
try_block_depth_stack: Vec<u32>,
/// Keeps track of the number of inferred return type closures we are inside, to avoid problems
/// with the `Err(x.into())` expansion being ambiguious.
/// with the `Err(x.into())` expansion being ambiguous.
inferred_ret_closure_stack: u16,
}

View file

@ -85,7 +85,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr));
let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
let parent_expr = get_parent_expr(cx, expr);
let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix);
let needs_parens_for_prefix =
parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix);
if expr_ty == indexed_ty {
if expr_ref_count > indexed_ref_count {

View file

@ -1,6 +1,9 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local_id;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
use clippy_utils::visitors::{Descend, Visitable, for_each_expr};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::Res;
use rustc_hir::def_id::LocalDefId;
@ -175,9 +178,31 @@ fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second
false
}
/// Checks if the given local is used, except for in child expression of `except`.
///
/// This is a version of [`is_local_used`](clippy_utils::visitors::is_local_used), used to
/// implement the fix for <https://github.com/rust-lang/rust-clippy/issues/10780>.
pub fn is_local_used_except<'tcx>(
cx: &LateContext<'tcx>,
visitable: impl Visitable<'tcx>,
id: HirId,
except: Option<HirId>,
) -> bool {
for_each_expr(cx, visitable, |e| {
if except.is_some_and(|it| it == e.hir_id) {
ControlFlow::Continue(Descend::No)
} else if path_to_local_id(e, id) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::Yes)
}
})
.is_some()
}
fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span) {
let (lint, msg) = match find_init(cx, pat.hir_id) {
Some(expr) if is_self_shadow(cx, pat, expr, shadowed) => {
Some((expr, _)) if is_self_shadow(cx, pat, expr, shadowed) => {
let msg = format!(
"`{}` is shadowed by itself in `{}`",
snippet(cx, pat.span, "_"),
@ -185,7 +210,7 @@ fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span)
);
(SHADOW_SAME, msg)
},
Some(expr) if is_local_used(cx, expr, shadowed) => {
Some((expr, except)) if is_local_used_except(cx, expr, shadowed, except) => {
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
(SHADOW_REUSE, msg)
},
@ -232,15 +257,32 @@ fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_
/// Finds the "init" expression for a pattern: `let <pat> = <init>;` (or `if let`) or
/// `match <init> { .., <pat> => .., .. }`
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
for (_, node) in cx.tcx.hir().parent_iter(hir_id) {
///
/// For closure arguments passed to a method call, returns the method call, and the `HirId` of the
/// closure (which will later be skipped). This is for <https://github.com/rust-lang/rust-clippy/issues/10780>
fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<(&'tcx Expr<'tcx>, Option<HirId>)> {
for (hir_id, node) in cx.tcx.hir().parent_iter(hir_id) {
let init = match node {
Node::Arm(_) | Node::Pat(_) => continue,
Node::Arm(_) | Node::Pat(_) | Node::PatField(_) | Node::Param(_) => continue,
Node::Expr(expr) => match expr.kind {
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some(e),
ExprKind::Match(e, _, _) | ExprKind::Let(&LetExpr { init: e, .. }) => Some((e, None)),
// If we're a closure argument, then a parent call is also an associated item.
ExprKind::Closure(_) => {
if let Some((_, node)) = cx.tcx.hir().parent_iter(hir_id).next() {
match node {
Node::Expr(expr) => match expr.kind {
ExprKind::MethodCall(_, _, _, _) | ExprKind::Call(_, _) => Some((expr, Some(hir_id))),
_ => None,
},
_ => None,
}
} else {
None
}
},
_ => None,
},
Node::LetStmt(local) => local.init,
Node::LetStmt(local) => local.init.map(|init| (init, None)),
_ => None,
};
return init;

View file

@ -99,16 +99,10 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> {
snippet(cx, apa.last_bind_ident.span, ".."),
)
};
diag.span_suggestion_verbose(
apa.first_stmt_span,
diag.multipart_suggestion_verbose(
"merge the temporary construction with its single usage",
stmt,
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
apa.last_stmt_span,
"remove separated single usage",
"",
vec![(apa.first_stmt_span, stmt), (apa.last_stmt_span, String::new())],
Applicability::MaybeIncorrect,
);
},

View file

@ -86,7 +86,8 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
return;
};
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind else {
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], StructTailExpr::None) = inner_expr.kind
else {
return;
};

View file

@ -370,12 +370,10 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// let _ = "str".to_string();
/// ```
/// Use instead:
/// ```no_run
/// // example code which does not raise clippy warning
/// let _ = "str".to_owned();
/// ```
#[clippy::version = "pre 1.29.0"]
@ -424,13 +422,11 @@ declare_clippy_lint! {
///
/// ### Example
/// ```no_run
/// // example code where clippy issues a warning
/// let msg = String::from("Hello World");
/// let _ = msg.to_string();
/// ```
/// Use instead:
/// ```no_run
/// // example code which does not raise clippy warning
/// let msg = String::from("Hello World");
/// let _ = msg.clone();
/// ```

View file

@ -25,13 +25,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
return;
}
let (reciever, args) = match expr.kind {
let (receiver, args) = match expr.kind {
ExprKind::Call(_, args) => (None, args),
ExprKind::MethodCall(_, receiver, args, _) => (Some(receiver), args),
_ => return,
};
let args_to_recover = reciever
let args_to_recover = receiver
.into_iter()
.chain(args)
.filter(|arg| {

View file

@ -17,7 +17,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
///
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unneccessary allocations or confusion.
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unnecessary allocations or confusion.
/// This is also most likely what you meant to write.
///
/// ### Example

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_parent_expr, path_to_local};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, UnOp, StructTailExpr};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, StructTailExpr, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -63,7 +63,9 @@ impl LateLintPass<'_> for UnnecessaryStruct {
// all fields match, no base given
path.span
},
(Some(path), StructTailExpr::Base(base)) if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => {
(Some(path), StructTailExpr::Base(base))
if base_is_suitable(cx, expr, base) && path_matches_base(path, base) =>
{
// all fields match, has base: ensure that the path of the base matches
base.span
},

View file

@ -1,8 +1,10 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::sugg::{DiagExt as _, Sugg};
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{get_parent_expr, is_trait_method, is_ty_alias, path_to_local};
use clippy_utils::{
get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local,
};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
@ -10,7 +12,7 @@ use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::Obligation;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, sym};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
@ -382,3 +384,50 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
}
}
}
/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a
/// higher-order mapping function.
pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
if has_eligible_receiver(cx, recv, expr)
&& (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From))
&& let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind()
&& let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice()
&& same_type_and_consts(from_ty, to_ty)
{
span_lint_and_then(
cx,
USELESS_CONVERSION,
expr.span.with_lo(recv.span.hi()),
format!("useless conversion to the same type: `{from_ty}`"),
|diag| {
diag.suggest_remove_item(
cx,
expr.span.with_lo(recv.span.hi()),
"consider removing",
Applicability::MachineApplicable,
);
},
);
}
}
fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool {
let recv_ty = cx.typeck_results().expr_ty(recv);
if is_inherent_method_call(cx, expr)
&& let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
{
if let Some(diag_name) = cx.tcx.get_diagnostic_name(recv_ty_defid)
&& matches!(diag_name, sym::Option | sym::Result)
{
return true;
}
if cx.tcx.is_diagnostic_item(sym::ControlFlow, recv_ty_defid) {
return true;
}
}
if is_trait_method(cx, expr, sym::Iterator) {
return true;
}
false
}

View file

@ -3,8 +3,8 @@ use rustc_ast::LitIntType;
use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind,
ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind, StructTailExpr,
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind, ExprKind,
FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, StructTailExpr, TyKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
@ -625,7 +625,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
},
ExprKind::UnsafeBinderCast(..) => {
unimplemented!("unsafe binders are not implemented yet");
}
},
}
}

View file

@ -92,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Call(func, [arg]) = &expr.kind
&& let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind()
&& match_def_path(cx, *def_id, &paths::SYMBOL_INTERN)
&& cx.tcx.is_diagnostic_item(sym::SymbolIntern, *def_id)
&& let Some(Constant::Str(arg)) = ConstEvalCtxt::new(cx).eval_simple(arg)
&& let value = Symbol::intern(&arg).as_u32()
&& let Some(&def_id) = self.symbol_map.get(&value)

View file

@ -1,29 +1,29 @@
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::match_type;
use clippy_utils::{match_function_call, paths};
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::{Span, sym};
declare_clippy_lint! {
/// ### What it does
///
/// Detects symbol comparision using `Symbol::intern`.
/// Detects symbol comparison using `Symbol::intern`.
///
/// ### Why is this bad?
///
/// Comparision via `Symbol::as_str()` is faster if the interned symbols are not reused.
/// Comparison via `Symbol::as_str()` is faster if the interned symbols are not reused.
///
/// ### Example
///
/// None, see suggestion.
pub SLOW_SYMBOL_COMPARISONS,
internal,
"detects slow comparisions of symbol"
"detects slow comparisons of symbol"
}
declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]);
@ -34,7 +34,12 @@ fn check_slow_comparison<'tcx>(
op2: &'tcx Expr<'tcx>,
) -> Option<(Span, String)> {
if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL)
&& let Some([symbol_name_expr]) = match_function_call(cx, op2, &paths::SYMBOL_INTERN)
&& let ExprKind::Call(fun, args) = op2.kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& cx
.tcx
.is_diagnostic_item(sym::SymbolIntern, cx.qpath_res(qpath, fun.hir_id).opt_def_id()?)
&& let [symbol_name_expr] = args
&& let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr)
{
Some((op1.span, symbol_name))

View file

@ -2,13 +2,14 @@ use ControlFlow::{Break, Continue};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, get_enclosing_block, match_any_def_paths, match_def_path, path_to_local_id, paths};
use rustc_ast::Mutability;
use rustc_ast::visit::visit_opt;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_local};
use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use rustc_span::{Span, sym};
use std::ops::ControlFlow;
declare_clippy_lint! {
@ -22,6 +23,17 @@ declare_clippy_lint! {
/// which can eventually lead to resource exhaustion, so it's recommended to call `wait()` in long-running applications.
/// Such processes are called "zombie processes".
///
/// To reduce the rate of false positives, if the spawned process is assigned to a binding, the lint actually works the other way around; it
/// conservatively checks that all uses of a variable definitely don't call `wait()` and only then emits a warning.
/// For that reason, a seemingly unrelated use can get called out as calling `wait()` in help messages.
///
/// ### Control flow
/// If a `wait()` call exists in an if/then block but not in the else block (or there is no else block),
/// then this still gets linted as not calling `wait()` in all code paths.
/// Likewise, when early-returning from the function, `wait()` calls that appear after the return expression
/// are also not accepted.
/// In other words, the `wait()` call must be unconditionally reachable after the spawn expression.
///
/// ### Example
/// ```rust
/// use std::process::Command;
@ -53,26 +65,47 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
if let PatKind::Binding(_, local_id, ..) = local.pat.kind
&& let Some(enclosing_block) = get_enclosing_block(cx, expr.hir_id) =>
{
let mut vis = WaitFinder::WalkUpTo(cx, local_id);
let mut vis = WaitFinder {
cx,
local_id,
state: VisitorState::WalkUpToLocal,
early_return: None,
missing_wait_branch: None,
};
// If it does have a `wait()` call, we're done. Don't lint.
if let Break(BreakReason::WaitFound) = walk_block(&mut vis, enclosing_block) {
return;
}
let res = (
walk_block(&mut vis, enclosing_block),
vis.missing_wait_branch,
vis.early_return,
);
let cause = match res {
(Break(MaybeWait(wait_span)), _, Some(return_span)) => {
Cause::EarlyReturn { wait_span, return_span }
},
(Break(MaybeWait(_)), _, None) => return,
(Continue(()), None, _) => Cause::NeverWait,
(Continue(()), Some(MissingWaitBranch::MissingElse { if_span, wait_span }), _) => {
Cause::MissingElse { wait_span, if_span }
},
(Continue(()), Some(MissingWaitBranch::MissingWaitInBranch { branch_span, wait_span }), _) => {
Cause::MissingWaitInBranch { wait_span, branch_span }
},
};
// Don't emit a suggestion since the binding is used later
check(cx, expr, false);
check(cx, expr, cause, false);
},
Node::LetStmt(&LetStmt { pat, .. }) if let PatKind::Wild = pat.kind => {
// `let _ = child;`, also dropped immediately without `wait()`ing
check(cx, expr, true);
check(cx, expr, Cause::NeverWait, true);
},
Node::Stmt(&Stmt {
kind: StmtKind::Semi(_),
..
}) => {
// Immediately dropped. E.g. `std::process::Command::new("echo").spawn().unwrap();`
check(cx, expr, true);
check(cx, expr, Cause::NeverWait, true);
},
_ => {},
}
@ -80,21 +113,10 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
}
}
enum BreakReason {
WaitFound,
EarlyReturn,
}
struct MaybeWait(Span);
/// A visitor responsible for finding a `wait()` call on a local variable.
///
/// Conditional `wait()` calls are assumed to not call wait:
/// ```ignore
/// let mut c = Command::new("").spawn().unwrap();
/// if true {
/// c.wait();
/// }
/// ```
///
/// Note that this visitor does NOT explicitly look for `wait()` calls directly, but rather does the
/// inverse -- checking if all uses of the local are either:
/// - a field access (`child.{stderr,stdin,stdout}`)
@ -104,43 +126,50 @@ enum BreakReason {
///
/// None of these are sufficient to prevent zombie processes.
/// Doing it like this means more FNs, but FNs are better than FPs.
///
/// `return` expressions, conditional or not, short-circuit the visitor because
/// if a `wait()` call hadn't been found at that point, it might never reach one at a later point:
/// ```ignore
/// let mut c = Command::new("").spawn().unwrap();
/// if true {
/// return; // Break(BreakReason::EarlyReturn)
/// }
/// c.wait(); // this might not be reachable
/// ```
enum WaitFinder<'a, 'tcx> {
WalkUpTo(&'a LateContext<'tcx>, HirId),
Found(&'a LateContext<'tcx>, HirId),
struct WaitFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
local_id: HirId,
state: VisitorState,
early_return: Option<Span>,
// When joining two if branches where one of them doesn't call `wait()`, stores its span for more targetted help
// messages
missing_wait_branch: Option<MissingWaitBranch>,
}
#[derive(PartialEq)]
enum VisitorState {
WalkUpToLocal,
LocalFound,
}
#[derive(Copy, Clone)]
enum MissingWaitBranch {
MissingElse { if_span: Span, wait_span: Span },
MissingWaitInBranch { branch_span: Span, wait_span: Span },
}
impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
type Result = ControlFlow<BreakReason>;
type Result = ControlFlow<MaybeWait>;
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result {
if let Self::WalkUpTo(cx, local_id) = *self
if self.state == VisitorState::WalkUpToLocal
&& let PatKind::Binding(_, pat_id, ..) = l.pat.kind
&& local_id == pat_id
&& self.local_id == pat_id
{
*self = Self::Found(cx, local_id);
self.state = VisitorState::LocalFound;
}
walk_local(self, l)
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
let Self::Found(cx, local_id) = *self else {
if self.state != VisitorState::LocalFound {
return walk_expr(self, ex);
};
}
if path_to_local_id(ex, local_id) {
match cx.tcx.parent_hir_node(ex.hir_id) {
if path_to_local_id(ex, self.local_id) {
match self.cx.tcx.parent_hir_node(ex.hir_id) {
Node::Stmt(Stmt {
kind: StmtKind::Semi(_),
..
@ -148,29 +177,33 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
Node::Expr(expr) if let ExprKind::Field(..) = expr.kind => {},
Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {},
Node::Expr(expr)
if let Some(fn_did) = fn_def_id(cx, expr)
&& match_any_def_paths(cx, fn_did, &[&paths::CHILD_ID, &paths::CHILD_KILL]).is_some() => {},
if let Some(fn_did) = fn_def_id(self.cx, expr)
&& match_any_def_paths(self.cx, fn_did, &[&paths::CHILD_ID, &paths::CHILD_KILL]).is_some() => {
},
// Conservatively assume that all other kinds of nodes call `.wait()` somehow.
_ => return Break(BreakReason::WaitFound),
_ => return Break(MaybeWait(ex.span)),
}
} else {
match ex.kind {
ExprKind::Ret(..) => return Break(BreakReason::EarlyReturn),
ExprKind::Ret(e) => {
visit_opt!(self, visit_expr, e);
if self.early_return.is_none() {
self.early_return = Some(ex.span);
}
return Continue(());
},
ExprKind::If(cond, then, None) => {
walk_expr(self, cond)?;
// A `wait()` call in an if expression with no else is not enough:
//
// let c = spawn();
// if true {
// c.wait();
// }
//
// This might not call wait(). However, early returns are propagated,
// because they might lead to a later wait() not being called.
if let Break(BreakReason::EarlyReturn) = walk_expr(self, then) {
return Break(BreakReason::EarlyReturn);
if let Break(MaybeWait(wait_span)) = walk_expr(self, then)
&& self.missing_wait_branch.is_none()
{
self.missing_wait_branch = Some(MissingWaitBranch::MissingElse {
if_span: ex.span,
wait_span,
});
}
return Continue(());
@ -179,22 +212,31 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
ExprKind::If(cond, then, Some(else_)) => {
walk_expr(self, cond)?;
#[expect(clippy::unnested_or_patterns)]
match (walk_expr(self, then), walk_expr(self, else_)) {
(Continue(()), Continue(()))
(Continue(()), Continue(())) => {},
// `wait()` in one branch but nothing in the other does not count
| (Continue(()), Break(BreakReason::WaitFound))
| (Break(BreakReason::WaitFound), Continue(())) => {},
// `wait()` in both branches is ok
(Break(BreakReason::WaitFound), Break(BreakReason::WaitFound)) => {
return Break(BreakReason::WaitFound);
(Continue(()), Break(MaybeWait(wait_span))) => {
if self.missing_wait_branch.is_none() {
self.missing_wait_branch = Some(MissingWaitBranch::MissingWaitInBranch {
branch_span: ex.span.shrink_to_lo().to(then.span),
wait_span,
});
}
},
(Break(MaybeWait(wait_span)), Continue(())) => {
if self.missing_wait_branch.is_none() {
self.missing_wait_branch = Some(MissingWaitBranch::MissingWaitInBranch {
branch_span: then.span.shrink_to_hi().to(else_.span),
wait_span,
});
}
},
// Propagate early returns in either branch
(Break(BreakReason::EarlyReturn), _) | (_, Break(BreakReason::EarlyReturn)) => {
return Break(BreakReason::EarlyReturn);
// `wait()` in both branches is ok
(Break(MaybeWait(wait_span)), Break(MaybeWait(_))) => {
self.missing_wait_branch = None;
return Break(MaybeWait(wait_span));
},
}
@ -208,8 +250,40 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
}
fn nested_visit_map(&mut self) -> Self::Map {
let (Self::Found(cx, _) | Self::WalkUpTo(cx, _)) = self;
cx.tcx.hir()
self.cx.tcx.hir()
}
}
#[derive(Copy, Clone)]
enum Cause {
/// No call to `wait()` at all
NeverWait,
/// `wait()` call exists, but not all code paths definitely lead to one due to
/// an early return
EarlyReturn { wait_span: Span, return_span: Span },
/// `wait()` call exists in some if branches but not this one
MissingWaitInBranch { wait_span: Span, branch_span: Span },
/// `wait()` call exists in an if/then branch but it is missing an else block
MissingElse { wait_span: Span, if_span: Span },
}
impl Cause {
fn message(self) -> &'static str {
match self {
Cause::NeverWait => "spawned process is never `wait()`ed on",
Cause::EarlyReturn { .. } | Cause::MissingWaitInBranch { .. } | Cause::MissingElse { .. } => {
"spawned process is not `wait()`ed on in all code paths"
},
}
}
fn fallback_help(self) -> &'static str {
match self {
Cause::NeverWait => "consider calling `.wait()`",
Cause::EarlyReturn { .. } | Cause::MissingWaitInBranch { .. } | Cause::MissingElse { .. } => {
"consider calling `.wait()` in all code paths"
},
}
}
}
@ -220,7 +294,7 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> {
/// `let _ = <expr that spawns child>;`.
///
/// This checks if the program doesn't unconditionally exit after the spawn expression.
fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, emit_suggestion: bool) {
fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Cause, emit_suggestion: bool) {
let Some(block) = get_enclosing_block(cx, spawn_expr.hir_id) else {
return;
};
@ -234,27 +308,46 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, emit_sugges
return;
}
span_lint_and_then(
cx,
ZOMBIE_PROCESSES,
spawn_expr.span,
"spawned process is never `wait()`ed on",
|diag| {
if emit_suggestion {
diag.span_suggestion(
spawn_expr.span.shrink_to_hi(),
"try",
".wait()",
Applicability::MaybeIncorrect,
span_lint_and_then(cx, ZOMBIE_PROCESSES, spawn_expr.span, cause.message(), |diag| {
match cause {
Cause::EarlyReturn { wait_span, return_span } => {
diag.span_note(
return_span,
"no `wait()` call exists on the code path to this early return",
);
} else {
diag.note("consider calling `.wait()`");
}
diag.span_note(
wait_span,
"`wait()` call exists, but it is unreachable due to the early return",
);
},
Cause::MissingWaitInBranch { wait_span, branch_span } => {
diag.span_note(branch_span, "`wait()` is not called in this if branch");
diag.span_note(wait_span, "`wait()` is called in the other branch");
},
Cause::MissingElse { if_span, wait_span } => {
diag.span_note(
if_span,
"this if expression has a `wait()` call, but it is missing an else block",
);
diag.span_note(wait_span, "`wait()` called here");
},
Cause::NeverWait => {},
}
diag.note("not doing so might leave behind zombie processes")
.note("see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning");
},
);
if emit_suggestion {
diag.span_suggestion(
spawn_expr.span.shrink_to_hi(),
"try",
".wait()",
Applicability::MaybeIncorrect,
);
} else {
diag.help(cause.fallback_help());
}
diag.note("not doing so might leave behind zombie processes")
.note("see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning");
});
}
/// Checks if the given expression exits the process.