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

This commit is contained in:
Philipp Krones 2025-01-09 18:00:37 +01:00
commit b5bf09e57a
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
156 changed files with 2118 additions and 522 deletions

View file

@ -428,8 +428,8 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
// Makes a note of the current item for comparison with the next.
cur_t = Some(CurItem {
order: module_level_order,
item,
order: module_level_order,
name: get_item_name(item),
});
}
@ -464,7 +464,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte
ItemKind::Use(..) => Use,
ItemKind::Static(..) => Static,
ItemKind::Const(..) => Const,
ItemKind::Fn{ .. } => Fn,
ItemKind::Fn { .. } => Fn,
ItemKind::Macro(..) => Macro,
ItemKind::Mod(..) => Mod,
ItemKind::ForeignMod { .. } => ForeignMod,

View file

@ -66,7 +66,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item_span: Span, attrs: &[Attribute])
fn lint_mixed_attrs(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
let mut attrs_iter = attrs.iter().filter(|attr| !attr.span.from_expansion());
let span = if let (Some(first), Some(last)) = (attrs_iter.next(), attrs_iter.last()) {
let span = if let (Some(first), Some(last)) = (attrs_iter.next(), attrs_iter.next_back()) {
first.span.with_hi(last.span.hi())
} else {
return;

View file

@ -1,11 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::snippet_with_context;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
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;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::BytePos;
use super::BORROW_AS_PTR;
@ -32,12 +34,21 @@ pub(super) fn check<'tcx>(
return false;
}
let suggestion = if msrv.meets(msrvs::RAW_REF_OP) {
let (suggestion, span) = if msrv.meets(msrvs::RAW_REF_OP) {
let operator_kind = match mutability {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
format!("&raw {operator_kind} {snip}")
// Make sure that the span to be replaced doesn't include parentheses, that could break the
// suggestion.
let span = if has_enclosing_paren(snippet_with_applicability(cx, expr.span, "", &mut app)) {
expr.span
.with_lo(expr.span.lo() + BytePos(1))
.with_hi(expr.span.hi() - BytePos(1))
} else {
expr.span
};
(format!("&raw {operator_kind} {snip}"), span)
} else {
let Some(std_or_core) = std_or_core(cx) else {
return false;
@ -46,18 +57,10 @@ pub(super) fn check<'tcx>(
Mutability::Not => "addr_of",
Mutability::Mut => "addr_of_mut",
};
format!("{std_or_core}::ptr::{macro_name}!({snip})")
(format!("{std_or_core}::ptr::{macro_name}!({snip})"), expr.span)
};
span_lint_and_sugg(
cx,
BORROW_AS_PTR,
expr.span,
"borrow as raw pointer",
"try",
suggestion,
Applicability::MachineApplicable,
);
span_lint_and_sugg(cx, BORROW_AS_PTR, span, "borrow as raw pointer", "try", suggestion, app);
return true;
}
false

View file

@ -836,11 +836,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
as_underscore::check(cx, expr, cast_to_hir);
as_pointer_underscore::check(cx, cast_to, cast_to_hir);
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
};
let was_borrow_as_ptr_emitted = self.msrv.meets(msrvs::BORROW_AS_PTR)
&& borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, &self.msrv);
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);
}

View file

@ -372,6 +372,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::CONST_IS_EMPTY_INFO,
crate::methods::DOUBLE_ENDED_ITERATOR_LAST_INFO,
crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO,

View file

@ -1,6 +1,6 @@
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item};
use clippy_utils::{is_doc_hidden, return_ty};
use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
use rustc_lint::LateContext;
@ -70,7 +70,14 @@ pub fn check(
&& let typeck = cx.tcx.typeck_body(body_id)
&& let body = cx.tcx.hir().body(body_id)
&& let ret_ty = typeck.expr_ty(body.value)
&& implements_trait(cx, ret_ty, future, &[])
&& implements_trait_with_env(
cx.tcx,
ty::TypingEnv::non_body_analysis(cx.tcx, owner_id.def_id),
ret_ty,
future,
Some(owner_id.def_id.to_def_id()),
&[],
)
&& let ty::Coroutine(_, subs) = ret_ty.kind()
&& is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
{

View file

@ -797,8 +797,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
parser.into_offset_iter(),
&doc,
Fragments {
fragments: &fragments,
doc: &doc,
fragments: &fragments,
},
))
}

View file

@ -25,7 +25,7 @@ pub(super) fn check(
// page. So associated items or impl blocks are not part of this list.
ItemKind::Static(..)
| ItemKind::Const(..)
| ItemKind::Fn{ .. }
| ItemKind::Fn { .. }
| ItemKind::Macro(..)
| ItemKind::Mod(..)
| ItemKind::TyAlias(..)

View file

@ -678,12 +678,12 @@ fn find_insert_calls<'tcx>(
map: contains_expr.map,
key: contains_expr.key,
ctxt: expr.span.ctxt(),
edits: Vec::new(),
is_map_used: false,
allow_insert_closure: true,
can_use_entry: true,
in_tail_pos: true,
is_single_insert: true,
is_map_used: false,
edits: Vec::new(),
loops: Vec::new(),
locals: HirIdSet::default(),
};

View file

@ -182,7 +182,7 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc
// will succeed iff `T: 'static`. But the region of `T` is always erased by `typeck.expr_ty()` when
// T is a generic type. For example, return type of `Option<String>::as_deref()` is a generic.
// So we have a hack like this.
&& generic_args.len() > 0
&& !generic_args.is_empty()
{
return;
}

View file

