Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
12edfb82e5
147 changed files with 3698 additions and 961 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
19
clippy_lints/src/casts/as_pointer_underscore.rs
Normal file
19
clippy_lints/src/casts/as_pointer_underscore.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
159
clippy_lints/src/literal_string_with_formatting_args.rs
Normal file
159
clippy_lints/src/literal_string_with_formatting_args.rs
Normal 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 = ¤t[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() };
|
||||
/// }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue