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

This commit is contained in:
Philipp Krones 2024-03-21 22:05:29 +01:00
commit 7d42d736c5
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
195 changed files with 4233 additions and 1076 deletions

View file

@ -1,3 +1,4 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::HirNode;
use clippy_utils::sugg::Sugg;
@ -6,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Instance, Mutability};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::ExpnKind;
@ -49,7 +50,19 @@ declare_clippy_lint! {
perf,
"assigning the result of cloning may be inefficient"
}
declare_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
pub struct AssigningClones {
msrv: Msrv,
}
impl AssigningClones {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
impl<'tcx> LateLintPass<'tcx> for AssigningClones {
fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx hir::Expr<'_>) {
@ -68,10 +81,12 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
return;
};
if is_ok_to_suggest(cx, lhs, &call) {
if is_ok_to_suggest(cx, lhs, &call, &self.msrv) {
suggest(cx, assign_expr, lhs, &call);
}
}
extract_msrv_attr!(LateContext);
}
// Try to resolve the call to `Clone::clone` or `ToOwned::to_owned`.
@ -135,7 +150,13 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<
// Return true if we find that the called method has a custom implementation and isn't derived or
// provided by default by the corresponding trait.
fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>) -> bool {
fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>, msrv: &Msrv) -> bool {
// For calls to .to_owned we suggest using .clone_into(), which was only stablilized in 1.63.
// If the current MSRV is below that, don't suggest the lint.
if !msrv.meets(msrvs::ASSIGNING_CLONES) && matches!(call.target, TargetTrait::ToOwned) {
return false;
}
// If the left-hand side is a local variable, it might be uninitialized at this point.
// In that case we do not want to suggest the lint.
if let Some(local) = path_to_local(lhs) {

View file

@ -0,0 +1,64 @@
use super::DUPLICATED_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::{Attribute, MetaItem};
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::{sym, Span};
use std::collections::hash_map::Entry;
fn emit_if_duplicated(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
complete_path: String,
) {
match attr_paths.entry(complete_path) {
Entry::Vacant(v) => {
v.insert(attr.span);
},
Entry::Occupied(o) => {
span_lint_and_then(cx, DUPLICATED_ATTRIBUTES, attr.span, "duplicated attribute", |diag| {
diag.span_note(*o.get(), "first defined here");
diag.span_help(attr.span, "remove this attribute");
});
},
}
}
fn check_duplicated_attr(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
parent: &mut Vec<String>,
) {
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
return;
}
if let Some(value) = attr.value_str() {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":")));
} else if let Some(sub_attrs) = attr.meta_item_list() {
parent.push(name.as_str().to_string());
for sub_attr in sub_attrs {
if let Some(meta) = sub_attr.meta_item() {
check_duplicated_attr(cx, meta, attr_paths, parent);
}
}
parent.pop();
} else {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":")));
}
}
pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
let mut attr_paths = FxHashMap::default();
for attr in attrs {
if let Some(meta) = attr.meta() {
check_duplicated_attr(cx, &meta, &mut attr_paths, &mut Vec::new());
}
}
}

View file

@ -4,6 +4,7 @@ mod allow_attributes_without_reason;
mod blanket_clippy_restriction_lints;
mod deprecated_cfg_attr;
mod deprecated_semver;
mod duplicated_attributes;
mod empty_line_after;
mod inline_always;
mod maybe_misused_cfg;
@ -16,7 +17,7 @@ mod useless_attribute;
mod utils;
use clippy_config::msrvs::Msrv;
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
use rustc_ast::{Attribute, Crate, MetaItemKind, NestedMetaItem};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, impl_lint_pass};
@ -489,6 +490,32 @@ declare_clippy_lint! {
"item has both inner and outer attributes"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for attributes that appear two or more times.
///
/// ### Why is this bad?
/// Repeating an attribute on the same item (or globally on the same crate)
/// is unnecessary and doesn't have an effect.
///
/// ### Example
/// ```no_run
/// #[allow(dead_code)]
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
///
/// Use instead:
/// ```no_run
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
#[clippy::version = "1.78.0"]
pub DUPLICATED_ATTRIBUTES,
suspicious,
"duplicated attribute"
}
declare_lint_pass!(Attributes => [
ALLOW_ATTRIBUTES_WITHOUT_REASON,
INLINE_ALWAYS,
@ -568,12 +595,18 @@ impl_lint_pass!(EarlyAttributes => [
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
MIXED_ATTRIBUTES_STYLE,
DUPLICATED_ATTRIBUTES,
]);
impl EarlyLintPass for EarlyAttributes {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
duplicated_attributes::check(cx, &krate.attrs);
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
empty_line_after::check(cx, item);
mixed_attributes_style::check(cx, item);
duplicated_attributes::check(cx, &item.attrs);
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {

View file

@ -1,12 +1,12 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_constant;
use clippy_utils::source::snippet_opt;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::ty::is_isize_or_usize;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::{self, FloatTy, Ty, UintTy};
use super::{utils, CAST_LOSSLESS};
@ -16,6 +16,7 @@ pub(super) fn check(
cast_op: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
cast_to_hir: &rustc_hir::Ty<'_>,
msrv: &Msrv,
) {
if !should_lint(cx, expr, cast_from, cast_to, msrv) {
@ -24,11 +25,11 @@ pub(super) fn check(
// The suggestion is to use a function call, so if the original expression
// has parens on the outside, they are no longer needed.
let mut applicability = Applicability::MachineApplicable;
let mut app = Applicability::MachineApplicable;
let opt = snippet_opt(cx, cast_op.span.source_callsite());
let sugg = opt.as_ref().map_or_else(
|| {
applicability = Applicability::HasPlaceholders;
app = Applicability::HasPlaceholders;
".."
},
|snip| {
@ -40,10 +41,27 @@ pub(super) fn check(
},
);
let message = if cast_from.is_bool() {
format!("casting `{cast_from:}` to `{cast_to:}` is more cleanly stated with `{cast_to:}::from(_)`")
// Display the type alias instead of the aliased type. Fixes #11285
//
// FIXME: Once `lazy_type_alias` is stabilized(?) we should use `rustc_middle` types instead,
// this will allow us to display the right type with `cast_from` as well.
let cast_to_fmt = if let TyKind::Path(QPath::Resolved(None, path)) = cast_to_hir.kind
// It's a bit annoying but the turbofish is optional for types. A type in an `as` cast
// shouldn't have these if they're primitives, which are the only things we deal with.
//
// This could be removed for performance if this check is determined to have a pretty major
// effect.
&& path.segments.iter().all(|segment| segment.args.is_none())
{
snippet_with_applicability(cx, cast_to_hir.span, "..", &mut app)
} else {
format!("casting `{cast_from}` to `{cast_to}` may become silently lossy if you later change the type")
cast_to.to_string().into()
};
let message = if cast_from.is_bool() {
format!("casting `{cast_from}` to `{cast_to_fmt}` is more cleanly stated with `{cast_to_fmt}::from(_)`")
} else {
format!("casting `{cast_from}` to `{cast_to_fmt}` may become silently lossy if you later change the type")
};
span_lint_and_sugg(
@ -52,14 +70,17 @@ pub(super) fn check(
expr.span,
&message,
"try",
format!("{cast_to}::from({sugg})"),
applicability,
format!("{cast_to_fmt}::from({sugg})"),
app,
);
}
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
if in_constant(cx, expr.hir_id) {
//
// If destination is u128, do not lint because source type cannot be larger
// If source is bool, still lint due to the lint message differing (refers to style)
if in_constant(cx, expr.hir_id) || (!cast_from.is_bool() && matches!(cast_to.kind(), ty::Uint(UintTy::U128))) {
return false;
}

View file

@ -791,7 +791,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_nan_to_int::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir, &self.msrv);
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
}

View file

@ -1,12 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::macros::{macro_backtrace, MacroCall};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_hir::{Expr, ExprKind, HirId, Node};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass;
use rustc_span::sym;
use rustc_span::{sym, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -31,31 +33,38 @@ declare_clippy_lint! {
"`dbg!` macro is intended as a debugging tool"
}
#[derive(Copy, Clone)]
#[derive(Clone)]
pub struct DbgMacro {
allow_dbg_in_tests: bool,
/// Tracks the `dbg!` macro callsites that are already checked.
checked_dbg_call_site: FxHashSet<Span>,
/// Tracks the previous `SyntaxContext`, to avoid walking the same context chain.
prev_ctxt: SyntaxContext,
}
impl_lint_pass!(DbgMacro => [DBG_MACRO]);
impl DbgMacro {
pub fn new(allow_dbg_in_tests: bool) -> Self {
DbgMacro { allow_dbg_in_tests }
DbgMacro {
allow_dbg_in_tests,
checked_dbg_call_site: FxHashSet::default(),
prev_ctxt: SyntaxContext::root(),
}
}
}
impl LateLintPass<'_> for DbgMacro {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return;
};
if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
let cur_syntax_ctxt = expr.span.ctxt();
if cur_syntax_ctxt != self.prev_ctxt &&
let Some(macro_call) = first_dbg_macro_in_expansion(cx, expr.span) &&
!in_external_macro(cx.sess(), macro_call.span) &&
self.checked_dbg_call_site.insert(macro_call.span) &&
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
if self.allow_dbg_in_tests
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id))
{
return;
}
!(self.allow_dbg_in_tests && is_in_test(cx, expr.hir_id))
{
let mut applicability = Applicability::MachineApplicable;
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
@ -101,6 +110,8 @@ impl LateLintPass<'_> for DbgMacro {
_ => return,
};
self.prev_ctxt = cur_syntax_ctxt;
span_lint_and_sugg(
cx,
DBG_MACRO,
@ -112,4 +123,16 @@ impl LateLintPass<'_> for DbgMacro {
);
}
}
fn check_crate_post(&mut self, _: &LateContext<'_>) {
self.checked_dbg_call_site = FxHashSet::default();
}
}
fn is_in_test(cx: &LateContext<'_>, hir_id: HirId) -> bool {
is_in_test_function(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id)
}
fn first_dbg_macro_in_expansion(cx: &LateContext<'_>, span: Span) -> Option<MacroCall> {
macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id))
}

View file

@ -54,6 +54,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
@ -235,6 +236,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO,
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO,
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
@ -310,6 +312,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
crate::match_result_ok::MATCH_RESULT_OK_INFO,
@ -353,6 +356,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::CLONE_ON_COPY_INFO,
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::CONST_IS_EMPTY_INFO,
crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO,
@ -750,5 +754,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::write::WRITE_LITERAL_INFO,
crate::write::WRITE_WITH_NEWLINE_INFO,
crate::zero_div_zero::ZERO_DIVIDED_BY_ZERO_INFO,
crate::zero_repeat_side_effects::ZERO_REPEAT_SIDE_EFFECTS_INFO,
crate::zero_sized_map_values::ZERO_SIZED_MAP_VALUES_INFO,
];