@ -243,11 +243,11 @@ fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds
&& !predicates.is_empty()
{
Some(ImplTraitBound {
span: bound.span(),
predicates,
trait_def_id,
args: path.args.map_or([].as_slice(), |p| p.args),
constraints: path.args.map_or([].as_slice(), |p| p.constraints),
trait_def_id,
span: bound.span(),
})
} else {
None

View file

@ -1,19 +1,21 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::fulfill_or_allowed;
use clippy_utils::source::snippet;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind, StructTailExpr};
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::Symbol;
use std::fmt::{self, Write as _};
declare_clippy_lint! {
/// ### What it does
/// Checks for struct constructors where all fields are shorthand and
/// the order of the field init shorthand in the constructor is inconsistent
/// with the order in the struct definition.
/// Checks for struct constructors where the order of the field
/// init in the constructor is inconsistent with the order in the
/// struct definition.
///
/// ### Why is this bad?
/// Since the order of fields in a constructor doesn't affect the
@ -59,16 +61,37 @@ declare_clippy_lint! {
#[clippy::version = "1.52.0"]
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
pedantic,
"the order of the field init shorthand is inconsistent with the order in the struct definition"
"the order of the field init is inconsistent with the order in the struct definition"
}
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
pub struct InconsistentStructConstructor {
lint_inconsistent_struct_field_initializers: bool,
}
impl InconsistentStructConstructor {
pub fn new(conf: &'static Conf) -> Self {
Self {
lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers,
}
}
}
impl_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let ExprKind::Struct(qpath, fields, base) = expr.kind
&& fields.iter().all(|f| f.is_shorthand)
&& !expr.span.from_expansion()
let ExprKind::Struct(_, fields, _) = expr.kind else {
return;
};
let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand);
let applicability = if all_fields_are_shorthand {
Applicability::MachineApplicable
} else if self.lint_inconsistent_struct_field_initializers {
Applicability::MaybeIncorrect
} else {
return;
};
if !expr.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
@ -85,36 +108,24 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let StructTailExpr::Base(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!(
"{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
let span = field_with_attrs_span(cx.tcx, fields.first().unwrap())
.with_hi(field_with_attrs_span(cx.tcx, fields.last().unwrap()).hi());
if !fulfill_or_allowed(cx, INCONSISTENT_STRUCT_CONSTRUCTOR, Some(ty_hir_id)) {
span_lint_and_sugg(
span_lint_and_then(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
|diag| {
let msg = if all_fields_are_shorthand {
"try"
} else {
"if the field evaluation order doesn't matter, try"
};
let sugg = suggestion(cx, fields, &def_order_map);
diag.span_suggestion(span, msg, sugg, applicability);
},
);
}
}
@ -135,3 +146,45 @@ fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map
true
}
fn suggestion<'tcx>(
cx: &LateContext<'_>,
fields: &'tcx [hir::ExprField<'tcx>],
def_order_map: &FxHashMap<Symbol, usize>,
) -> String {
let ws = fields
.windows(2)
.map(|w| {
let w0_span = field_with_attrs_span(cx.tcx, &w[0]);
let w1_span = field_with_attrs_span(cx.tcx, &w[1]);
let span = w0_span.between(w1_span);
snippet(cx, span, " ")
})
.collect::<Vec<_>>();
let mut fields = fields.to_vec();
fields.sort_unstable_by_key(|field| def_order_map[&field.ident.name]);
let field_snippets = fields
.iter()
.map(|field| snippet(cx, field_with_attrs_span(cx.tcx, field), ".."))
.collect::<Vec<_>>();
assert_eq!(field_snippets.len(), ws.len() + 1);
let mut sugg = String::new();
for i in 0..field_snippets.len() {
sugg += &field_snippets[i];
if i < ws.len() {
sugg += &ws[i];
}
}
sugg
}
fn field_with_attrs_span(tcx: TyCtxt<'_>, field: &hir::ExprField<'_>) -> Span {
if let Some(attr) = tcx.hir().attrs(field.hir_id).first() {
field.span.with_lo(attr.span.lo())
} else {
field.span
}
}

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, is_trait_method, peel_ref_operators};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -620,18 +621,30 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
})
}
let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
match ty.kind() {
ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| {
let is_empty = sym!(is_empty);
cx.tcx
.associated_items(principal.def_id())
.filter_by_name_unhygienic(is_empty)
.any(|item| is_is_empty(cx, item))
}),
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),
ty::Adt(id, _) => has_is_empty_impl(cx, id.did()),
ty::Array(..) | ty::Slice(..) | ty::Str => true,
_ => false,
fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool {
match ty.kind() {
ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| {
let is_empty = sym!(is_empty);
cx.tcx
.associated_items(principal.def_id())
.filter_by_name_unhygienic(is_empty)
.any(|item| is_is_empty(cx, item))
}),
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),
ty::Adt(id, _) => {
has_is_empty_impl(cx, id.did())
|| (cx.tcx.recursion_limit().value_within_limit(depth)
&& cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| {
implements_trait(cx, ty, deref_id, &[])
&& cx
.get_associated_type(ty, deref_id, "Target")
.is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1))
}))
},
ty::Array(..) | ty::Slice(..) | ty::Str => true,
_ => false,
}
}
ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0)
}

View file

@ -649,7 +649,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn));
store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf)));
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(move |_| {
Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new(
conf,
))
});
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf)));

View file

@ -38,8 +38,8 @@ pub(super) fn check<'tcx>(
cx,
label,
inner_labels: label.into_iter().collect(),
is_finite: false,
loop_depth: 0,
is_finite: false,
};
loop_visitor.visit_block(loop_block);

View file

@ -220,11 +220,11 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
// `check_stmt_post` on `(Late)LintPass`, which we'd need to detect when we're leaving a macro span
let mut vis = BodyVisitor {
macro_unsafe_blocks: Vec::new(),
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
expn_depth: if body.value.span.from_expansion() { 1 } else { 0 },
macro_unsafe_blocks: Vec::new(),
lint: self,
cx
cx,
lint: self
};
vis.visit_body(body);
}

View file