View file

@ -8,7 +8,14 @@ use url::Url;
use crate::doc::DOC_MARKDOWN;
pub fn check(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span, code_level: isize) {
pub fn check(
cx: &LateContext<'_>,
valid_idents: &FxHashSet<String>,
text: &str,
span: Span,
code_level: isize,
blockquote_level: isize,
) {
for orig_word in text.split(|c: char| c.is_whitespace() || c == '\'') {
// Trim punctuation as in `some comment (see foo::bar).`
// ^^
@ -46,11 +53,11 @@ pub fn check(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str,
span.parent(),
);
check_word(cx, word, span, code_level);
check_word(cx, word, span, code_level, blockquote_level);
}
}
fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize) {
fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, blockquote_level: isize) {
/// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
/// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
/// letter (`NASA` is ok).
@ -97,7 +104,9 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize) {
}
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
if code_level > 0 || (has_underscore(word) && has_hyphen(word)) {
//
// We also assume that backticks are not necessary if inside a quote. (Issue #10262)
if code_level > 0 || blockquote_level > 0 || (has_underscore(word) && has_hyphen(word)) {
return;
}

View file

@ -7,14 +7,14 @@ use clippy_utils::{is_entrypoint_fn, method_chain_args};
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{AnonConst, Expr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
@ -538,7 +538,16 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
suspicious_doc_comments::check(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
let (fragments, _) = attrs_to_doc_fragments(
attrs.iter().filter_map(|attr| {
if in_external_macro(cx.sess(), attr.span) {
None
} else {
Some((attr, None))
}
}),
true,
);
let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| {
add_doc_fragment(&mut acc, fragment);
acc
@ -602,6 +611,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut text_to_check: Vec<(CowStr<'_>, Range<usize>, isize)> = Vec::new();
let mut paragraph_range = 0..0;
let mut code_level = 0;
let mut blockquote_level = 0;
for (event, range) in events {
match event {
@ -610,8 +620,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
code_level += 1;
} else if tag.starts_with("</code") {
code_level -= 1;
} else if tag.starts_with("<blockquote") || tag.starts_with("<q") {
blockquote_level += 1;
} else if tag.starts_with("</blockquote") || tag.starts_with("</q") {
blockquote_level -= 1;
}
},
Start(BlockQuote) => blockquote_level += 1,
End(BlockQuote) => blockquote_level -= 1,
Start(CodeBlock(ref kind)) => {
in_code = true;
if let CodeBlockKind::Fenced(lang) = kind {
@ -663,7 +679,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
} else {
for (text, range, assoc_code_level) in text_to_check {
if let Some(span) = fragments.span(cx, range) {
markdown::check(cx, valid_idents, &text, span, assoc_code_level);
markdown::check(cx, valid_idents, &text, span, assoc_code_level, blockquote_level);
}
}
}

View file

@ -49,24 +49,22 @@ declare_clippy_lint! {
declare_lint_pass!(ElseIfWithoutElse => [ELSE_IF_WITHOUT_ELSE]);
impl EarlyLintPass for ElseIfWithoutElse {
fn check_expr(&mut self, cx: &EarlyContext<'_>, mut item: &Expr) {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if in_external_macro(cx.sess(), item.span) {
return;
}
while let ExprKind::If(_, _, Some(ref els)) = item.kind {
if let ExprKind::If(_, _, None) = els.kind {
span_lint_and_help(
cx,
ELSE_IF_WITHOUT_ELSE,
els.span,
"`if` expression with an `else if`, but without a final `else`",
None,
"add an `else` block here",
);
}
item = els;
if let ExprKind::If(_, _, Some(ref els)) = item.kind
&& let ExprKind::If(_, _, None) = els.kind
{
span_lint_and_help(
cx,
ELSE_IF_WITHOUT_ELSE,
els.span,
"`if` expression with an `else if`, but without a final `else`",
None,
"add an `else` block here",
);
}
}
}

View file

@ -358,7 +358,7 @@ struct InsertSearcher<'cx, 'tcx> {
can_use_entry: bool,
/// Whether this expression is the final expression in this code path. This may be a statement.
in_tail_pos: bool,
// Is this expression a single insert. A slightly better suggestion can be made in this case.
/// Is this expression a single insert. A slightly better suggestion can be made in this case.
is_single_insert: bool,
/// If the visitor has seen the map being used.
is_map_used: bool,
@ -431,6 +431,9 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
self.is_single_insert = false;
self.visit_expr(e);
}
if let Some(els) = &l.els {
self.visit_block(els);
}
},
StmtKind::Item(_) => {
self.allow_insert_closure &= !self.in_tail_pos;

View file

@ -181,6 +181,9 @@ fn convert_to_from(
let from = snippet_opt(cx, self_ty.span)?;
let into = snippet_opt(cx, target_ty.span)?;
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
.then_some(String::from("Self"))
.unwrap_or_default();
let mut suggestions = vec![
// impl Into<T> for U -> impl From<T> for U
// ~~~~ ~~~~
@ -197,13 +200,10 @@ fn convert_to_from(
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {
// fn into(self) -> T -> fn into(self) -> Self
// ~ ~~~~
suggestions.push((sig.decl.output.span(), String::from("Self")));
}
(sig.decl.output.span(), return_type),
];
let mut finder = SelfFinder {
cx,

View file

@ -250,7 +250,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
/// A `Result` is at least as large as the `Err`-variant. While we
/// expect that variant to be seldomly used, the compiler needs to reserve
/// expect that variant to be seldom used, the compiler needs to reserve
/// and move that much memory every single time.
/// Furthermore, errors are often simply passed up the call-stack, making
/// use of the `?`-operator and its type-conversion mechanics. If the

View file

@ -0,0 +1,50 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::BinOpKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of division (/) and remainder (%) operations
/// when performed on any integer types using the default Div and Rem trait implementations.
///
/// ### Why is this bad?
/// In cryptographic contexts, division can result in timing sidechannel vulnerabilities,
/// and needs to be replaced with constant-time code instead (e.g. Barrett reduction).
///
/// ### Example
/// ```no_run
/// let my_div = 10 / 2;
/// ```
/// Use instead:
/// ```no_run
/// let my_div = 10 >> 1;
/// ```
#[clippy::version = "1.78.0"]
pub INTEGER_DIVISION_REMAINDER_USED,
restriction,
"use of disallowed default division and remainder operations"
}
declare_lint_pass!(IntegerDivisionRemainderUsed => [INTEGER_DIVISION_REMAINDER_USED]);
impl LateLintPass<'_> for IntegerDivisionRemainderUsed {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
&& let BinOpKind::Div | BinOpKind::Rem = op.node
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
&& let rhs_ty = cx.typeck_results().expr_ty(rhs)
&& let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind()
&& let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind()
{
span_lint(
cx,
INTEGER_DIVISION_REMAINDER_USED,
expr.span.source_callsite(),
&format!("use of {} has been disallowed in this context", op.node.as_str()),
);
}
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::path_to_local_id;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
@ -122,9 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(
span_lint_hir_and_then(
cx,
USELESS_LET_IF_SEQ,
local.hir_id,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {

View file

@ -172,6 +172,7 @@ mod init_numbered_fields;
mod inline_fn_without_body;
mod instant_subtraction;
mod int_plus_one;
mod integer_division_remainder_used;
mod invalid_upcast_comparisons;
mod item_name_repetitions;
mod items_after_statements;
@ -211,6 +212,7 @@ mod manual_retain;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod manual_unwrap_or_default;
mod map_unit_fn;
mod match_result_ok;
mod matches;
@ -373,6 +375,7 @@ mod visibility;
mod wildcard_imports;
mod write;
mod zero_div_zero;
mod zero_repeat_side_effects;
mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints`
@ -1119,7 +1122,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
store.register_late_pass(|_| Box::new(assigning_clones::AssigningClones));
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(msrv())));
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -273,7 +273,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
}
return false; // no need to walk further *on the variable*
},
Res::Def(DefKind::Static{..} | DefKind::Const, ..) => {
Res::Def(DefKind::Static { .. } | DefKind::Const, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,

View file

@ -255,9 +255,7 @@ fn never_loop_expr<'tcx>(
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
NeverLoopResult::Normal
},
InlineAsmOperand::Label { block } => {
never_loop_block(cx, block, local_labels, main_loop_id)
}
InlineAsmOperand::Label { block } => never_loop_block(cx, block, local_labels, main_loop_id),
})),
ExprKind::OffsetOf(_, _)
| ExprKind::Yield(_, _)

View file

@ -1,62 +1,41 @@
use super::UNUSED_ENUMERATE_INDEX;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::{pat_is_wild, sugg};
use clippy_utils::{match_def_path, pat_is_wild, sugg};
use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
/// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
let PatKind::Tuple([index, elem], _) = pat.kind else {
return;
};
let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind else {
return;
};
let ty = cx.typeck_results().expr_ty(arg);
if !pat_is_wild(cx, &index.kind, body) {
return;
///
/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) {
if let PatKind::Tuple([index, elem], _) = pat.kind
&& let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind
&& let ty = cx.typeck_results().expr_ty(arg)
&& pat_is_wild(cx, &index.kind, body)
&& let ty::Adt(base, _) = *ty.kind()
&& match_def_path(cx, base.did(), &clippy_utils::paths::CORE_ITER_ENUMERATE_STRUCT)
&& let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id)
&& match_def_path(cx, call_id, &clippy_utils::paths::CORE_ITER_ENUMERATE_METHOD)
{
span_lint_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
arg.span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter");
multispan_sugg(
diag,
"remove the `.enumerate()` call",
vec![
(pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()),
],
);
},
);
}
let name = match *ty.kind() {
ty::Adt(base, _substs) => cx.tcx.def_path_str(base.did()),
_ => return,
};
if name != "std::iter::Enumerate" && name != "core::iter::Enumerate" {
return;
}
let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) else {
return;
};
let call_name = cx.tcx.def_path_str(call_id);
if call_name != "std::iter::Iterator::enumerate" && call_name != "core::iter::Iterator::enumerate" {
return;
}
span_lint_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
arg.span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter");
multispan_sugg(
diag,
"remove the `.enumerate()` call",
vec![
(pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()),
],
);
},
);
}

View file

@ -101,7 +101,7 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static{..}, def_id) => {
Res::Def(DefKind::Static { .. }, def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},

View file

@ -2,7 +2,7 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
use clippy_utils::{match_def_path, paths, SpanlessEq};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@ -69,17 +69,16 @@ impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let Assign(left_expr, collect_expr, _) = &parent_expr.kind
if let Assign(left_expr, collect_expr, _) = &expr.kind
&& let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind
&& seg.args.is_none()
&& let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
{
check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_into_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
check_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
check_to_owned(cx, left_expr, target_expr, expr.span, &self.msrv);
}
}

View file

@ -0,0 +1,181 @@
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_default_equivalent;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
declare_clippy_lint! {
/// ### What it does
/// Checks if a `match` or `if let` expression can be simplified using
/// `.unwrap_or_default()`.
///
/// ### Why is this bad?
/// It can be done in one call with `.unwrap_or_default()`.
///
/// ### Example
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = match x {
/// Some(v) => v,
/// None => String::new(),
/// };
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = if let Some(v) = x {
/// v
/// } else {
/// Vec::new()
/// };
/// ```
/// Use instead:
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = x.unwrap_or_default();
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = x.unwrap_or_default();
/// ```
#[clippy::version = "1.78.0"]
pub MANUAL_UNWRAP_OR_DEFAULT,
suspicious,
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
}
declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]);
fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
{
let mut bindings = Vec::new();
pat.each_binding(|_, id, _, _| bindings.push(id));
if let &[id] = bindings.as_slice() {
Some(id)
} else {
None
}
} else {
None
}
}
fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let PatKind::Path(QPath::Resolved(_, path)) = arm.pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::Wild = arm.pat.kind {
// We consider that the `Some` check will filter it out if it's not right.
Some(arm.body)
} else {
None
}
}
fn get_some_and_none_bodies<'tcx>(
cx: &LateContext<'tcx>,
arm1: &'tcx Arm<'tcx>,
arm2: &'tcx Arm<'tcx>,
) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> {
if let Some(binding_id) = get_some(cx, arm1.pat)
&& let Some(body_none) = get_none(cx, arm2)
{
Some(((arm1.body, binding_id), body_none))
} else if let Some(binding_id) = get_some(cx, arm2.pat)
&& let Some(body_none) = get_none(cx, arm1)
{
Some(((arm2.body, binding_id), body_none))
} else {
None
}
}
fn handle_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
let ExprKind::Match(match_expr, [arm1, arm2], MatchSource::Normal | MatchSource::ForLoopDesugar) = expr.kind else {
return false;
};
// We don't want conditions on the arms to simplify things.
if arm1.guard.is_none()
&& arm2.guard.is_none()
// We check that the returned type implements the `Default` trait.
&& let match_ty = cx.typeck_results().expr_ty(expr)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, match_ty, default_trait_id, &[])
// We now get the bodies for both the `Some` and `None` arms.
&& let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2)
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
&& let ExprKind::Path(QPath::Resolved(_, path)) = body_some.peel_blocks().kind
&& let Res::Local(local_id) = path.res
&& local_id == binding_id
// We now check the `None` arm is calling a method equivalent to `Default::default`.
&& let body_none = body_none.peel_blocks()
&& is_default_equivalent(cx, body_none)
&& let Some(match_expr_snippet) = snippet_opt(cx, match_expr.span)
{
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
"match can be simplified with `.unwrap_or_default()`",
"replace it with",
format!("{match_expr_snippet}.unwrap_or_default()"),
Applicability::MachineApplicable,
);
}
true
}
fn handle_if_let<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::If(cond, if_block, Some(else_expr)) = expr.kind
&& let ExprKind::Let(let_) = cond.kind
&& let ExprKind::Block(_, _) = else_expr.kind
// We check that the returned type implements the `Default` trait.
&& let match_ty = cx.typeck_results().expr_ty(expr)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, match_ty, default_trait_id, &[])
&& let Some(binding_id) = get_some(cx, let_.pat)
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
&& let ExprKind::Path(QPath::Resolved(_, path)) = if_block.peel_blocks().kind
&& let Res::Local(local_id) = path.res
&& local_id == binding_id
// We now check the `None` arm is calling a method equivalent to `Default::default`.
&& let body_else = else_expr.peel_blocks()
&& is_default_equivalent(cx, body_else)
&& let Some(if_let_expr_snippet) = snippet_opt(cx, let_.init.span)
{
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
"if let can be simplified with `.unwrap_or_default()`",
"replace it with",
format!("{if_let_expr_snippet}.unwrap_or_default()"),
Applicability::MachineApplicable,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() {
return;
}
if !handle_match(cx, expr) {
handle_if_let(cx, expr);
}
}
}

View file

@ -55,23 +55,15 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
};
let ty = cx.typeck_results().expr_ty(ex);
if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
check_single_pattern(cx, ex, arms, expr, els);
check_opt_like(cx, ex, arms, expr, ty, els);
if (*ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id)) &&
(check_single_pattern(arms) || check_opt_like(cx, arms, ty)) {
report_single_pattern(cx, ex, arms, expr, els);
}
}
}
fn check_single_pattern(
cx: &LateContext<'_>,
ex: &Expr<'_>,
arms: &[Arm<'_>],
expr: &Expr<'_>,
els: Option<&Expr<'_>>,
) {
if is_wild(arms[1].pat) {
report_single_pattern(cx, ex, arms, expr, els);
}
fn check_single_pattern(arms: &[Arm<'_>]) -> bool {
is_wild(arms[1].pat)
}
fn report_single_pattern(
@ -140,19 +132,10 @@ fn report_single_pattern(
span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);
}
fn check_opt_like<'a>(
cx: &LateContext<'a>,
ex: &Expr<'_>,
arms: &[Arm<'_>],
expr: &Expr<'_>,
ty: Ty<'a>,
els: Option<&Expr<'_>>,
) {
fn check_opt_like<'a>(cx: &LateContext<'a>, arms: &[Arm<'_>], ty: Ty<'a>) -> bool {
// We don't want to lint if the second arm contains an enum which could
// have more variants in the future.
if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
report_single_pattern(cx, ex, arms, expr, els);
}
form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat)
}
/// Returns `true` if all of the types in the pattern are enums which we know

View file

@ -91,7 +91,7 @@ pub(super) fn check<'tcx>(
},
hir::ExprKind::Path(ref p) => matches!(
cx.qpath_res(p, arg.hir_id),
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static{..}, _)
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _)
),
_ => false,
}