@ -2,14 +2,15 @@ use clippy_utils::SpanlessEq;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_ast::{BinOpKind, LitKind};
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp};
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::Symbol;
use clippy_config::Conf;
@ -138,9 +139,40 @@ fn build_suggestion(
applicability: &mut Applicability,
) {
let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_par();
let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric()
&& matches!(
lhs.kind,
ExprKind::Lit(Spanned {
node: LitKind::Int(_, LitIntType::Unsuffixed),
..
}) | ExprKind::Unary(UnOp::Neg, Expr {
kind: ExprKind::Lit(Spanned {
node: LitKind::Int(_, LitIntType::Unsuffixed),
..
}),
..
})
) {
format!("_{}", cx.typeck_results().expr_ty(rhs))
} else {
String::new()
};
let dividend_sugg_str = dividend_sugg.into_string();
// If `dividend_sugg` has enclosing paren like `(-2048)` and we need to add type suffix in the
// suggestion message, we want to make a suggestion string before `div_ceil` like
// `(-2048_{type_suffix})`.
let suggestion_before_div_ceil = if has_enclosing_paren(&dividend_sugg_str) {
format!(
"{}{})",
&dividend_sugg_str[..dividend_sugg_str.len() - 1].to_string(),
type_suffix
)
} else {
format!("{dividend_sugg_str}{type_suffix}")
};
let divisor_snippet = snippet_with_applicability(cx, rhs.span.source_callsite(), "..", applicability);
let sugg = format!("{dividend_sugg}.div_ceil({divisor_snippet})");
let sugg = format!("{suggestion_before_div_ceil}.div_ceil({divisor_snippet})");
span_lint_and_sugg(
cx,

View file

@ -32,7 +32,7 @@ declare_clippy_lint! {
/// a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")
/// }
/// ```
#[clippy::version = "1.82.0"]
#[clippy::version = "1.84.0"]
pub MANUAL_IGNORE_CASE_CMP,
perf,
"manual case-insensitive ASCII comparison"

View file

@ -9,7 +9,7 @@ use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node, Param, PatKind, RangeEnd};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, sym};
@ -114,7 +114,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
&& !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_))
{
let arg = peel_ref_operators(cx, arg);
let ty_sugg = get_ty_sugg(cx, arg, start);
let ty_sugg = get_ty_sugg(cx, arg);
let range = check_range(start, end);
check_is_ascii(cx, expr.span, arg, &range, ty_sugg);
}
@ -123,19 +123,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
extract_msrv_attr!(LateContext);
}
fn get_ty_sugg(cx: &LateContext<'_>, arg: &Expr<'_>, bound_expr: &Expr<'_>) -> Option<(Span, &'static str)> {
if let ExprKind::Lit(lit) = bound_expr.kind
&& let local_hid = path_to_local(arg)?
&& let Node::Param(Param { ty_span, span, .. }) = cx.tcx.parent_hir_node(local_hid)
fn get_ty_sugg<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'_>) -> Option<(Span, Ty<'tcx>)> {
let local_hid = path_to_local(arg)?;
if let Node::Param(Param { ty_span, span, .. }) = cx.tcx.parent_hir_node(local_hid)
// `ty_span` and `span` are the same for inferred type, thus a type suggestion must be given
&& ty_span == span
{
let ty_str = match lit.node {
Char(_) => "char",
Byte(_) => "u8",
_ => return None,
};
return Some((*ty_span, ty_str));
let arg_type = cx.typeck_results().expr_ty(arg);
return Some((*ty_span, arg_type));
}
None
}
@ -145,7 +140,7 @@ fn check_is_ascii(
span: Span,
recv: &Expr<'_>,
range: &CharRange,
ty_sugg: Option<(Span, &'_ str)>,
ty_sugg: Option<(Span, Ty<'_>)>,
) {
let sugg = match range {
CharRange::UpperChar => "is_ascii_uppercase",
@ -159,8 +154,8 @@ fn check_is_ascii(
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))];
if let Some((ty_span, ty_str)) = ty_sugg {
suggestion.push((ty_span, format!("{recv}: {ty_str}")));
if let Some((ty_span, ty)) = ty_sugg {
suggestion.push((ty_span, format!("{recv}: {ty}")));
}
span_lint_and_then(

View file

@ -583,11 +583,6 @@ declare_clippy_lint! {
/// are the same on purpose, you can factor them
/// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
///
/// ### Known problems
/// False positive possible with order dependent `match`
/// (see issue
/// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
///
/// ### Example
/// ```rust,ignore
/// match foo {

View file

@ -133,7 +133,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
},
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
Node::Item(item) => {
if let ItemKind::Fn{ .. } = item.kind {
if let ItemKind::Fn { .. } = item.kind {
let output = cx
.tcx
.fn_sig(item.owner_id)

View file

@ -1,6 +1,6 @@
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::source::walk_span_to_context;
use clippy_utils::sugg::{Sugg, make_unop};
use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr_without_closures};
@ -274,7 +274,9 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
ExprKind::AddrOf(_, _, borrowed) => borrowed,
_ => op,
};
let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_"));
let mut app = Applicability::MachineApplicable;
let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_par();
let mut sugg = format!("{receiver_sugg}.{good_method}");
if let Some(guard) = maybe_guard {
// wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying!
@ -307,7 +309,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
format!("redundant pattern matching, consider using `{good_method}`"),
"try",
sugg,
Applicability::MachineApplicable,
app,
);
}
}

View file

@ -345,7 +345,7 @@ impl<'a> PatState<'a> {
PatKind::Guard(..) => {
matches!(self, Self::Wild)
}
},
// Patterns for things which can only contain a single sub-pattern.
PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) | PatKind::Box(pat) | PatKind::Deref(pat) => {

View file

@ -0,0 +1,41 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::Instance;
use rustc_span::{Span, sym};
use super::DOUBLE_ENDED_ITERATOR_LAST;
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) {
let typeck = cx.typeck_results();
// if the "last" method is that of Iterator
if is_trait_method(cx, expr, sym::Iterator)
// if self implements DoubleEndedIterator
&& let Some(deiter_id) = cx.tcx.get_diagnostic_item(sym::DoubleEndedIterator)
&& let self_type = cx.typeck_results().expr_ty(self_expr)
&& implements_trait(cx, self_type.peel_refs(), deiter_id, &[])
// resolve the method definition
&& let id = typeck.type_dependent_def_id(expr.hir_id).unwrap()
&& let args = typeck.node_args(expr.hir_id)
&& let Ok(Some(fn_def)) = Instance::try_resolve(cx.tcx, cx.typing_env(), id, args)
// find the provided definition of Iterator::last
&& let Some(item) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name.as_str() == "last")
// if the resolved method is the same as the provided definition
&& fn_def.def_id() == last_def.def_id
{
span_lint_and_sugg(
cx,
DOUBLE_ENDED_ITERATOR_LAST,
call_span,
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
"try",
"next_back()".to_string(),
Applicability::MachineApplicable,
);
}
}

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_trait_method, span_contains_comment};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
@ -17,10 +17,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_
let mut applicability = Applicability::MachineApplicable;
let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
let span = expr.span.with_lo(map_span.lo());
// If the methods are separated with comments, we don't apply suggestion automatically.
if span_contains_comment(cx.tcx.sess.source_map(), span) {
applicability = Applicability::Unspecified;
}
span_lint_and_sugg(
cx,
MAP_FLATTEN,
expr.span.with_lo(map_span.lo()),
span,
format!("called `map(..).flatten()` on `{caller_ty_name}`"),
format!("try replacing `map` with `{method_to_use}` and remove the `.flatten()`"),
format!("{method_to_use}({closure_snippet})"),

View file

@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method, path_to_local};
use rustc_ast::BindingMode;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{self as hir, Node, PatKind};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
@ -24,6 +25,16 @@ pub(super) fn check(
&& is_expr_untyped_identity_function(cx, map_arg)
&& let Some(sugg_span) = expr.span.trim_start(caller.span)
{
// If the result of `.map(identity)` is used as a mutable reference,
// the caller must not be an immutable binding.
if cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr()
&& let Some(hir_id) = path_to_local(caller)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& !matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
{
return;
}
span_lint_and_sugg(
cx,
MAP_IDENTITY,

View file

@ -14,6 +14,7 @@ mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod collapsible_str_replace;
mod double_ended_iterator_last;
mod drain_collect;
mod err_expect;
mod expect_fun_call;
@ -4284,6 +4285,32 @@ declare_clippy_lint! {
"map of a trivial closure (not dependent on parameter) over a range"
}
declare_clippy_lint! {
/// ### What it does
///
/// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced
/// with `DoubleEndedIterator::next_back`.
///
/// ### Why is this bad?
///
/// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if
/// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization,
/// `Iterator::last` cannot be optimized for `DoubleEndedIterator`.
///
/// ### Example
/// ```no_run
/// let last_arg = "echo hello world".split(' ').last();
/// ```
/// Use instead:
/// ```no_run
/// let last_arg = "echo hello world".split(' ').next_back();
/// ```
#[clippy::version = "1.85.0"]
pub DOUBLE_ENDED_ITERATOR_LAST,
perf,
"using `Iterator::last` on a `DoubleEndedIterator`"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4449,6 +4476,7 @@ impl_lint_pass!(Methods => [
MAP_ALL_ANY_IDENTITY,
MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES,
UNNECESSARY_MAP_OR,
DOUBLE_ENDED_ITERATOR_LAST,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -4931,6 +4959,7 @@ impl Methods {
false,
);
}
double_ended_iterator_last::check(cx, expr, recv, call_span);
},
("len", []) => {
if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) {

View file

@ -7,6 +7,7 @@ use rustc_span::Span;
use super::NEEDLESS_CHARACTER_ITERATION;
use super::utils::get_last_chain_binding_hir_id;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::CHAR_IS_ASCII;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{match_def_path, path_to_local_id, peel_blocks};
@ -77,7 +78,7 @@ fn handle_expr(
if revert != is_all
&& let ExprKind::Path(path) = fn_path.kind
&& let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
&& match_def_path(cx, fn_def_id, &["core", "char", "methods", "<impl char>", "is_ascii"])
&& match_def_path(cx, fn_def_id, &CHAR_IS_ASCII)
&& path_to_local_id(peels_expr_ref(arg), first_param)
&& let Some(snippet) = before_chars.get_source_text(cx)
{

View file

@ -470,14 +470,14 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
captured_ids: HirIdSet,
) -> Option<Vec<IterFunction>> {
let mut visitor = IterFunctionVisitor {
uses: Vec::new(),
target: id,
seen_other: false,
cx,
current_mutably_captured_ids: HirIdSet::default(),
illegal_mutable_capture_ids: captured_ids,
current_mutably_captured_ids: HirIdSet::default(),
cx,
uses: Vec::new(),
hir_id_uses_map: FxHashMap::default(),
current_statement_hir_id: None,
seen_other: false,
target: id,
};
visitor.visit_block(block);
if visitor.seen_other {

View file

@ -1,6 +1,7 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::STDIN;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_local_use_after_expr;
@ -33,7 +34,7 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool {
pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
if let Some(recv_adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& match_def_path(cx, recv_adt.did(), &["std", "io", "stdio", "Stdin"])
&& match_def_path(cx, recv_adt.did(), &STDIN)
&& let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind
&& let Res::Local(local_id) = path.res
{

View file

@ -297,8 +297,8 @@ fn parse_iter_usage<'tcx>(
{
Some(IterUsage {
kind: IterUsageKind::NextTuple,
span: e.span,
unwrap_kind: None,
span: e.span,
})
} else {
None

View file

@ -124,30 +124,30 @@ pub(super) fn check(
match lit.node {
ast::LitKind::Bool(false) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, Replacement {
method_name: "any",
has_args: true,
has_generic_return: false,
method_name: "any",
});
},
ast::LitKind::Bool(true) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, Replacement {
method_name: "all",
has_args: true,
has_generic_return: false,
method_name: "all",
});
},
ast::LitKind::Int(Pu128(0), _) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, Replacement {
method_name: "sum",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "sum",
});
},
ast::LitKind::Int(Pu128(1), _) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, Replacement {
method_name: "product",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "product",
});
},
_ => (),

View file

@ -7,7 +7,7 @@ use clippy_utils::source::snippet_opt;
use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::ty::{get_type_diagnostic_name, implements_trait};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_from_proc_macro, path_to_local_id};
use clippy_utils::{get_parent_expr, is_from_proc_macro, path_to_local_id};
use rustc_ast::LitKind::Bool;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
@ -96,11 +96,25 @@ pub(super) fn check<'a>(
Sugg::hir(cx, non_binding_location, "")
)));
let binop = make_binop(op.node, &Sugg::hir(cx, recv, ".."), &inner_non_binding)
.maybe_par()
.into_string();
let mut app = Applicability::MachineApplicable;
let binop = make_binop(
op.node,
&Sugg::hir_with_applicability(cx, recv, "..", &mut app),
&inner_non_binding,
);
(binop, "a standard comparison", Applicability::MaybeIncorrect)
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
match parent_expr.kind {
ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_par(),
ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_par(),
_ => binop,
}
} else {
binop
}
.into_string();
(sugg, "a standard comparison", app)
} else if !def_bool
&& msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
&& let Some(recv_callsite) = snippet_opt(cx, recv.span.source_callsite())