View file

@ -0,0 +1,49 @@
use clippy_utils::consts::constant_is_empty;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{find_binding_init, path_to_local};
use rustc_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use super::CONST_IS_EMPTY;
/// Expression whose initialization depend on a constant conditioned by a `#[cfg(…)]` directive will
/// not trigger the lint.
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
return;
}
let init_expr = expr_or_init(cx, receiver);
if !receiver.span.eq_ctxt(init_expr.span) {
return;
}
if let Some(init_is_empty) = constant_is_empty(cx, init_expr) {
span_lint(
cx,
CONST_IS_EMPTY,
expr.span,
&format!("this expression always evaluates to {init_is_empty:?}"),
);
}
}
fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx
.hir()
.parent_id_iter(id)
.any(|id| cx.tcx.hir().attrs(id).iter().any(|attr| attr.has_name(sym::cfg)))
}
/// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization
/// value depends on a `#[cfg(…)]` directive.
fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
while let Some(init) = path_to_local(expr)
.and_then(|id| find_binding_init(cx, id))
.filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
.filter(|init| !is_under_cfg(cx, init.hir_id))
{
expr = init;
}
expr
}

View file

@ -1,10 +1,10 @@
use super::utils::derefs_to_slice;
use crate::methods::iter_nth_zero;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::get_type_diagnostic_name;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use rustc_span::Span;
use super::ITER_NTH;
@ -12,28 +12,33 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
iter_recv: &'tcx hir::Expr<'tcx>,
nth_recv: &hir::Expr<'_>,
nth_arg: &hir::Expr<'_>,
is_mut: bool,
) {
let mut_str = if is_mut { "_mut" } else { "" };
let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() {
"slice"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::Vec) {
"`Vec`"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::VecDeque) {
"`VecDeque`"
} else {
iter_nth_zero::check(cx, expr, nth_recv, nth_arg);
return; // caller is not a type that we want to lint
iter_method: &str,
iter_span: Span,
nth_span: Span,
) -> bool {
let caller_type = match get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(iter_recv).peel_refs()) {
Some(sym::Vec) => "`Vec`",
Some(sym::VecDeque) => "`VecDeque`",
_ if cx.typeck_results().expr_ty_adjusted(iter_recv).peel_refs().is_slice() => "slice",
// caller is not a type that we want to lint
_ => return false,
};
span_lint_and_help(
span_lint_and_then(
cx,
ITER_NTH,
expr.span,
&format!("called `.iter{mut_str}().nth()` on a {caller_type}"),
None,
&format!("calling `.get{mut_str}()` is both faster and more readable"),
&format!("called `.{iter_method}().nth()` on a {caller_type}"),
|diag| {
let get_method = if iter_method == "iter_mut" { "get_mut" } else { "get" };
diag.span_suggestion_verbose(
iter_span.to(nth_span),
format!("`{get_method}` is equivalent but more concise"),
get_method,
Applicability::MachineApplicable,
);
},
);
true
}

View file

@ -86,8 +86,11 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
}
}
},
hir::ExprKind::Call(call, [_]) => {
if let hir::ExprKind::Path(qpath) = call.kind {
hir::ExprKind::Call(call, args) => {
if let hir::ExprKind::Path(qpath) = call.kind
&& let [arg] = args
&& ident_eq(name, arg)
{
handle_path(cx, call, &qpath, e, recv);
}
},
@ -118,7 +121,9 @@ fn handle_path(
if let ty::Adt(_, args) = cx.typeck_results().expr_ty(recv).kind()
&& let args = args.as_slice()
&& let Some(ty) = args.iter().find_map(|generic_arg| generic_arg.as_type())
&& ty.is_ref()
&& let ty::Ref(_, ty, Mutability::Not) = ty.kind()
&& let ty::FnDef(_, lst) = cx.typeck_results().expr_ty(arg).kind()
&& lst.iter().all(|l| l.as_type() == Some(*ty))
{
lint_path(cx, e.span, recv.span, is_copy(cx, ty.peel_refs()));
}

View file

@ -36,6 +36,7 @@ mod inefficient_to_string;
mod inspect_for_each;
mod into_iter_on_ref;
mod is_digit_ascii_radix;
mod is_empty;
mod iter_cloned_collect;
mod iter_count;
mod iter_filter;
@ -118,6 +119,7 @@ mod unnecessary_literal_unwrap;
mod unnecessary_result_map_or_else;
mod unnecessary_sort_by;
mod unnecessary_to_owned;
mod unused_enumerate_index;
mod unwrap_expect_used;
mod useless_asref;
mod utils;
@ -1235,12 +1237,11 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.iter().nth()` (and the related
/// `.iter_mut().nth()`) on standard library types with *O*(1) element access.
/// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have
/// equivalent `.get()`/`.get_mut()` methods.
///
/// ### Why is this bad?
/// `.get()` and `.get_mut()` are more efficient and more
/// readable.
/// `.get()` and `.get_mut()` are equivalent but more concise.
///
/// ### Example
/// ```no_run
@ -1256,7 +1257,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub ITER_NTH,
perf,
style,
"using `.iter().nth()` on a standard library type with O(1) element access"
}
@ -2848,7 +2849,7 @@ declare_clippy_lint! {
/// the file is created from scratch, or ensure `truncate` is
/// called so that the truncation behaviour is explicit. `truncate(true)`
/// will ensure the file is entirely overwritten with new data, whereas
/// `truncate(false)` will explicitely keep the default behavior.
/// `truncate(false)` will explicitly keep the default behavior.
///
/// ### Example
/// ```rust,no_run
@ -2862,7 +2863,7 @@ declare_clippy_lint! {
///
/// OpenOptions::new().create(true).truncate(true);
/// ```
#[clippy::version = "1.75.0"]
#[clippy::version = "1.77.0"]
pub SUSPICIOUS_OPEN_OPTIONS,
suspicious,
"suspicious combination of options for opening a file"
@ -3182,8 +3183,8 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
///
/// Checks an argument of `seek` method of `Seek` trait
/// and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
/// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`,
/// and if it is, suggests using `stream_position` instead.
///
/// ### Why is this bad?
///
@ -3590,7 +3591,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.73.0"]
pub READONLY_WRITE_LOCK,
nursery,
perf,
"acquiring a write lock when a read lock would work"
}
@ -3816,7 +3817,7 @@ declare_clippy_lint! {
/// ```no_run
/// let _ = std::iter::empty::<Result<i32, ()>>().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub RESULT_FILTER_MAP,
complexity,
"filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation"
@ -3825,7 +3826,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call.
/// This lint will require additional changes to the follow-up calls as it appects the type.
/// This lint will require additional changes to the follow-up calls as it affects the type.
///
/// ### Why is this bad?
/// This pattern is often followed by manual unwrapping of the `Option`. The simplification
@ -3842,7 +3843,7 @@ declare_clippy_lint! {
/// // example code which does not raise clippy warning
/// vec![Some(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub ITER_FILTER_IS_SOME,
pedantic,
"filtering an iterator over `Option`s for `Some` can be achieved with `flatten`"
@ -3851,7 +3852,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call.
/// This lint will require additional changes to the follow-up calls as it appects the type.
/// This lint will require additional changes to the follow-up calls as it affects the type.
///
/// ### Why is this bad?
/// This pattern is often followed by manual unwrapping of `Result`. The simplification
@ -3868,7 +3869,7 @@ declare_clippy_lint! {
/// // example code which does not raise clippy warning
/// vec![Ok::<i32, String>(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub ITER_FILTER_IS_OK,
pedantic,
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
@ -3895,7 +3896,7 @@ declare_clippy_lint! {
/// option.is_some_and(|a| a > 10);
/// result.is_ok_and(|a| a > 10);
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub MANUAL_IS_VARIANT_AND,
pedantic,
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
@ -3925,7 +3926,7 @@ declare_clippy_lint! {
/// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is
/// valid.
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub STR_SPLIT_AT_NEWLINE,
pedantic,
"splitting a trimmed string at hard-coded newlines"
@ -4044,6 +4045,31 @@ declare_clippy_lint! {
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
}
declare_clippy_lint! {
/// ### What it does
/// It identifies calls to `.is_empty()` on constant values.
///
/// ### Why is this bad?
/// String literals and constant values are known at compile time. Checking if they
/// are empty will always return the same value. This might not be the intention of
/// the expression.
///
/// ### Example
/// ```no_run
/// let value = "";
/// if value.is_empty() {
/// println!("the string is empty");
/// }
/// ```
/// Use instead:
/// ```no_run
/// println!("the string is empty");
/// ```
#[clippy::version = "1.78.0"]
pub CONST_IS_EMPTY,
suspicious,
"is_empty() called on strings known at compile time"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4092,6 +4118,7 @@ impl_lint_pass!(Methods => [
CLONE_ON_COPY,
CLONE_ON_REF_PTR,
COLLAPSIBLE_STR_REPLACE,
CONST_IS_EMPTY,
ITER_OVEREAGER_CLONED,
CLONED_INSTEAD_OF_COPIED,
FLAT_MAP_OPTION,
@ -4403,6 +4430,7 @@ impl Methods {
zst_offset::check(cx, expr, recv);
},
("all", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
@ -4421,23 +4449,26 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
("any", [arg]) => match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
Some(("chars", recv, _, _, _))
if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
},
_ => {},
("any", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
Some(("chars", recv, _, _, _))
if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
},
_ => {},
}
},
("arg", [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
@ -4445,7 +4476,7 @@ impl Methods {
("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name);
},
("as_bytes" | "is_empty", []) => {
("as_bytes", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
@ -4570,14 +4601,17 @@ impl Methods {
}
},
("filter_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_bool_then::check(cx, expr, arg, call_span);
filter_map_identity::check(cx, expr, arg, span);
},
("find_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
},
("flat_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);
},
@ -4599,17 +4633,20 @@ impl Methods {
manual_try_fold::check(cx, expr, init, acc, call_span, &self.msrv);
unnecessary_fold::check(cx, expr, init, acc, span);
},
("for_each", [arg]) => match method_call(recv) {
Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
_ => {},
("for_each", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
match method_call(recv) {
Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
_ => {},
}
},
("get", [arg]) => {
get_first::check(cx, expr, recv, arg);
@ -4619,6 +4656,12 @@ impl Methods {
("hash", [arg]) => {
unit_hash::check(cx, expr, recv, arg);
},
("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
is_empty::check(cx, expr, recv);
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
@ -4650,6 +4693,7 @@ impl Methods {
},
(name @ ("map" | "map_err"), [m_arg]) => {
if name == "map" {
unused_enumerate_index::check(cx, expr, recv, m_arg);
map_clone::check(cx, expr, recv, m_arg, &self.msrv);
match method_call(recv) {
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
@ -4723,8 +4767,11 @@ impl Methods {
iter_overeager_cloned::Op::LaterCloned,
false,
),
Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
Some((iter_method @ ("iter" | "iter_mut"), iter_recv, [], iter_span, _)) => {
if !iter_nth::check(cx, expr, iter_recv, iter_method, iter_span, span) {
iter_nth_zero::check(cx, expr, recv, n_arg);
}
},
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
},
("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),

View file

@ -25,6 +25,7 @@ pub(super) fn check<'tcx>(
&& param1 == param2.as_str()
{
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
return;
}
if SpanlessEq::new(cx).eq_expr(arg1, arg2) {

View file

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

View file

@ -0,0 +1,135 @@
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_hir_and_then};
use clippy_utils::paths::{CORE_ITER_ENUMERATE_METHOD, CORE_ITER_ENUMERATE_STRUCT};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::{expr_or_init, is_trait_method, match_def_path, pat_is_wild};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::AdtDef;
use rustc_span::{sym, Span};
use crate::loops::UNUSED_ENUMERATE_INDEX;
/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops.
///
/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is
/// checked:
/// ```ignore
/// for (_, x) in some_iter.enumerate() {
/// // Index is ignored
/// }
/// ```
///
/// This `check` function checks for chained method calls constructs where we can detect that the
/// index is unused. Currently, this checks only for the following patterns:
/// ```ignore
/// some_iter.enumerate().map_function(|(_, x)| ..)
/// let x = some_iter.enumerate();
/// x.map_function(|(_, x)| ..)
/// ```
/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or
/// `map`.
///
/// # Preconditions
/// This function must be called not on the `enumerate` call expression itself, but on any of the
/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and
/// that the method call is one of the `std::iter::Iterator` trait.
///
/// * `call_expr`: The map function call expression
/// * `recv`: The receiver of the call
/// * `closure_arg`: The argument to the map function call containing the closure/function to apply
pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
let recv_ty = cx.typeck_results().expr_ty(recv);
if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
// If we call a method on a `std::iter::Enumerate` instance
&& match_def_path(cx, recv_ty_defid, &CORE_ITER_ENUMERATE_STRUCT)
// If we are calling a method of the `Iterator` trait
&& is_trait_method(cx, call_expr, sym::Iterator)
// And the map argument is a closure
&& let ExprKind::Closure(closure) = closure_arg.kind
&& let closure_body = cx.tcx.hir().body(closure.body)
// And that closure has one argument ...
&& let [closure_param] = closure_body.params
// .. which is a tuple of 2 elements
&& let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind
// And that the first element (the index) is either `_` or unused in the body
&& pat_is_wild(cx, &index.kind, closure_body)
// Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the
// first example below, `expr_or_init` would return `recv`.
// ```
// iter.enumerate().map(|(_, x)| x)
// ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate`
//
// let binding = iter.enumerate();
// ^^^^^^^^^^^^^^^^ `recv_init_expr`
// binding.map(|(_, x)| x)
// ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate`
// ```
&& let recv_init_expr = expr_or_init(cx, recv)
// Make sure the initializer is a method call. It may be that the `Enumerate` comes from something
// that we cannot control.
// This would for instance happen with:
// ```
// external_lib::some_function_returning_enumerate().map(|(_, x)| x)
// ```
&& let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind
&& let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id)
// Make sure the method call is `std::iter::Iterator::enumerate`.
&& match_def_path(cx, enumerate_defid, &CORE_ITER_ENUMERATE_METHOD)
{
// Check if the tuple type was explicit. It may be the type system _needs_ the type of the element
// that would be explicited in the closure.
let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) {
// We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`.
// Fallback to `..` if we fail getting either snippet.
Some(ty_span) => snippet_opt(cx, elem.span)
.and_then(|binding_name| snippet_opt(cx, ty_span).map(|ty_name| format!("{binding_name}: {ty_name}")))
.unwrap_or_else(|| "..".to_string()),
// Otherwise, we have no explicit type. We can replace with the binding name of the element.
None => snippet(cx, elem.span, "..").into_owned(),
};
// Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we
// can get from the `MethodCall`.
span_lint_hir_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
recv_init_expr.hir_id,
enumerate_span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
multispan_sugg_with_applicability(
diag,
"remove the `.enumerate()` call",
Applicability::MachineApplicable,
vec![
(closure_param.span, new_closure_param),
(
enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()),
String::new(),
),
],
);
},
);
}
}
/// Find the span of the explicit type of the element.
///
/// # Returns
/// If the tuple argument:
/// * Has no explicit type, returns `None`
/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None`
/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for
/// the element type.
fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option<Span> {
if let [tuple_ty] = fn_decl.inputs
&& let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind
&& !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer)
{
Some(elem_ty.span)
} else {
None
}
}

View file

@ -8,6 +8,7 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
@ -32,6 +33,13 @@ declare_clippy_lint! {
"detects missing documentation for private members"
}
macro_rules! note_prev_span_then_ret {
($prev_span:expr, $span:expr) => {{
$prev_span = Some($span);
return;
}};
}
pub struct MissingDoc {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
@ -39,6 +47,8 @@ pub struct MissingDoc {
/// Stack of whether #[doc(hidden)] is set
/// at each level which has lint attributes.
doc_hidden_stack: Vec<bool>,
/// Used to keep tracking of the previous item, field or variants etc, to get the search span.
prev_span: Option<Span>,
}
impl Default for MissingDoc {
@ -54,6 +64,7 @@ impl MissingDoc {
Self {
crate_items_only,
doc_hidden_stack: vec![false],
prev_span: None,
}
}
@ -108,7 +119,8 @@ impl MissingDoc {
let has_doc = attrs
.iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()))
|| matches!(self.search_span(sp), Some(span) if span_to_snippet_contains_docs(cx, span));
if !has_doc {
span_lint(
@ -119,6 +131,32 @@ impl MissingDoc {
);
}
}
/// Return a span to search for doc comments manually.
///
/// # Example
/// ```ignore
/// fn foo() { ... }
/// ^^^^^^^^^^^^^^^^ prev_span
/// ↑
/// | search_span |
/// ↓
/// fn bar() { ... }
/// ^^^^^^^^^^^^^^^^ cur_span
/// ```
fn search_span(&self, cur_span: Span) -> Option<Span> {
let prev_span = self.prev_span?;
let start_pos = if prev_span.contains(cur_span) {
// In case when the prev_span is an entire struct, or enum,
// and the current span is a field, or variant, we need to search from
// the starting pos of the previous span.
prev_span.lo()
} else {
prev_span.hi()
};
let search_span = cur_span.with_lo(start_pos).with_hi(cur_span.lo());
Some(search_span)
}
}
impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
@ -138,6 +176,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
self.check_missing_docs_attrs(cx, CRATE_DEF_ID, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate");
}
fn check_crate_post(&mut self, _: &LateContext<'tcx>) {
self.prev_span = None;
}
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
match it.kind {
hir::ItemKind::Fn(..) => {
@ -145,7 +187,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if it.ident.name == sym::main {
let at_root = cx.tcx.local_parent(it.owner_id.def_id) == CRATE_DEF_ID;
if at_root {
return;
note_prev_span_then_ret!(self.prev_span, it.span);
}
}
},
@ -164,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::GlobalAsm(..)
| hir::ItemKind::Impl { .. }
| hir::ItemKind::Use(..) => return,
| hir::ItemKind::Use(..) => note_prev_span_then_ret!(self.prev_span, it.span),
};
let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id());
@ -173,6 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, it) {
self.check_missing_docs_attrs(cx, it.owner_id.def_id, attrs, it.span, article, desc);
}
self.prev_span = Some(it.span);
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
@ -182,16 +225,17 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, trait_item) {
self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, attrs, trait_item.span, article, desc);
}
self.prev_span = Some(trait_item.span);
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
// If the method is an impl for a trait, don't doc.
if let Some(cid) = cx.tcx.associated_item(impl_item.owner_id).impl_container(cx.tcx) {
if cx.tcx.impl_trait_ref(cid).is_some() {
return;
note_prev_span_then_ret!(self.prev_span, impl_item.span);
}
} else {
return;
note_prev_span_then_ret!(self.prev_span, impl_item.span);
}
let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id());
@ -199,6 +243,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, impl_item) {
self.check_missing_docs_attrs(cx, impl_item.owner_id.def_id, attrs, impl_item.span, article, desc);
}
self.prev_span = Some(impl_item.span);
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
@ -208,6 +253,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
}
self.prev_span = Some(sf.span);
}
fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
@ -215,5 +261,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, v) {
self.check_missing_docs_attrs(cx, v.def_id, attrs, v.span, "a", "variant");
}
self.prev_span = Some(v.span);
}
}
fn span_to_snippet_contains_docs(cx: &LateContext<'_>, search_span: Span) -> bool {
let Some(snippet) = snippet_opt(cx, search_span) else {
return false;
};
snippet.lines().rev().any(|line| line.trim().starts_with("///"))
}

View file

@ -109,7 +109,14 @@ fn collect_unsafe_exprs<'tcx>(
ExprKind::Path(QPath::Resolved(
_,
hir::Path {
res: Res::Def(DefKind::Static{mutability:Mutability::Mut, ..}, _),
res:
Res::Def(
DefKind::Static {
mutability: Mutability::Mut,
..
},
_,
),
..
},
)) => {
@ -149,7 +156,13 @@ fn collect_unsafe_exprs<'tcx>(
ExprKind::Path(QPath::Resolved(
_,
hir::Path {
res: Res::Def(DefKind::Static{mutability:Mutability::Mut, ..}, _),
res: Res::Def(
DefKind::Static {
mutability: Mutability::Mut,
..
},
_
),
..
}
))

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::{span_lint, span_lint_hir};
use clippy_utils::higher;
use rustc_hir as hir;
use rustc_hir::intravisit;
@ -35,9 +35,34 @@ impl<'tcx> LateLintPass<'tcx> for MutMut {
}
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_>) {
use rustc_hir::intravisit::Visitor;
if in_external_macro(cx.sess(), ty.span) {
return;
}
MutVisitor { cx }.visit_ty(ty);
if let hir::TyKind::Ref(
_,
hir::MutTy {
ty: pty,
mutbl: hir::Mutability::Mut,
},
) = ty.kind
{
if let hir::TyKind::Ref(
_,
hir::MutTy {
mutbl: hir::Mutability::Mut,
..
},
) = pty.kind
{
span_lint(
cx,
MUT_MUT,
ty.span,
"generally you want to avoid `&mut &mut _` if possible",
);
}
}
}
}
@ -62,17 +87,19 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
intravisit::walk_expr(self, body);
} else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
span_lint(
span_lint_hir(
self.cx,
MUT_MUT,
expr.hir_id,
expr.span,
"generally you want to avoid `&mut &mut _` if possible",
);
} else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() {
if ty.peel_refs().is_sized(self.cx.tcx, self.cx.param_env) {
span_lint(
span_lint_hir(
self.cx,
MUT_MUT,
expr.hir_id,
expr.span,
"this expression mutably borrows a mutable reference. Consider reborrowing",
);
@ -80,37 +107,4 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
}
}
}
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
if in_external_macro(self.cx.sess(), ty.span) {
return;
}
if let hir::TyKind::Ref(
_,
hir::MutTy {
ty: pty,
mutbl: hir::Mutability::Mut,
},
) = ty.kind
{
if let hir::TyKind::Ref(
_,
hir::MutTy {
mutbl: hir::Mutability::Mut,
..
},
) = pty.kind
{
span_lint(
self.cx,
MUT_MUT,
ty.span,
"generally you want to avoid `&mut &mut _` if possible",
);
}
}
intravisit::walk_ty(self, ty);
}
}

View file

@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, '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

@ -290,14 +290,21 @@ impl NonCopyConst {
promoted: None,
};
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, rustc_span::DUMMY_SP);
let result = cx
.tcx
.const_eval_global_id_for_typeck(param_env, cid, rustc_span::DUMMY_SP);
self.is_value_unfrozen_raw(cx, result, ty)
}
fn is_value_unfrozen_expr<'tcx>(&self, cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
let args = cx.typeck_results().node_args(hir_id);
let result = Self::const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), rustc_span::DUMMY_SP);
let result = Self::const_eval_resolve(
cx.tcx,
cx.param_env,
ty::UnevaluatedConst::new(def_id, args),
rustc_span::DUMMY_SP,
);
self.is_value_unfrozen_raw(cx, result, ty)
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::get_enclosing_block;
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
use clippy_utils::source::snippet;
@ -77,36 +77,52 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
if let Some(expr) = visitor.read_zero_expr {
let applicability = Applicability::MaybeIncorrect;
match vec_init_kind {
VecInitKind::WithConstCapacity(len) => {
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
applicability,
);
},
VecInitKind::WithConstCapacity(len) => span_lint_hir_and_then(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
|diag| {
diag.span_suggestion(
expr.span,
"try",
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
applicability,
);
},
),
VecInitKind::WithExprCapacity(hir_id) => {
let e = cx.tcx.hir().expect_expr(hir_id);
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!(
"{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, expr.span, "..")
),
applicability,
|diag| {
diag.span_suggestion(
expr.span,
"try",
format!(
"{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, expr.span, "..")
),
applicability,
);
},
);
},
_ => {
span_lint(cx, READ_ZERO_BYTE_VEC, expr.span, "reading zero byte data to `Vec`");
span_lint_hir(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
);
},
}
}