View file

@ -5,7 +5,8 @@ use clippy_utils::{is_trait_method, std_or_core};
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

@ -494,7 +494,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match node {
Node::Stmt(_) => return true,
Node::Block(..) => continue,
Node::Block(..) => {},
Node::Item(item) => {
if let ItemKind::Fn { body: body_id, .. } = &item.kind
&& let output_ty = return_ty(cx, item.owner_id)

View file

@ -2,7 +2,7 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method};
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, is_in_test, trait_ref_of_method};
use rustc_errors::Applicability;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind;
@ -97,6 +97,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
span: Span,
def_id: LocalDefId,
) {
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
if is_in_test(cx.tcx, hir_id) {
return;
}
if !self.msrv.meets(msrvs::CONST_IF_MATCH) {
return;
}
@ -136,8 +141,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
return;
}
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
// Const fns are not allowed as methods in a trait.
{
let parent = cx.tcx.hir().get_parent_item(hir_id).def_id;

View file

@ -192,7 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
match it.kind {
hir::ItemKind::Fn{ .. } => {
hir::ItemKind::Fn { .. } => {
// ignore main()
if it.ident.name == sym::main {
let at_root = cx.tcx.local_parent(it.owner_id.def_id) == CRATE_DEF_ID;

View file

@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
return;
}
match it.kind {
hir::ItemKind::Fn{ .. } => {
hir::ItemKind::Fn { .. } => {
let desc = "a function";
let attrs = cx.tcx.hir().attrs(it.hir_id());
check_missing_inline_attrs(cx, attrs, it.span, desc);

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

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Mutability, Param, PatKind, Path, TyKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -80,7 +81,8 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod
applicability = Applicability::HasPlaceholders;
"&'_ mut self".to_string()
} else {
format!("&{} mut self", &lifetime.ident.name)
let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability);
format!("&{lt_name} mut self")
}
},
(Mode::Ref(None), Mutability::Not) => "&self".to_string(),
@ -89,7 +91,8 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod
applicability = Applicability::HasPlaceholders;
"&'_ self".to_string()
} else {
format!("&{} self", &lifetime.ident.name)
let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability);
format!("&{lt_name} self")
}
},
(Mode::Value, Mutability::Mut) => "mut self".to_string(),

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::{indent_of, snippet, snippet_block};
use rustc_ast::ast;
use rustc_ast::{Block, Label, ast};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -11,6 +11,7 @@ declare_clippy_lint! {
/// that contain a `continue` statement in either their main blocks or their
/// `else`-blocks, when omitting the `else`-block possibly with some
/// rearrangement of code can make the code easier to understand.
/// The lint also checks if the last statement in the loop is a `continue`
///
/// ### Why is this bad?
/// Having explicit `else` blocks for `if` statements
@ -75,6 +76,49 @@ declare_clippy_lint! {
/// # break;
/// }
/// ```
///
/// ```rust
/// # use std::io::ErrorKind;
///
/// fn foo() -> ErrorKind { ErrorKind::NotFound }
/// for _ in 0..10 {
/// match foo() {
/// ErrorKind::NotFound => {
/// eprintln!("not found");
/// continue
/// }
/// ErrorKind::TimedOut => {
/// eprintln!("timeout");
/// continue
/// }
/// _ => {
/// eprintln!("other error");
/// continue
/// }
/// }
/// }
/// ```
/// Could be rewritten as
///
///
/// ```rust
/// # use std::io::ErrorKind;
///
/// fn foo() -> ErrorKind { ErrorKind::NotFound }
/// for _ in 0..10 {
/// match foo() {
/// ErrorKind::NotFound => {
/// eprintln!("not found");
/// }
/// ErrorKind::TimedOut => {
/// eprintln!("timeout");
/// }
/// _ => {
/// eprintln!("other error");
/// }
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEEDLESS_CONTINUE,
pedantic,
@ -144,7 +188,7 @@ impl EarlyLintPass for NeedlessContinue {
///
/// - The expression is a `continue` node.
/// - The expression node is a block with the first statement being a `continue`.
fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&Label>) -> bool {
match else_expr.kind {
ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
@ -152,7 +196,7 @@ fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>)
}
}
fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
fn is_first_block_stmt_continue(block: &Block, label: Option<&Label>) -> bool {
block.stmts.first().is_some_and(|stmt| match stmt.kind {
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
if let ast::ExprKind::Continue(ref l) = e.kind {
@ -166,7 +210,7 @@ fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>)
}
/// If the `continue` has a label, check it matches the label of the loop.
fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool {
fn compare_labels(loop_label: Option<&Label>, continue_label: Option<&Label>) -> bool {
match (loop_label, continue_label) {
// `loop { continue; }` or `'a loop { continue; }`
(_, None) => true,
@ -181,7 +225,7 @@ fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::
/// the AST object representing the loop block of `expr`.
fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
where
F: FnMut(&ast::Block, Option<&ast::Label>),
F: FnMut(&Block, Option<&Label>),
{
if let ast::ExprKind::While(_, loop_block, label)
| ast::ExprKind::ForLoop {
@ -205,7 +249,7 @@ where
/// - The `else` expression.
fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
where
F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
F: FnMut(&ast::Expr, &ast::Expr, &Block, &ast::Expr),
{
match stmt.kind {
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
@ -231,14 +275,14 @@ struct LintData<'a> {
/// The condition expression for the above `if`.
if_cond: &'a ast::Expr,
/// The `then` block of the `if` statement.
if_block: &'a ast::Block,
if_block: &'a Block,
/// The `else` block of the `if` statement.
/// Note that we only work with `if` exprs that have an `else` branch.
else_expr: &'a ast::Expr,
/// The 0-based index of the `if` statement in the containing loop block.
stmt_idx: usize,
/// The statements of the loop block.
loop_block: &'a ast::Block,
loop_block: &'a Block,
}
const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
@ -329,33 +373,74 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin
)
}
fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind
&& let Some(last_stmt) = loop_block.stmts.last()
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind
&& let ast::ExprKind::Continue(continue_label) = inner_expr.kind
&& compare_labels(loop_label.as_ref(), continue_label.as_ref())
{
span_lint_and_help(
cx,
NEEDLESS_CONTINUE,
last_stmt.span,
MSG_REDUNDANT_CONTINUE_EXPRESSION,
None,
DROP_CONTINUE_EXPRESSION_MSG,
);
fn check_last_stmt_in_expr<F>(inner_expr: &ast::Expr, func: &F)
where
F: Fn(Option<&Label>, Span),
{
match &inner_expr.kind {
ast::ExprKind::Continue(continue_label) => {
func(continue_label.as_ref(), inner_expr.span);
},
ast::ExprKind::If(_, then_block, else_block) => {
check_last_stmt_in_block(then_block, func);
if let Some(else_block) = else_block {
check_last_stmt_in_expr(else_block, func);
}
},
ast::ExprKind::Match(_, arms, _) => {
for arm in arms {
if let Some(expr) = &arm.body {
check_last_stmt_in_expr(expr, func);
}
}
},
ast::ExprKind::Block(b, _) => {
check_last_stmt_in_block(b, func);
},
_ => {},
}
}
fn check_last_stmt_in_block<F>(b: &Block, func: &F)
where
F: Fn(Option<&Label>, Span),
{
if let Some(last_stmt) = b.stmts.last()
&& let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind
{
check_last_stmt_in_expr(inner_expr, func);
}
}
fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
with_loop_block(expr, |loop_block, label| {
for (i, stmt) in loop_block.stmts.iter().enumerate() {
let p = |continue_label: Option<&Label>, span: Span| {
if compare_labels(label, continue_label) {
span_lint_and_help(
cx,
NEEDLESS_CONTINUE,
span,
MSG_REDUNDANT_CONTINUE_EXPRESSION,
None,
DROP_CONTINUE_EXPRESSION_MSG,
);
}
};
let stmts = &loop_block.stmts;
for (i, stmt) in stmts.iter().enumerate() {
let mut maybe_emitted_in_if = false;
with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
let data = &LintData {
stmt_idx: i,
if_expr,
if_cond: cond,
if_block: then_block,
else_expr,
stmt_idx: i,
loop_block,
};
maybe_emitted_in_if = true;
if needless_continue_in_else(else_expr, label) {
emit_warning(
cx,
@ -365,8 +450,14 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
);
} else if is_first_block_stmt_continue(then_block, label) {
emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
} else {
maybe_emitted_in_if = false;
}
});
if i == stmts.len() - 1 && !maybe_emitted_in_if {
check_last_stmt_in_block(loop_block, &p);
}
}
});
}
@ -400,7 +491,7 @@ fn erode_from_back(s: &str) -> String {
if ret.is_empty() { s.to_string() } else { ret }
}
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
fn span_of_first_expr_in_block(block: &Block) -> Option<Span> {
block.stmts.first().map(|stmt| stmt.span)
}

View file

@ -144,7 +144,7 @@ impl NoEffect {
|diag| {
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
if let Node::Item(item) = parent.1
&& let ItemKind::Fn{ .. } = item.kind
&& let ItemKind::Fn { .. } = item.kind
&& let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id)
&& let [.., final_stmt] = block.stmts
&& final_stmt.hir_id == stmt.hir_id

View file

@ -189,6 +189,8 @@ impl<'tcx> NonCopyConst<'tcx> {
}
fn is_value_unfrozen_raw_inner(cx: &LateContext<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> bool {
// No branch that we check (yet) should continue if val isn't a ValTree::Branch
let ty::ValTree::Branch(val) = val else { return false };
match *ty.kind() {
// the fact that we have to dig into every structs to search enums
// leads us to the point checking `UnsafeCell` directly is the only option.
@ -197,12 +199,13 @@ impl<'tcx> NonCopyConst<'tcx> {
// contained value.
ty::Adt(def, ..) if def.is_union() => false,
ty::Array(ty, _) => val
.unwrap_branch()
.iter()
.any(|field| Self::is_value_unfrozen_raw_inner(cx, *field, ty)),
ty::Adt(def, args) if def.is_enum() => {
let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap();
let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().to_u32());
let Some((&ty::ValTree::Leaf(variant_index), fields)) = val.split_first() else {
return false;
};
let variant_index = VariantIdx::from_u32(variant_index.to_u32());
fields
.iter()
.copied()
@ -215,12 +218,10 @@ impl<'tcx> NonCopyConst<'tcx> {
.any(|(field, ty)| Self::is_value_unfrozen_raw_inner(cx, field, ty))
},
ty::Adt(def, args) => val
.unwrap_branch()
.iter()
.zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args)))
.any(|(field, ty)| Self::is_value_unfrozen_raw_inner(cx, *field, ty)),
ty::Tuple(tys) => val
.unwrap_branch()
.iter()
.zip(tys)
.any(|(field, ty)| Self::is_value_unfrozen_raw_inner(cx, *field, ty)),

View file

@ -270,10 +270,10 @@ impl SimilarNamesNameVisitor<'_, '_, '_> {
return;
}
self.0.names.push(ExistingName {
exemptions: get_exemptions(interned_name).unwrap_or(&[]),
interned: ident.name,
span: ident.span,
len: count,
exemptions: get_exemptions(interned_name).unwrap_or(&[]),
});
}

View file

@ -47,6 +47,7 @@ impl ArithmeticSideEffects {
Self {
allowed_binary,
allowed_unary,
const_span: None,
disallowed_int_methods: [
sym::saturating_div,
sym::wrapping_div,
@ -55,7 +56,6 @@ impl ArithmeticSideEffects {
]
.into_iter()
.collect(),
const_span: None,
expr_span: None,
}
}

View file

@ -143,11 +143,11 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> {
self.searcher = Some(PathbufPushSearcher {
local_id: id,
lhs_is_let: true,
name: name.name,
let_ty_span: local.ty.map(|ty| ty.span),
err_span: local.span,
init_val: *init_expr,
arg: None,
name: name.name,
err_span: local.span,
});
}
}
@ -165,10 +165,10 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> {
local_id: id,
lhs_is_let: false,
let_ty_span: None,
name: name.ident.name,
err_span: expr.span,
init_val: *right,
arg: None,
name: name.ident.name,
err_span: expr.span,
});
}
}

View file

@ -69,7 +69,6 @@ impl EarlyLintPass for RedundantElse {
ExprKind::If(_, next_then, Some(next_els)) => {
then = next_then;
els = next_els;
continue;
},
// else if without else
ExprKind::If(..) => return,

View file

@ -17,9 +17,9 @@ declare_clippy_lint! {
/// Checks for redundant redefinitions of local bindings.
///
/// ### Why is this bad?
/// Redundant redefinitions of local bindings do not change behavior and are likely to be unintended.
/// Redundant redefinitions of local bindings do not change behavior other than variable's lifetimes and are likely to be unintended.
///
/// Note that although these bindings do not affect your code's meaning, they _may_ affect `rustc`'s stack allocation.
/// These rebindings can be intentional to shorten the lifetimes of variables because they affect when the `Drop` implementation is called. Other than that, they do not affect your code's meaning but they _may_ affect `rustc`'s stack allocation.
///
/// ### Example
/// ```no_run
@ -41,7 +41,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.73.0"]
pub REDUNDANT_LOCALS,
correctness,
suspicious,
"redundant redefinition of a local binding"
}
declare_lint_pass!(RedundantLocals => [REDUNDANT_LOCALS]);

View file

@ -24,6 +24,11 @@ declare_clippy_lint! {
/// ```ignore
/// Regex::new("(")
/// ```
///
/// Use instead:
/// ```ignore
/// Regex::new("\(")
/// ```
#[clippy::version = "pre 1.29.0"]
pub INVALID_REGEX,
correctness,
@ -49,6 +54,11 @@ declare_clippy_lint! {
/// ```ignore
/// Regex::new("^foobar")
/// ```
///
/// Use instead:
/// ```ignore
/// str::starts_with("foobar")
/// ```
#[clippy::version = "pre 1.29.0"]
pub TRIVIAL_REGEX,
nursery,
@ -87,7 +97,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[clippy::version = "1.83.0"]
#[clippy::version = "1.84.0"]
pub REGEX_CREATION_IN_LOOPS,
perf,
"regular expression compilation performed in a loop"

View file

@ -282,9 +282,9 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> {
}
{
let mut apa = AuxParamsAttr {
first_bind_ident: ident,
first_block_hir_id: self.ap.curr_block_hir_id,
first_block_span: self.ap.curr_block_span,
first_bind_ident: ident,
first_method_span: {
let expr_or_init = expr_or_init(self.cx, expr);
if let hir::ExprKind::MethodCall(_, local_expr, _, span) = expr_or_init.kind {
@ -395,8 +395,8 @@ impl Default for AuxParamsAttr {
counter: 0,
has_expensive_expr_after_last_attr: false,
first_block_hir_id: HirId::INVALID,
first_bind_ident: Ident::empty(),
first_block_span: DUMMY_SP,
first_bind_ident: Ident::empty(),
first_method_span: DUMMY_SP,
first_stmt_span: DUMMY_SP,
last_bind_ident: Ident::empty(),

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::matching_root_macro_call;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
@ -203,14 +203,18 @@ impl SlowVectorInit {
"len",
);
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
diag.span_suggestion(
vec_alloc.allocation_expr.span.source_callsite(),
"consider replacing this with",
format!("vec![0; {len_expr}]"),
Applicability::Unspecified,
);
});
let span_to_replace = slow_fill
.span
.with_lo(vec_alloc.allocation_expr.span.source_callsite().lo());
span_lint_and_sugg(
cx,
SLOW_VECTOR_INITIALIZATION,
span_to_replace,
msg,
"consider replacing this with",
format!("vec![0; {len_expr}]"),
Applicability::Unspecified,
);
}
}

View file

@ -384,9 +384,9 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
fn is_used_after_swap(&mut self, idx_ident: Ident) -> bool {
let mut v = IndexBindingVisitor {
found_used: false,
suggest_span: self.suggest_span,
idx: idx_ident,
suggest_span: self.suggest_span,
found_used: false,
};
for stmt in self.block.stmts {

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::has_repr_attr;
use clippy_utils::{has_repr_attr, is_in_test};
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
@ -37,7 +37,10 @@ declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
if is_struct_with_trailing_zero_sized_array(cx, item)
&& !has_repr_attr(cx, item.hir_id())
&& !is_in_test(cx.tcx, item.hir_id())
{
span_lint_and_help(
cx,
TRAILING_EMPTY_ARRAY,

View file

@ -30,17 +30,14 @@ pub(super) fn check<'tcx>(
| (ReducedTy::UnorderedFields(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
(ReducedTy::OrderedFields(Some(from_sub_ty)), ReducedTy::Other(to_sub_ty)) if reduced_tys.to_fat_ptr => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
(ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) if reduced_tys.from_fat_ptr => {
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
// ptr <-> ptr
@ -50,7 +47,6 @@ pub(super) fn check<'tcx>(
{
from_ty = from_sub_ty;
to_ty = to_sub_ty;
continue;
},
// fat ptr <-> (*size, *size)

View file

@ -388,8 +388,8 @@ impl<'tcx> LateLintPass<'tcx> for Types {
self.check_fn_decl(cx, decl, CheckTyContext {
is_in_trait_impl,
is_exported,
in_body: matches!(fn_kind, FnKind::Closure),
is_exported,
..CheckTyContext::default()
});
}

View file

@ -47,7 +47,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[clippy::version = "1.83.0"]
#[clippy::version = "1.84.0"]
pub UNNECESSARY_LITERAL_BOUND,
pedantic,
"detects &str that could be &'static str in function return types"

View file

@ -120,8 +120,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
let mut visitor = AsyncFnVisitor {
cx,
found_await: false,
async_depth: 0,
await_in_async_block: None,
async_depth: 0,
};
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
if !visitor.found_await {
@ -129,9 +129,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
// The actual linting happens in `check_crate_post`, once we've found all
// uses of local async functions that do require asyncness to pass typeck
self.unused_async_fns.push(UnusedAsyncFn {
await_in_async_block: visitor.await_in_async_block,
fn_span: span,
def_id,
fn_span: span,
await_in_async_block: visitor.await_in_async_block,
});
}
}

View file

@ -245,9 +245,9 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> {
let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
let mut delegate = MutationVisitor {
tcx: self.cx.tcx,
is_mutated: false,
local_id: unwrap_info.local_id,
tcx: self.cx.tcx,
};
let vis = ExprUseVisitor::for_clippy(self.cx, cond.hir_id.owner.def_id, &mut delegate);
@ -397,8 +397,8 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap {
}
let mut v = UnwrappableVariablesVisitor {
cx,
unwrappables: Vec::new(),
cx,
};
walk_fn(&mut v, kind, decl, body.id(), fn_id);

View file

@ -717,7 +717,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
kind!("Guard({pat}, {cond})");
self.pat(pat);
self.expr(cond);
}
},
PatKind::Lit(lit_expr) => {
bind!(self, lit_expr);
kind!("Lit({lit_expr})");

View file

@ -73,6 +73,7 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
SimplifiedType::Slice,
SimplifiedType::Str,
SimplifiedType::Bool,
SimplifiedType::Char,
]
.iter()
.flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter())

View file

@ -8,7 +8,7 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_local_use_after_expr;
use clippy_utils::{get_parent_expr, higher, is_in_test, is_trait_method};
use clippy_utils::{get_parent_expr, higher, is_in_test, is_trait_method, span_contains_comment};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, LetStmt, Mutability, Node, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
@ -132,9 +132,19 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for (span, lint_opt) in &self.span_to_lint_map {
if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt {
let help_msg = format!("you can use {} directly", suggest_slice.desc(),);
let help_msg = format!("you can use {} directly", suggest_slice.desc());
span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| {
diag.span_suggestion(*span, help_msg, snippet, *applicability);
// If the `vec!` macro contains comment, better not make the suggestion machine
// applicable as it would remove them.
let applicability = if *applicability != Applicability::Unspecified
&& let source_map = cx.tcx.sess.source_map()
&& span_contains_comment(source_map, *span)
{
Applicability::Unspecified
} else {
*applicability
};
diag.span_suggestion(*span, help_msg, snippet, applicability);
});
}
}

View file

@ -166,8 +166,8 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
local_id: id,
init,
lhs_is_let: true,
name: name.name,
let_ty_span: local.ty.map(|ty| ty.span),
name: name.name,
err_span: local.span,
found: 0,
last_push_expr: init_expr.hir_id,
@ -206,8 +206,8 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
&& name.ident.as_str() == "push"
{
self.searcher = Some(VecPushSearcher {
found: searcher.found + 1,
err_span: searcher.err_span.to(stmt.span),
found: searcher.found + 1,
last_push_expr: expr.hir_id,
..searcher
});

View file

@ -248,8 +248,8 @@ impl Write {
pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
Self {
format_args,
allow_print_in_tests: conf.allow_print_in_tests,
in_debug_impl: false,
allow_print_in_tests: conf.allow_print_in_tests,
}
}
}

View file

@ -300,8 +300,8 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus
};
let mut vis = ExitPointFinder {
cx,
state: ExitPointState::WalkUpTo(spawn_expr.hir_id),
cx,
};
if let Break(ExitCallFound) = vis.visit_block(block) {
// Visitor found an unconditional `exit()` call, so don't lint.