View file

@ -1,5 +1,5 @@
use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir};
use clippy_utils::get_parent_expr;
use clippy_utils::sugg::Sugg;
use hir::Param;
@ -273,9 +273,10 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
&& ident == path.segments[0].ident
&& count_closure_usage(cx, block, path) == 1
{
span_lint(
span_lint_hir(
cx,
REDUNDANT_CLOSURE_CALL,
second.hir_id,
second.span,
"closure called just once immediately after it was declared",
);

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
@ -380,7 +380,7 @@ fn check_final_expr<'tcx>(
return;
}
emit_return_lint(cx, ret_span, semi_spans, &replacement);
emit_return_lint(cx, ret_span, semi_spans, &replacement, expr.hir_id);
},
ExprKind::If(_, then, else_clause_opt) => {
check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
@ -415,18 +415,31 @@ fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool {
contains_if(expr, false)
}
fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>, replacement: &RetReplacement<'_>) {
fn emit_return_lint(
cx: &LateContext<'_>,
ret_span: Span,
semi_spans: Vec<Span>,
replacement: &RetReplacement<'_>,
at: HirId,
) {
if ret_span.from_expansion() {
return;
}
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
let suggestions = std::iter::once((ret_span, replacement.to_string()))
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
.collect();
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
at,
ret_span,
"unneeded `return` statement",
|diag| {
let suggestions = std::iter::once((ret_span, replacement.to_string()))
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
.collect();
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
});
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
},
);
}
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
@ -452,8 +465,8 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
// Go backwards while encountering whitespace and extend the given Span to that point.
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
let ws = [' ', '\t', '\n'];
if let Some(non_ws_pos) = prev_source.rfind(|c| !ws.contains(&c)) {
let ws = [b' ', b'\t', b'\n'];
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
let len = prev_source.len() - non_ws_pos - 1;
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
}

View file

@ -109,6 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
sym::core => (STD_INSTEAD_OF_CORE, "std", "core"),
sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"),
_ => {
self.prev_span = first_segment.ident.span;
return;
},
},
@ -116,6 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
if cx.tcx.crate_name(def_id.krate) == sym::core {
(ALLOC_INSTEAD_OF_CORE, "alloc", "core")
} else {
self.prev_span = first_segment.ident.span;
return;
}
},

View file

@ -37,7 +37,7 @@ declare_clippy_lint! {
/// static BUF: String = const { String::new() };
/// }
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
perf,
"suggest using `const` in `thread_local!` macro"

View file

@ -514,7 +514,7 @@ declare_clippy_lint! {
/// ^^^^ ^^ `bool::then` only executes the closure if the condition is true!
/// }
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub EAGER_TRANSMUTE,
correctness,
"eager evaluation of `transmute`"

View file

@ -392,6 +392,10 @@ impl<'tcx> LateLintPass<'tcx> for Types {
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &hir::FieldDef<'tcx>) {
if field.span.from_expansion() {
return;
}
let is_exported = cx.effective_visibilities.is_exported(field.def_id);
self.check_ty(

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id};
use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id};
use rustc_ast::BinOpKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
@ -19,11 +19,11 @@ use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
declare_clippy_lint! {
/// ### What it does
/// Checks that there isn't an infinite recursion in `PartialEq` trait
/// implementation.
/// Checks that there isn't an infinite recursion in trait
/// implementations.
///
/// ### Why is this bad?
/// This is a hard to find infinite recursion which will crashing any code
/// This is a hard to find infinite recursion that will crash any code.
/// using it.
///
/// ### Example
@ -42,7 +42,7 @@ declare_clippy_lint! {
/// Use instead:
///
/// In such cases, either use `#[derive(PartialEq)]` or don't implement it.
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub UNCONDITIONAL_RECURSION,
suspicious,
"detect unconditional recursion in some traits implementation"
@ -201,7 +201,6 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
}
}
#[allow(clippy::unnecessary_def_path)]
fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
let args = cx
.tcx
@ -224,7 +223,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
&& let Some(trait_) = impl_.of_trait
&& let Some(trait_def_id) = trait_.trait_def_id()
// The trait is `ToString`.
&& Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id)
{
let is_bad = match expr.kind {
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
@ -291,7 +290,6 @@ where
self.map
}
#[allow(clippy::unnecessary_def_path)]
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.found_default_call {
return;
@ -303,7 +301,7 @@ where
&& is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
&& let Some(method_def_id) = path_def_id(self.cx, f)
&& let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id)
&& Some(trait_def_id) == get_trait_def_id(self.cx, &["core", "default", "Default"])
&& self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
{
self.found_default_call = true;
span_error(self.cx, self.method_span, expr);
@ -312,10 +310,9 @@ where
}
impl UnconditionalRecursion {
#[allow(clippy::unnecessary_def_path)]
fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) {
if self.default_impl_for_type.is_empty()
&& let Some(default_trait_id) = get_trait_def_id(cx, &["core", "default", "Default"])
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
{
let impls = cx.tcx.trait_impls_of(default_trait_id);
for (ty, impl_def_ids) in impls.non_blanket_impls() {
@ -394,6 +391,34 @@ impl UnconditionalRecursion {
}
}
fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) {
let Some(sig) = cx
.typeck_results()
.liberated_fn_sigs()
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
else {
return;
};
// Check if we are calling `Into::into` where the node args match with our `From::from` signature:
// From::from signature: fn(S1) -> S2
// <S1 as Into<S2>>::into(s1), node_args=[S1, S2]
// If they do match, then it must mean that it is the blanket impl,
// which calls back into our `From::from` again (`Into` is not specializable).
// rustc's unconditional_recursion already catches calling `From::from` directly
if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr)
&& let [s1, s2] = **node_args
&& let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type())
&& let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
&& cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
&& get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
&& s1 == sig.inputs()[0]
&& s2 == sig.output()
{
span_error(cx, method_span, expr);
}
}
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
fn check_fn(
&mut self,
@ -410,10 +435,11 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
// Doesn't have a conditional return.
&& !has_conditional_return(body, expr)
{
if name.name == sym::eq || name.name == sym::ne {
check_partial_eq(cx, method_span, method_def_id, name, expr);
} else if name.name == sym::to_string {
check_to_string(cx, method_span, method_def_id, name, expr);
match name.name {
sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr),
sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr),
sym::from => check_from(cx, method_span, method_def_id, expr),
_ => {},
}
self.check_default_new(cx, decl, body, method_span, method_def_id);
}

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
use hir::{ExprKind, PatKind};
use hir::{ExprKind, HirId, PatKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -135,22 +135,22 @@ fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
&& is_ok_wild_or_dotdot_pattern(cx, pat)
&& let Some(op) = should_lint(cx, init) =>
{
emit_lint(cx, cond.span, op, &[pat.span]);
emit_lint(cx, cond.span, cond.hir_id, op, &[pat.span]);
},
// we will capture only the case where the match is Ok( ) or Err( )
// prefer to match the minimum possible, and expand later if needed
// to avoid false positives on something as used as this
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
emit_lint(cx, expr.span, expr.hir_id, op, &[arm1.pat.span]);
}
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
emit_lint(cx, expr.span, expr.hir_id, op, &[arm2.pat.span]);
}
},
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
_ if let Some(op) = should_lint(cx, expr) => {
emit_lint(cx, expr.span, op, &[]);
emit_lint(cx, expr.span, expr.hir_id, op, &[]);
},
_ => {},
};
@ -279,7 +279,7 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
}
}
fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
fn emit_lint(cx: &LateContext<'_>, span: Span, at: HirId, op: IoOp, wild_cards: &[Span]) {
let (msg, help) = match op {
IoOp::AsyncRead(false) => (
"read amount is not handled",
@ -301,7 +301,7 @@ fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
IoOp::SyncWrite(true) | IoOp::AsyncWrite(true) => ("written amount is not handled", None),
};
span_lint_and_then(cx, UNUSED_IO_AMOUNT, span, msg, |diag| {
span_lint_hir_and_then(cx, UNUSED_IO_AMOUNT, at, span, msg, |diag| {
if let Some(help_str) = help {
diag.help(help_str);
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, peel_ref_operators};
use rustc_ast::Mutability;
@ -79,13 +79,15 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable {
}
if !vis.found_peek_call {
span_lint_and_help(
span_lint_hir_and_then(
cx,
UNUSED_PEEKABLE,
local.hir_id,
ident.span,
"`peek` never called on `Peekable` iterator",
None,
"consider removing the call to `peekable`",
|diag| {
diag.help("consider removing the call to `peekable`");
},
);
}
}

View file

@ -8,11 +8,12 @@ use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{walk_inf, walk_ty, Visitor};
use rustc_hir::{
self as hir, Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind,
HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
self as hir, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl,
ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty as MiddleTy;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -95,10 +96,9 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
let stack_item = if let ItemKind::Impl(Impl { self_ty, generics, .. }) = item.kind
&& let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind
&& let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args
&& parameters.as_ref().map_or(true, |params| {
params.parenthesized == GenericArgsParentheses::No
&& !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
})
&& parameters
.as_ref()
.map_or(true, |params| params.parenthesized == GenericArgsParentheses::No)
&& !item.span.from_expansion()
&& !is_from_proc_macro(cx, item)
// expensive, should be last check
@ -226,7 +226,12 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
} else {
hir_ty_to_ty(cx.tcx, hir_ty)
}
&& same_type_and_consts(ty, cx.tcx.type_of(impl_id).instantiate_identity())
&& let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity()
&& same_type_and_consts(ty, impl_ty)
// Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that
// the lifetime parameters of `ty` are ellided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in
// which case we must still trigger the lint.
&& (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty))
{
span_lint(cx, hir_ty.span);
}
@ -318,3 +323,37 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
span_lint(cx, span);
}
}
/// Returns `true` if types `a` and `b` have the same lifetime parameters, otherwise returns
/// `false`.
///
/// This function does not check that types `a` and `b` are the same types.
fn same_lifetimes<'tcx>(a: MiddleTy<'tcx>, b: MiddleTy<'tcx>) -> bool {
use rustc_middle::ty::{Adt, GenericArgKind};
match (&a.kind(), &b.kind()) {
(&Adt(_, args_a), &Adt(_, args_b)) => {
args_a
.iter()
.zip(args_b.iter())
.all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) {
// TODO: Handle inferred lifetimes
(GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b,
(GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b),
_ => true,
})
},
_ => a == b,
}
}
/// Returns `true` if `ty` has no lifetime parameter, otherwise returns `false`.
fn has_no_lifetime(ty: MiddleTy<'_>) -> bool {
use rustc_middle::ty::{Adt, GenericArgKind};
match ty.kind() {
&Adt(_, args) => !args
.iter()
// TODO: Handle inferred lifetimes
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(..))),
_ => true,
}
}

View file

@ -925,7 +925,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
&& let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr))
&& match_type(self.cx, expr_ty, &paths::LINT)
{
if let hir::def::Res::Def(DefKind::Static(..), _) = path.res {
if let hir::def::Res::Def(DefKind::Static { .. }, _) = path.res {
let lint_name = last_path_segment(qpath).ident.name;
self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
} else if let Some(local) = get_parent_local(self.cx, expr) {

View file

@ -223,7 +223,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
None
}
},
Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
Res::Def(DefKind::Static { .. }, def_id) => read_mir_alloc_def_path(
cx,
cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
cx.tcx.type_of(def_id).instantiate_identity(),

View file

@ -213,7 +213,7 @@ fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
// Allow skipping imports containing user configured segments,
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
// segment matching need to be exact instead of using 'contains', in case user unintentionally put
// a single character in the config thus skipping most of the warnings.
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
}

View file

@ -0,0 +1,154 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ConstKind, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for array or vec initializations which call a function or method,
/// but which have a repeat count of zero.
///
/// ### Why is this bad?
/// Such an initialization, despite having a repeat length of 0, will still call the inner function.
/// This may not be obvious and as such there may be unintended side effects in code.
///
/// ### Example
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// let a = [side_effect(); 0];
/// ```
/// Use instead:
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// side_effect();
/// let a: [i32; 0] = [];
/// ```
#[clippy::version = "1.75.0"]
pub ZERO_REPEAT_SIDE_EFFECTS,
suspicious,
"usage of zero-sized initializations of arrays or vecs causing side effects"
}
declare_lint_pass!(ZeroRepeatSideEffects => [ZERO_REPEAT_SIDE_EFFECTS]);
impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
if let Some(args) = VecArgs::hir(cx, expr)
&& let VecArgs::Repeat(inner_expr, len) = args
&& let ExprKind::Lit(l) = len.kind
&& let LitKind::Int(i, _) = l.node
&& i.0 == 0
{
inner_check(cx, expr, inner_expr, true);
} else if let ExprKind::Repeat(inner_expr, _) = expr.kind
&& let ty::Array(_, cst) = cx.typeck_results().expr_ty(expr).kind()
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
&& element_count == 0
{
inner_check(cx, expr, inner_expr, false);
}
}
}
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if for_each_expr(inner_expr, |x| {
if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind {
std::ops::ControlFlow::Break(())
} else {
std::ops::ControlFlow::Continue(())
}
})
.is_some()
{
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
let return_type = cx.typeck_results().expr_ty(expr);
if let Node::Local(l) = parent_hir_node {
array_span_lint(
cx,
l.span,
inner_expr.span,
l.pat.span,
Some(return_type),
is_vec,
false,
);
} else if let Node::Expr(x) = parent_hir_node
&& let ExprKind::Assign(l, _, _) = x.kind
{
array_span_lint(cx, x.span, inner_expr.span, l.span, Some(return_type), is_vec, true);
} else {
span_lint_and_sugg(
cx,
ZERO_REPEAT_SIDE_EFFECTS,
expr.span.source_callsite(),
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
"consider using",
format!(
"{{ {}; {}[] as {return_type} }}",
snippet(cx, inner_expr.span.source_callsite(), ".."),
if is_vec { "vec!" } else { "" },
),
Applicability::Unspecified,
);
}
}
}
fn array_span_lint(
cx: &LateContext<'_>,
expr_span: Span,
func_call_span: Span,
variable_name_span: Span,
expr_ty: Option<Ty<'_>>,
is_vec: bool,
is_assign: bool,
) {
let has_ty = expr_ty.is_some();
span_lint_and_sugg(
cx,
ZERO_REPEAT_SIDE_EFFECTS,
expr_span.source_callsite(),
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
"consider using",
format!(
"{}; {}{}{} = {}[]{}{}",
snippet(cx, func_call_span.source_callsite(), ".."),
if has_ty && !is_assign { "let " } else { "" },
snippet(cx, variable_name_span.source_callsite(), ".."),
if let Some(ty) = expr_ty
&& !is_assign
{
format!(": {ty}")
} else {
String::new()
},
if is_vec { "vec!" } else { "" },
if let Some(ty) = expr_ty
&& is_assign
{
format!(" as {ty}")
} else {
String::new()
},
if is_assign { "" } else { ";" }
),
Applicability::Unspecified,
);
}