Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
dc0bb69e66
1053 changed files with 12006 additions and 10203 deletions
|
|
@ -2,8 +2,11 @@ use std::fmt;
|
|||
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, Lint};
|
||||
use rustc_ast::{InlineAsm, Item, ItemKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::asm::InlineAsmArch;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum AsmStyle {
|
||||
|
|
@ -31,8 +34,14 @@ impl std::ops::Not for AsmStyle {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) {
|
||||
if let ExprKind::InlineAsm(ref inline_asm) = expr.kind {
|
||||
fn check_asm_syntax(
|
||||
lint: &'static Lint,
|
||||
cx: &EarlyContext<'_>,
|
||||
inline_asm: &InlineAsm,
|
||||
span: Span,
|
||||
check_for: AsmStyle,
|
||||
) {
|
||||
if matches!(cx.sess().asm_arch, Some(InlineAsmArch::X86 | InlineAsmArch::X86_64)) {
|
||||
let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) {
|
||||
AsmStyle::Att
|
||||
} else {
|
||||
|
|
@ -43,7 +52,7 @@ fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr
|
|||
span_lint_and_help(
|
||||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
span,
|
||||
&format!("{style} x86 assembly syntax used"),
|
||||
None,
|
||||
&format!("use {} x86 assembly syntax", !style),
|
||||
|
|
@ -89,7 +98,15 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
|
|||
|
||||
impl EarlyLintPass for InlineAsmX86IntelSyntax {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel);
|
||||
if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
|
||||
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
|
||||
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +147,14 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]);
|
|||
|
||||
impl EarlyLintPass for InlineAsmX86AttSyntax {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att);
|
||||
if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
|
||||
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
|
||||
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
//! checks for attributes
|
||||
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{
|
||||
span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
|
||||
};
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::macros::{is_panic, macro_backtrace};
|
||||
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
|
||||
|
|
@ -433,6 +435,56 @@ declare_clippy_lint! {
|
|||
"prevent from misusing the wrong attr name"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for
|
||||
/// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with
|
||||
/// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This feature has been deprecated for years and shouldn't be used anymore.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(feature = "cargo-clippy")]
|
||||
/// struct Bar;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[cfg(clippy)]
|
||||
/// struct Bar;
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
suspicious,
|
||||
"usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]`
|
||||
/// and suggests to replace it with `#[allow(clippy::lint)]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// There is no reason to put clippy attributes behind a clippy `cfg` as they are not
|
||||
/// run by anything else than clippy.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![allow(clippy::deprecated_cfg_attr)]
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub UNNECESSARY_CLIPPY_CFG,
|
||||
suspicious,
|
||||
"usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Attributes => [
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
INLINE_ALWAYS,
|
||||
|
|
@ -794,6 +846,8 @@ impl_lint_pass!(EarlyAttributes => [
|
|||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
NON_MINIMAL_CFG,
|
||||
MAYBE_MISUSED_CFG,
|
||||
DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
]);
|
||||
|
||||
impl EarlyLintPass for EarlyAttributes {
|
||||
|
|
@ -803,6 +857,7 @@ impl EarlyLintPass for EarlyAttributes {
|
|||
|
||||
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
check_deprecated_cfg_attr(cx, attr, &self.msrv);
|
||||
check_deprecated_cfg(cx, attr);
|
||||
check_mismatched_target_os(cx, attr);
|
||||
check_minimal_cfg_condition(cx, attr);
|
||||
check_misused_cfg(cx, attr);
|
||||
|
|
@ -857,42 +912,151 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
|
|||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
|
||||
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check cfg_attr
|
||||
&& attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
// check for `rustfmt`
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
&& feature_item.has_name(sym::rustfmt)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
fn check_cargo_clippy_attr(cx: &EarlyContext<'_>, item: &rustc_ast::MetaItem) {
|
||||
if item.has_name(sym::feature) && item.value_str().is_some_and(|v| v.as_str() == "cargo-clippy") {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
item.span,
|
||||
"`feature = \"cargo-clippy\"` was replaced by `clippy`",
|
||||
"replace with",
|
||||
"clippy".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) {
|
||||
if let Some(ident) = attr.ident() {
|
||||
if ["any", "all", "not"].contains(&ident.name.as_str()) {
|
||||
let Some(list) = attr.meta_item_list() else { return };
|
||||
for item in list.iter().filter_map(|item| item.meta_item()) {
|
||||
check_deprecated_cfg_recursively(cx, item);
|
||||
}
|
||||
} else {
|
||||
check_cargo_clippy_attr(cx, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(list) = attr.meta_item_list()
|
||||
{
|
||||
for item in list.iter().filter_map(|item| item.meta_item()) {
|
||||
check_deprecated_cfg_recursively(cx, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
|
||||
// check cfg_attr
|
||||
if attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
{
|
||||
// check for `rustfmt`
|
||||
if feature_item.has_name(sym::rustfmt)
|
||||
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
check_deprecated_cfg_recursively(cx, feature_item);
|
||||
if let Some(behind_cfg_attr) = items[1].meta_item() {
|
||||
check_clippy_cfg_attr(cx, feature_item, behind_cfg_attr, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_clippy_cfg_attr(
|
||||
cx: &EarlyContext<'_>,
|
||||
cfg_attr: &rustc_ast::MetaItem,
|
||||
behind_cfg_attr: &rustc_ast::MetaItem,
|
||||
attr: &Attribute,
|
||||
) {
|
||||
if cfg_attr.has_name(sym::clippy)
|
||||
&& let Some(ident) = behind_cfg_attr.ident()
|
||||
// FIXME: replace with `from_symbol` once https://github.com/rust-lang/rust/pull/121230
|
||||
// is merged.
|
||||
&& Level::from_str(ident.name.as_str()).is_some()
|
||||
&& let Some(items) = behind_cfg_attr.meta_item_list()
|
||||
{
|
||||
let nb_items = items.len();
|
||||
let mut clippy_lints = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
if let Some(meta_item) = item.meta_item()
|
||||
&& let [part1, _] = meta_item.path.segments.as_slice()
|
||||
&& part1.ident.name == sym::clippy
|
||||
{
|
||||
clippy_lints.push(item.span());
|
||||
}
|
||||
}
|
||||
if clippy_lints.is_empty() {
|
||||
return;
|
||||
}
|
||||
if nb_items == clippy_lints.len() {
|
||||
if let Some(snippet) = snippet_opt(cx, behind_cfg_attr.span) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
attr.span,
|
||||
"no need to put clippy lints behind a `clippy` cfg",
|
||||
"replace with",
|
||||
format!(
|
||||
"#{}[{}]",
|
||||
if attr.style == AttrStyle::Inner { "!" } else { "" },
|
||||
snippet
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let snippet = clippy_lints
|
||||
.iter()
|
||||
.filter_map(|sp| snippet_opt(cx, *sp))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
clippy_lints,
|
||||
"no need to put clippy lints behind a `clippy` cfg",
|
||||
None,
|
||||
&format!(
|
||||
"write instead: `#{}[{}({})]`",
|
||||
if attr.style == AttrStyle::Inner { "!" } else { "" },
|
||||
ident.name,
|
||||
snippet
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
|||
use clippy_utils::source::snippet_block_with_applicability;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::visitors::{for_each_expr, Descend};
|
||||
use clippy_utils::{get_parent_expr, higher};
|
||||
use clippy_utils::{get_parent_expr, higher, is_from_proc_macro};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
|
||||
|
|
@ -13,7 +13,7 @@ use rustc_span::sym;
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `if` conditions that use blocks containing an
|
||||
/// Checks for `if` and `match` conditions that use blocks containing an
|
||||
/// expression, statements or conditions that use closures with blocks.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
|
|
@ -25,6 +25,11 @@ declare_clippy_lint! {
|
|||
/// if { true } { /* ... */ }
|
||||
///
|
||||
/// if { let x = somefunc(); x } { /* ... */ }
|
||||
///
|
||||
/// match { let e = somefunc(); e } {
|
||||
/// // ...
|
||||
/// # _ => {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
|
@ -34,6 +39,12 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// let res = { let x = somefunc(); x };
|
||||
/// if res { /* ... */ }
|
||||
///
|
||||
/// let res = { let e = somefunc(); e };
|
||||
/// match res {
|
||||
/// // ...
|
||||
/// # _ => {}
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.45.0"]
|
||||
pub BLOCKS_IN_CONDITIONS,
|
||||
|
|
@ -94,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
|
|||
}
|
||||
} else {
|
||||
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||
if span.from_expansion() || expr.span.from_expansion() {
|
||||
if span.from_expansion() || expr.span.from_expansion() || is_from_proc_macro(cx, cond) {
|
||||
return;
|
||||
}
|
||||
// move block higher
|
||||
|
|
|
|||
|
|
@ -85,7 +85,117 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
|
|||
) {
|
||||
NonminimalBoolVisitor { cx }.visit_body(body);
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
match expr.kind {
|
||||
ExprKind::Unary(UnOp::Not, sub) => check_inverted_condition(cx, expr.span, sub),
|
||||
// This check the case where an element in a boolean comparison is inverted, like:
|
||||
//
|
||||
// ```
|
||||
// let a = true;
|
||||
// !a == false;
|
||||
// ```
|
||||
ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) => {
|
||||
check_inverted_bool_in_condition(cx, expr.span, op.node, left, right);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
|
||||
match op {
|
||||
BinOpKind::Eq => Some("!="),
|
||||
BinOpKind::Ne => Some("=="),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
|
||||
match op {
|
||||
BinOpKind::Eq => Some("=="),
|
||||
BinOpKind::Ne => Some("!="),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inverted_condition(cx: &LateContext<'_>, expr_span: Span, sub_expr: &Expr<'_>) {
|
||||
if !expr_span.from_expansion()
|
||||
&& let ExprKind::Binary(op, left, right) = sub_expr.kind
|
||||
&& let Some(left) = snippet_opt(cx, left.span)
|
||||
&& let Some(right) = snippet_opt(cx, right.span)
|
||||
{
|
||||
let Some(op) = inverted_bin_op_eq_str(op.node) else {
|
||||
return;
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NONMINIMAL_BOOL,
|
||||
expr_span,
|
||||
"this boolean expression can be simplified",
|
||||
"try",
|
||||
format!("{left} {op} {right}",),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inverted_bool_in_condition(
|
||||
cx: &LateContext<'_>,
|
||||
expr_span: Span,
|
||||
op: BinOpKind,
|
||||
left: &Expr<'_>,
|
||||
right: &Expr<'_>,
|
||||
) {
|
||||
if expr_span.from_expansion()
|
||||
&& (!cx.typeck_results().node_types()[left.hir_id].is_bool()
|
||||
|| !cx.typeck_results().node_types()[right.hir_id].is_bool())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let suggestion = match (left.kind, right.kind) {
|
||||
(ExprKind::Unary(UnOp::Not, left_sub), ExprKind::Unary(UnOp::Not, right_sub)) => {
|
||||
let Some(left) = snippet_opt(cx, left_sub.span) else {
|
||||
return;
|
||||
};
|
||||
let Some(right) = snippet_opt(cx, right_sub.span) else {
|
||||
return;
|
||||
};
|
||||
let Some(op) = bin_op_eq_str(op) else { return };
|
||||
format!("{left} {op} {right}")
|
||||
},
|
||||
(ExprKind::Unary(UnOp::Not, left_sub), _) => {
|
||||
let Some(left) = snippet_opt(cx, left_sub.span) else {
|
||||
return;
|
||||
};
|
||||
let Some(right) = snippet_opt(cx, right.span) else {
|
||||
return;
|
||||
};
|
||||
let Some(op) = inverted_bin_op_eq_str(op) else { return };
|
||||
format!("{left} {op} {right}")
|
||||
},
|
||||
(_, ExprKind::Unary(UnOp::Not, right_sub)) => {
|
||||
let Some(left) = snippet_opt(cx, left.span) else { return };
|
||||
let Some(right) = snippet_opt(cx, right_sub.span) else {
|
||||
return;
|
||||
};
|
||||
let Some(op) = inverted_bin_op_eq_str(op) else { return };
|
||||
format!("{left} {op} {right}")
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NONMINIMAL_BOOL,
|
||||
expr_span,
|
||||
"this boolean expression can be simplified",
|
||||
"try",
|
||||
suggestion,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
struct NonminimalBoolVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,25 +101,22 @@ impl<'tcx> Visitor<'tcx> for InferVisitor {
|
|||
|
||||
fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Local(Local { ty: Some(ty), .. })) => {
|
||||
Node::Local(Local { ty: Some(ty), .. }) => {
|
||||
let mut v = InferVisitor::default();
|
||||
v.visit_ty(ty);
|
||||
!v.0
|
||||
},
|
||||
Some(
|
||||
Node::Expr(Expr {
|
||||
Node::Expr(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
})
|
||||
| Node::Block(Block {
|
||||
expr: Some(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
})
|
||||
| Node::Block(Block {
|
||||
expr:
|
||||
Some(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
..
|
||||
}) => {
|
||||
if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
|
||||
&& let Some(sig) = expr_sig(cx, path)
|
||||
&& let Some(input) = sig.input(index)
|
||||
|
|
|
|||
|
|
@ -131,8 +131,7 @@ pub(super) fn check(
|
|||
|
||||
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
|
||||
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
|
||||
(false, false) if from_nbits > to_nbits => "",
|
||||
(true, false) if from_nbits > to_nbits => "",
|
||||
(_, false) if from_nbits > to_nbits => "",
|
||||
(false, true) if from_nbits > 64 => "",
|
||||
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
|
||||
_ => return,
|
||||
|
|
|
|||
|
|
@ -264,8 +264,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx
|
|||
}
|
||||
// Local usage
|
||||
} else if let Res::Local(hir_id) = res
|
||||
&& let Some(parent) = get_parent_node(cx.tcx, hir_id)
|
||||
&& let Node::Local(l) = parent
|
||||
&& let Node::Local(l) = get_parent_node(cx.tcx, hir_id)
|
||||
{
|
||||
if let Some(e) = l.init
|
||||
&& is_cast_from_ty_alias(cx, e, cast_from)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
|||
// `id` appearing in the left-hand side of an assignment is not a read access:
|
||||
//
|
||||
// id = ...; // Not reading `id`.
|
||||
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
if let Node::Expr(parent) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
&& let ExprKind::Assign(lhs, ..) = parent.kind
|
||||
&& path_to_local_id(lhs, id)
|
||||
{
|
||||
|
|
@ -108,7 +108,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
|||
// Only assuming this for "official" methods defined on the type. For methods defined in extension
|
||||
// traits (identified as local, based on the orphan rule), pessimistically assume that they might
|
||||
// have side effects, so consider them a read.
|
||||
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
if let Node::Expr(parent) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
|
||||
&& path_to_local_id(receiver, id)
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
||||
|
|
@ -117,7 +117,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
|
|||
// The method call is a statement, so the return value is not used. That's not a read access:
|
||||
//
|
||||
// id.foo(args);
|
||||
if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) {
|
||||
if let Node::Stmt(..) = get_parent_node(cx.tcx, parent.hir_id) {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO,
|
||||
crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO,
|
||||
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_SEMVER_INFO,
|
||||
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
|
||||
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
|
||||
|
|
@ -59,6 +60,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::attrs::MISMATCHED_TARGET_OS_INFO,
|
||||
crate::attrs::NON_MINIMAL_CFG_INFO,
|
||||
crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO,
|
||||
crate::attrs::UNNECESSARY_CLIPPY_CFG_INFO,
|
||||
crate::attrs::USELESS_ATTRIBUTE_INFO,
|
||||
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
|
||||
crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
|
||||
|
|
|
|||
|
|
@ -1009,7 +1009,7 @@ fn report<'tcx>(
|
|||
state.msg,
|
||||
|diag| {
|
||||
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) {
|
||||
Some(Node::Expr(e)) => match e.kind {
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
|
||||
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
|
||||
_ => (e.precedence().order(), false),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use clippy_config::types::DisallowedPath;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use rustc_ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::DiagnosticBuilder;
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_hir::{Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
|
||||
use rustc_hir::{
|
||||
Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{ExpnId, Span};
|
||||
use rustc_span::{ExpnId, MacroKind, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -57,6 +60,10 @@ pub struct DisallowedMacros {
|
|||
conf_disallowed: Vec<DisallowedPath>,
|
||||
disallowed: DefIdMap<usize>,
|
||||
seen: FxHashSet<ExpnId>,
|
||||
|
||||
// Track the most recently seen node that can have a `derive` attribute.
|
||||
// Needed to use the correct lint level.
|
||||
derive_src: Option<OwnerId>,
|
||||
}
|
||||
|
||||
impl DisallowedMacros {
|
||||
|
|
@ -65,10 +72,11 @@ impl DisallowedMacros {
|
|||
conf_disallowed,
|
||||
disallowed: DefIdMap::default(),
|
||||
seen: FxHashSet::default(),
|
||||
derive_src: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&mut self, cx: &LateContext<'_>, span: Span) {
|
||||
fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option<OwnerId>) {
|
||||
if self.conf_disallowed.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -80,18 +88,26 @@ impl DisallowedMacros {
|
|||
|
||||
if let Some(&index) = self.disallowed.get(&mac.def_id) {
|
||||
let conf = &self.conf_disallowed[index];
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DISALLOWED_MACROS,
|
||||
mac.span,
|
||||
&format!("use of a disallowed macro `{}`", conf.path()),
|
||||
|diag| {
|
||||
if let Some(reason) = conf.reason() {
|
||||
diag.note(reason);
|
||||
}
|
||||
},
|
||||
);
|
||||
let msg = format!("use of a disallowed macro `{}`", conf.path());
|
||||
let add_note = |diag: &mut DiagnosticBuilder<'_, _>| {
|
||||
if let Some(reason) = conf.reason() {
|
||||
diag.note(reason);
|
||||
}
|
||||
};
|
||||
if matches!(mac.kind, MacroKind::Derive)
|
||||
&& let Some(derive_src) = derive_src
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
DISALLOWED_MACROS,
|
||||
cx.tcx.local_def_id_to_hir_id(derive_src.def_id),
|
||||
mac.span,
|
||||
&msg,
|
||||
add_note,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_then(cx, DISALLOWED_MACROS, mac.span, &msg, add_note);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,49 +126,57 @@ impl LateLintPass<'_> for DisallowedMacros {
|
|||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
self.check(cx, expr.span);
|
||||
self.check(cx, expr.span, None);
|
||||
// `$t + $t` can have the context of $t, check also the span of the binary operator
|
||||
if let ExprKind::Binary(op, ..) = expr.kind {
|
||||
self.check(cx, op.span);
|
||||
self.check(cx, op.span, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
||||
self.check(cx, stmt.span);
|
||||
self.check(cx, stmt.span, None);
|
||||
}
|
||||
|
||||
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) {
|
||||
self.check(cx, ty.span);
|
||||
self.check(cx, ty.span, None);
|
||||
}
|
||||
|
||||
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
|
||||
self.check(cx, pat.span);
|
||||
self.check(cx, pat.span, None);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, self.derive_src);
|
||||
self.check(cx, item.vis_span, None);
|
||||
|
||||
if matches!(
|
||||
item.kind,
|
||||
ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)
|
||||
) && macro_backtrace(item.span).all(|m| !matches!(m.kind, MacroKind::Derive))
|
||||
{
|
||||
self.derive_src = Some(item.owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, None);
|
||||
self.check(cx, item.vis_span, None);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, None);
|
||||
self.check(cx, item.vis_span, None);
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.span, None);
|
||||
}
|
||||
|
||||
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
|
||||
self.check(cx, path.span);
|
||||
self.check(cx, path.span, None);
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
|
||||
self.check(cx, attr.span);
|
||||
self.check(cx, attr.span, self.derive_src);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,8 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|
|||
// }
|
||||
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
|
||||
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
|
||||
let parent_node = get_parent_node(cx.tcx, drop_expr.hir_id);
|
||||
if let Some(Node::Arm(Arm { body, .. })) = &parent_node {
|
||||
if let Node::Arm(Arm { body, .. }) = get_parent_node(cx.tcx, drop_expr.hir_id) {
|
||||
return body.hir_id == drop_expr.hir_id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
|||
use clippy_utils::is_diag_trait_item;
|
||||
use clippy_utils::macros::{
|
||||
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
|
||||
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage,
|
||||
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
|
||||
};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_lang_item};
|
||||
|
|
@ -20,7 +20,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
|
|||
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::edition::Edition::Edition2021;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
|
|
@ -189,32 +188,18 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
|||
&& is_format_macro(cx, macro_call.def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
{
|
||||
let arg_expr = find_format_arg_expr(expr, arg);
|
||||
let linter = FormatArgsExpr {
|
||||
cx,
|
||||
expr,
|
||||
macro_call: ¯o_call,
|
||||
format_args: &format_args,
|
||||
ignore_mixed: self.ignore_mixed,
|
||||
};
|
||||
|
||||
check_unused_format_specifier(cx, placeholder, arg_expr);
|
||||
|
||||
if placeholder.format_trait != FormatTrait::Display
|
||||
|| placeholder.format_options != FormatOptions::default()
|
||||
|| is_aliased(&format_args, index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(arg_hir_expr) = arg_expr {
|
||||
let name = cx.tcx.item_name(macro_call.def_id);
|
||||
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
|
||||
check_to_string_in_format_args(cx, name, arg_hir_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
linter.check_templates();
|
||||
|
||||
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
|
||||
check_uninlined_args(cx, &format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
|
||||
linter.check_uninlined_args();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -222,255 +207,279 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
|||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn check_unused_format_specifier(
|
||||
cx: &LateContext<'_>,
|
||||
placeholder: &FormatPlaceholder,
|
||||
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
|
||||
) {
|
||||
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());
|
||||
struct FormatArgsExpr<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
macro_call: &'a MacroCall,
|
||||
format_args: &'a rustc_ast::FormatArgs,
|
||||
ignore_mixed: bool,
|
||||
}
|
||||
|
||||
let is_format_args = match ty_or_ast_expr {
|
||||
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
|
||||
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
|
||||
};
|
||||
impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> {
|
||||
fn check_templates(&self) {
|
||||
for piece in &self.format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = self.format_args.arguments.all_args().get(index)
|
||||
{
|
||||
let arg_expr = find_format_arg_expr(self.expr, arg);
|
||||
|
||||
let options = &placeholder.format_options;
|
||||
self.check_unused_format_specifier(placeholder, arg_expr);
|
||||
|
||||
let arg_span = match arg_expr {
|
||||
Ok(expr) => expr.span,
|
||||
Err(expr) => expr.span,
|
||||
};
|
||||
if let Ok(arg_expr) = arg_expr
|
||||
&& placeholder.format_trait == FormatTrait::Display
|
||||
&& placeholder.format_options == FormatOptions::default()
|
||||
&& !self.is_aliased(index)
|
||||
{
|
||||
let name = self.cx.tcx.item_name(self.macro_call.def_id);
|
||||
self.check_format_in_format_args(name, arg_expr);
|
||||
self.check_to_string_in_format_args(name, arg_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(placeholder_span) = placeholder.span
|
||||
&& is_format_args
|
||||
&& *options != FormatOptions::default()
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
placeholder_span,
|
||||
"format specifiers have no effect on `format_args!()`",
|
||||
|diag| {
|
||||
let mut suggest_format = |spec| {
|
||||
let message = format!("for the {spec} to apply consider using `format!()`");
|
||||
fn check_unused_format_specifier(
|
||||
&self,
|
||||
placeholder: &FormatPlaceholder,
|
||||
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
|
||||
) {
|
||||
let ty_or_ast_expr = arg_expr.map(|expr| self.cx.typeck_results().expr_ty(expr).peel_refs());
|
||||
|
||||
if let Some(mac_call) = root_macro_call(arg_span)
|
||||
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
cx.sess().source_map().span_until_char(mac_call.span, '!'),
|
||||
message,
|
||||
"format",
|
||||
let is_format_args = match ty_or_ast_expr {
|
||||
Ok(ty) => is_type_lang_item(self.cx, ty, LangItem::FormatArguments),
|
||||
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
|
||||
};
|
||||
|
||||
let options = &placeholder.format_options;
|
||||
|
||||
let arg_span = match arg_expr {
|
||||
Ok(expr) => expr.span,
|
||||
Err(expr) => expr.span,
|
||||
};
|
||||
|
||||
if let Some(placeholder_span) = placeholder.span
|
||||
&& is_format_args
|
||||
&& *options != FormatOptions::default()
|
||||
{
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
placeholder_span,
|
||||
"format specifiers have no effect on `format_args!()`",
|
||||
|diag| {
|
||||
let mut suggest_format = |spec| {
|
||||
let message = format!("for the {spec} to apply consider using `format!()`");
|
||||
|
||||
if let Some(mac_call) = root_macro_call(arg_span)
|
||||
&& self.cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
self.cx.sess().source_map().span_until_char(mac_call.span, '!'),
|
||||
message,
|
||||
"format",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help(message);
|
||||
}
|
||||
};
|
||||
|
||||
if options.width.is_some() {
|
||||
suggest_format("width");
|
||||
}
|
||||
|
||||
if options.precision.is_some() {
|
||||
suggest_format("precision");
|
||||
}
|
||||
|
||||
if let Some(format_span) = format_placeholder_format_span(placeholder) {
|
||||
diag.span_suggestion_verbose(
|
||||
format_span,
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help(message);
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if options.width.is_some() {
|
||||
suggest_format("width");
|
||||
}
|
||||
fn check_uninlined_args(&self) {
|
||||
if self.format_args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if self.macro_call.span.edition() < Edition2021
|
||||
&& (is_panic(self.cx, self.macro_call.def_id) || is_assert_macro(self.cx, self.macro_call.def_id))
|
||||
{
|
||||
// panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as
|
||||
// non-format
|
||||
return;
|
||||
}
|
||||
|
||||
if options.precision.is_some() {
|
||||
suggest_format("precision");
|
||||
}
|
||||
let mut fixes = Vec::new();
|
||||
// If any of the arguments are referenced by an index number,
|
||||
// and that argument is not a simple variable and cannot be inlined,
|
||||
// we cannot remove any other arguments in the format string,
|
||||
// because the index numbers might be wrong after inlining.
|
||||
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
|
||||
for (pos, usage) in self.format_arg_positions() {
|
||||
if !self.check_one_arg(pos, usage, &mut fixes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(format_span) = format_placeholder_format_span(placeholder) {
|
||||
diag.span_suggestion_verbose(
|
||||
format_span,
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
if fixes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
|
||||
// in those cases, make the code suggestion hidden
|
||||
let multiline_fix = fixes
|
||||
.iter()
|
||||
.any(|(span, _)| self.cx.sess().source_map().is_multiline(*span));
|
||||
|
||||
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
|
||||
fixes.sort_unstable_by_key(|(span, _)| *span);
|
||||
fixes.dedup_by_key(|(span, _)| *span);
|
||||
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
UNINLINED_FORMAT_ARGS,
|
||||
self.macro_call.span,
|
||||
"variables can be used directly in the `format!` string",
|
||||
|diag| {
|
||||
diag.multipart_suggestion_with_style(
|
||||
"change this to",
|
||||
fixes,
|
||||
Applicability::MachineApplicable,
|
||||
if multiline_fix { CompletelyHidden } else { ShowCode },
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_uninlined_args(
|
||||
cx: &LateContext<'_>,
|
||||
args: &rustc_ast::FormatArgs,
|
||||
call_site: Span,
|
||||
def_id: DefId,
|
||||
ignore_mixed: bool,
|
||||
) {
|
||||
if args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
|
||||
// panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as
|
||||
// non-format
|
||||
return;
|
||||
fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool {
|
||||
let index = pos.index.unwrap();
|
||||
let arg = &self.format_args.arguments.all_args()[index];
|
||||
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
|
||||
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
|
||||
&& let [segment] = path.segments.as_slice()
|
||||
&& segment.args.is_none()
|
||||
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
|
||||
&& let Some(pos_span) = pos.span
|
||||
{
|
||||
let replacement = match usage {
|
||||
FormatParamUsage::Argument => segment.ident.name.to_string(),
|
||||
FormatParamUsage::Width => format!("{}$", segment.ident.name),
|
||||
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
|
||||
};
|
||||
fixes.push((pos_span, replacement));
|
||||
fixes.push((arg_span, String::new()));
|
||||
true // successful inlining, continue checking
|
||||
} else {
|
||||
// Do not continue inlining (return false) in case
|
||||
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
|
||||
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
|
||||
pos.kind != FormatArgPositionKind::Number
|
||||
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut fixes = Vec::new();
|
||||
// If any of the arguments are referenced by an index number,
|
||||
// and that argument is not a simple variable and cannot be inlined,
|
||||
// we cannot remove any other arguments in the format string,
|
||||
// because the index numbers might be wrong after inlining.
|
||||
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
|
||||
for (pos, usage) in format_arg_positions(args) {
|
||||
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
|
||||
fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) {
|
||||
let expn_data = arg.span.ctxt().outer_expn_data();
|
||||
if expn_data.call_site.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let Some(mac_id) = expn_data.macro_def_id else { return };
|
||||
if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
FORMAT_IN_FORMAT_ARGS,
|
||||
self.macro_call.span,
|
||||
&format!("`format!` in `{name}!` args"),
|
||||
|diag| {
|
||||
diag.help(format!(
|
||||
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if fixes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
|
||||
// in those cases, make the code suggestion hidden
|
||||
let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span));
|
||||
|
||||
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
|
||||
fixes.sort_unstable_by_key(|(span, _)| *span);
|
||||
fixes.dedup_by_key(|(span, _)| *span);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNINLINED_FORMAT_ARGS,
|
||||
call_site,
|
||||
"variables can be used directly in the `format!` string",
|
||||
|diag| {
|
||||
diag.multipart_suggestion_with_style(
|
||||
"change this to",
|
||||
fixes,
|
||||
Applicability::MachineApplicable,
|
||||
if multiline_fix { CompletelyHidden } else { ShowCode },
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn check_one_arg(
|
||||
args: &rustc_ast::FormatArgs,
|
||||
pos: &FormatArgPosition,
|
||||
usage: FormatParamUsage,
|
||||
fixes: &mut Vec<(Span, String)>,
|
||||
ignore_mixed: bool,
|
||||
) -> bool {
|
||||
let index = pos.index.unwrap();
|
||||
let arg = &args.arguments.all_args()[index];
|
||||
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
|
||||
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
|
||||
&& let [segment] = path.segments.as_slice()
|
||||
&& segment.args.is_none()
|
||||
&& let Some(arg_span) = format_arg_removal_span(args, index)
|
||||
&& let Some(pos_span) = pos.span
|
||||
{
|
||||
let replacement = match usage {
|
||||
FormatParamUsage::Argument => segment.ident.name.to_string(),
|
||||
FormatParamUsage::Width => format!("{}$", segment.ident.name),
|
||||
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
|
||||
};
|
||||
fixes.push((pos_span, replacement));
|
||||
fixes.push((arg_span, String::new()));
|
||||
true // successful inlining, continue checking
|
||||
} else {
|
||||
// Do not continue inlining (return false) in case
|
||||
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
|
||||
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
|
||||
pos.kind != FormatArgPositionKind::Number
|
||||
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
|
||||
let expn_data = arg.span.ctxt().outer_expn_data();
|
||||
if expn_data.call_site.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let Some(mac_id) = expn_data.macro_def_id else { return };
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FORMAT_IN_FORMAT_ARGS,
|
||||
call_site,
|
||||
&format!("`format!` in `{name}!` args"),
|
||||
|diag| {
|
||||
diag.help(format!(
|
||||
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
|
||||
if !value.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
|
||||
&& implements_trait(cx, target, display_trait_id, &[])
|
||||
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
{
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) {
|
||||
let cx = self.cx;
|
||||
if !value.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
|
||||
&& implements_trait(cx, target, display_trait_id, &[])
|
||||
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
{
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_arg_positions(
|
||||
format_args: &rustc_ast::FormatArgs,
|
||||
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
|
||||
format_args.template.iter().flat_map(|piece| match piece {
|
||||
FormatArgsPiece::Placeholder(placeholder) => {
|
||||
let mut positions = ArrayVec::<_, 3>::new();
|
||||
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
|
||||
self.format_args.template.iter().flat_map(|piece| match piece {
|
||||
FormatArgsPiece::Placeholder(placeholder) => {
|
||||
let mut positions = ArrayVec::<_, 3>::new();
|
||||
|
||||
positions.push((&placeholder.argument, FormatParamUsage::Argument));
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
|
||||
positions.push((position, FormatParamUsage::Width));
|
||||
}
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
|
||||
positions.push((position, FormatParamUsage::Precision));
|
||||
}
|
||||
positions.push((&placeholder.argument, FormatParamUsage::Argument));
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
|
||||
positions.push((position, FormatParamUsage::Width));
|
||||
}
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
|
||||
positions.push((position, FormatParamUsage::Precision));
|
||||
}
|
||||
|
||||
positions
|
||||
},
|
||||
FormatArgsPiece::Literal(_) => ArrayVec::new(),
|
||||
})
|
||||
}
|
||||
positions
|
||||
},
|
||||
FormatArgsPiece::Literal(_) => ArrayVec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the format argument at `index` is referred to by multiple format params
|
||||
fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool {
|
||||
format_arg_positions(format_args)
|
||||
.filter(|(position, _)| position.index == Ok(index))
|
||||
.at_most_one()
|
||||
.is_err()
|
||||
/// Returns true if the format argument at `index` is referred to by multiple format params
|
||||
fn is_aliased(&self, index: usize) -> bool {
|
||||
self.format_arg_positions()
|
||||
.filter(|(position, _)| position.index == Ok(index))
|
||||
.at_most_one()
|
||||
.is_err()
|
||||
}
|
||||
}
|
||||
|
||||
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
|||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
use rustc_span::{sym, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -119,123 +119,132 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
|
|||
}
|
||||
|
||||
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
// Assume no nested Impl of Debug and Display within eachother
|
||||
// Assume no nested Impl of Debug and Display within each other
|
||||
if is_format_trait_impl(cx, impl_item).is_some() {
|
||||
self.format_trait_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(format_trait_impl) = self.format_trait_impl else {
|
||||
return;
|
||||
};
|
||||
|
||||
if format_trait_impl.name == sym::Display {
|
||||
check_to_string_in_display(cx, expr);
|
||||
if let Some(format_trait_impl) = self.format_trait_impl {
|
||||
let linter = FormatImplExpr {
|
||||
cx,
|
||||
expr,
|
||||
format_trait_impl,
|
||||
};
|
||||
linter.check_to_string_in_display();
|
||||
linter.check_self_in_format_args();
|
||||
linter.check_print_in_format_impl();
|
||||
}
|
||||
|
||||
check_self_in_format_args(cx, expr, format_trait_impl);
|
||||
check_print_in_format_impl(cx, expr, format_trait_impl);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind
|
||||
// Get the hir_id of the object we are calling the method on
|
||||
// Is the method to_string() ?
|
||||
&& path.ident.name == sym::to_string
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& is_diag_trait_item(cx, expr_def_id, sym::ToString)
|
||||
// Is the method is called on self
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
|
||||
&& let [segment] = path.segments
|
||||
&& segment.ident.name == kw::SelfLower
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
struct FormatImplExpr<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
format_trait_impl: FormatTraitNames,
|
||||
}
|
||||
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, outer_macro.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> {
|
||||
fn check_to_string_in_display(&self) {
|
||||
if self.format_trait_impl.name == sym::Display
|
||||
&& let ExprKind::MethodCall(path, self_arg, ..) = self.expr.kind
|
||||
// Get the hir_id of the object we are calling the method on
|
||||
// Is the method to_string() ?
|
||||
&& path.ident.name == sym::to_string
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
&& let Some(expr_def_id) = self.cx.typeck_results().type_dependent_def_id(self.expr.hir_id)
|
||||
&& is_diag_trait_item(self.cx, expr_def_id, sym::ToString)
|
||||
// Is the method is called on self
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
|
||||
&& let [segment] = path.segments
|
||||
&& segment.ident.name == kw::SelfLower
|
||||
{
|
||||
span_lint(
|
||||
self.cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
self.expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_self_in_format_args(&self) {
|
||||
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||||
if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(self.cx, macro_def_id)
|
||||
&& let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == self.format_trait_impl.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(self.expr, arg)
|
||||
{
|
||||
self.check_format_arg_self(arg_expr);
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||||
// Since the argument to fmt is itself a reference: &self
|
||||
let reference = peel_ref_operators(cx, arg);
|
||||
let map = cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTraitNames { name, .. } = impl_trait;
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
fn check_format_arg_self(&self, arg: &Expr<'_>) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||||
// Since the argument to fmt is itself a reference: &self
|
||||
let reference = peel_ref_operators(self.cx, arg);
|
||||
let map = self.cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTraitNames { name, .. } = self.format_trait_impl;
|
||||
span_lint(
|
||||
self.cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
self.expr.span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
fn check_print_in_format_impl(&self) {
|
||||
if let Some(macro_call) = root_macro_call_first_node(self.cx, self.expr)
|
||||
&& let Some(name) = self.cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = impl_trait.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
self.cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", self.format_trait_impl.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = self.format_trait_impl.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_errors::{Applicability, SuggestionStyle};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem,
|
||||
TraitItemKind, TyKind,
|
||||
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, OpaqueTy, TraitBoundModifier,
|
||||
TraitItem, TraitItemKind, TyKind, TypeBinding,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -50,20 +51,17 @@ declare_clippy_lint! {
|
|||
}
|
||||
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn emit_lint(
|
||||
cx: &LateContext<'_>,
|
||||
poly_trait: &rustc_hir::PolyTraitRef<'_>,
|
||||
opaque_ty: &rustc_hir::OpaqueTy<'_>,
|
||||
index: usize,
|
||||
// The bindings that were implied
|
||||
// The bindings that were implied, used for suggestion purposes since removing a bound with associated types
|
||||
// means we might need to then move it to a different bound
|
||||
implied_bindings: &[rustc_hir::TypeBinding<'_>],
|
||||
// The original bindings that `implied_bindings` are implied from
|
||||
implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
|
||||
implied_by_args: &[GenericArg<'_>],
|
||||
implied_by_span: Span,
|
||||
bound: &ImplTraitBound<'_>,
|
||||
) {
|
||||
let implied_by = snippet(cx, implied_by_span, "..");
|
||||
let implied_by = snippet(cx, bound.span, "..");
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
@ -93,17 +91,17 @@ fn emit_lint(
|
|||
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
|
||||
let omitted_assoc_tys: Vec<_> = implied_bindings
|
||||
.iter()
|
||||
.filter(|binding| !implied_by_bindings.iter().any(|b| b.ident == binding.ident))
|
||||
.filter(|binding| !bound.bindings.iter().any(|b| b.ident == binding.ident))
|
||||
.collect();
|
||||
|
||||
if !omitted_assoc_tys.is_empty() {
|
||||
// `<>` needs to be added if there aren't yet any generic arguments or bindings
|
||||
let needs_angle_brackets = implied_by_args.is_empty() && implied_by_bindings.is_empty();
|
||||
let insert_span = match (implied_by_args, implied_by_bindings) {
|
||||
let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty();
|
||||
let insert_span = match (bound.args, bound.bindings) {
|
||||
([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
|
||||
([.., arg], []) => arg.span().shrink_to_hi(),
|
||||
([], [.., binding]) => binding.span.shrink_to_hi(),
|
||||
([], []) => implied_by_span.shrink_to_hi(),
|
||||
([], []) => bound.span.shrink_to_hi(),
|
||||
};
|
||||
|
||||
let mut associated_tys_sugg = if needs_angle_brackets {
|
||||
|
|
@ -223,42 +221,93 @@ fn is_same_generics<'tcx>(
|
|||
})
|
||||
}
|
||||
|
||||
struct ImplTraitBound<'tcx> {
|
||||
/// The span of the bound in the `impl Trait` type
|
||||
span: Span,
|
||||
/// The predicates defined in the trait referenced by this bound. This also contains the actual
|
||||
/// supertrait bounds
|
||||
predicates: &'tcx [(ty::Clause<'tcx>, Span)],
|
||||
/// The `DefId` of the trait being referenced by this bound
|
||||
trait_def_id: DefId,
|
||||
/// The generic arguments on the `impl Trait` bound
|
||||
args: &'tcx [GenericArg<'tcx>],
|
||||
/// The associated types on this bound
|
||||
bindings: &'tcx [TypeBinding<'tcx>],
|
||||
}
|
||||
|
||||
/// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds").
|
||||
///
|
||||
/// For `impl Deref + DerefMut + Eq` this returns `[Deref, PartialEq]`.
|
||||
/// The `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`, and `PartialEq` comes from
|
||||
/// `Eq`.
|
||||
fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, opaque_ty: &OpaqueTy<'tcx>) -> Vec<ImplTraitBound<'tcx>> {
|
||||
opaque_ty
|
||||
.bounds
|
||||
.iter()
|
||||
.filter_map(|bound| {
|
||||
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& poly_trait.bound_generic_params.is_empty()
|
||||
&& let Some(trait_def_id) = path.res.opt_def_id()
|
||||
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
|
||||
// If the trait has no supertrait, there is no need to collect anything from that bound
|
||||
&& !predicates.is_empty()
|
||||
{
|
||||
Some(ImplTraitBound {
|
||||
predicates,
|
||||
args: path.args.map_or([].as_slice(), |p| p.args),
|
||||
bindings: path.args.map_or([].as_slice(), |p| p.bindings),
|
||||
trait_def_id,
|
||||
span: bound.span(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Given a bound in an `impl Trait` type, looks for a trait in the set of supertraits (previously
|
||||
/// collected in [`collect_supertrait_bounds`]) that matches (same trait and generic arguments).
|
||||
fn find_bound_in_supertraits<'a, 'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
trait_def_id: DefId,
|
||||
args: &'tcx [GenericArg<'tcx>],
|
||||
bounds: &'a [ImplTraitBound<'tcx>],
|
||||
) -> Option<&'a ImplTraitBound<'tcx>> {
|
||||
bounds.iter().find(|bound| {
|
||||
bound.predicates.iter().any(|(clause, _)| {
|
||||
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
|
||||
&& tr.def_id() == trait_def_id
|
||||
{
|
||||
is_same_generics(
|
||||
cx.tcx,
|
||||
tr.trait_ref.args,
|
||||
bound.args,
|
||||
args,
|
||||
bound.trait_def_id,
|
||||
trait_def_id,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
|
||||
if let FnRetTy::Return(ty) = decl.output
|
||||
&&let TyKind::OpaqueDef(item_id, ..) = ty.kind
|
||||
&& let TyKind::OpaqueDef(item_id, ..) = ty.kind
|
||||
&& let item = cx.tcx.hir().item(item_id)
|
||||
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind
|
||||
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case
|
||||
// we can avoid doing a bunch of stuff unnecessarily.
|
||||
&& opaque_ty.bounds.len() > 1
|
||||
{
|
||||
// Get all the (implied) trait predicates in the bounds.
|
||||
// For `impl Deref + DerefMut` this will contain [`Deref`].
|
||||
// The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`.
|
||||
// N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits.
|
||||
// Example:
|
||||
// `impl Deref<Target = i32> + DerefMut<Target = u32>` is not allowed.
|
||||
// `DerefMut::Target` needs to match `Deref::Target`.
|
||||
let implied_bounds: Vec<_> = opaque_ty
|
||||
.bounds
|
||||
.iter()
|
||||
.filter_map(|bound| {
|
||||
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& poly_trait.bound_generic_params.is_empty()
|
||||
&& let Some(trait_def_id) = path.res.opt_def_id()
|
||||
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
|
||||
&& !predicates.is_empty()
|
||||
// If the trait has no supertrait, there is nothing to add.
|
||||
{
|
||||
Some((bound.span(), path, predicates, trait_def_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let supertraits = collect_supertrait_bounds(cx, opaque_ty);
|
||||
|
||||
// Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec.
|
||||
// Lint all bounds in the `impl Trait` type that we've previously also seen in the set of
|
||||
// supertraits of each of the bounds.
|
||||
// This involves some extra logic when generic arguments are present, since
|
||||
// simply comparing trait `DefId`s won't be enough. We also need to compare the generics.
|
||||
for (index, bound) in opaque_ty.bounds.iter().enumerate() {
|
||||
|
|
@ -267,42 +316,18 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
|
|||
&& let implied_args = path.args.map_or([].as_slice(), |a| a.args)
|
||||
&& let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings)
|
||||
&& let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
|
||||
&& let Some((implied_by_span, implied_by_args, implied_by_bindings)) =
|
||||
implied_bounds
|
||||
.iter()
|
||||
.find_map(|&(span, implied_by_path, preds, implied_by_def_id)| {
|
||||
let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args);
|
||||
let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings);
|
||||
|
||||
preds.iter().find_map(|(clause, _)| {
|
||||
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
|
||||
&& tr.def_id() == def_id
|
||||
&& is_same_generics(
|
||||
cx.tcx,
|
||||
tr.trait_ref.args,
|
||||
implied_by_args,
|
||||
implied_args,
|
||||
implied_by_def_id,
|
||||
def_id,
|
||||
)
|
||||
{
|
||||
Some((span, implied_by_args, implied_by_bindings))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
&& let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits)
|
||||
// If the implied bound has a type binding that also exists in the implied-by trait,
|
||||
// then we shouldn't lint. See #11880 for an example.
|
||||
&& let assocs = cx.tcx.associated_items(bound.trait_def_id)
|
||||
&& !implied_bindings.iter().any(|binding| {
|
||||
assocs
|
||||
.filter_by_name_unhygienic(binding.ident.name)
|
||||
.next()
|
||||
.is_some_and(|assoc| assoc.kind == ty::AssocKind::Type)
|
||||
})
|
||||
{
|
||||
emit_lint(
|
||||
cx,
|
||||
poly_trait,
|
||||
opaque_ty,
|
||||
index,
|
||||
implied_bindings,
|
||||
implied_by_bindings,
|
||||
implied_by_args,
|
||||
implied_by_span,
|
||||
);
|
||||
emit_lint(cx, poly_trait, opaque_ty, index, implied_bindings, bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_in_test_function;
|
||||
use rustc_attr::{StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_hir::{Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{ExpnKind, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -81,13 +82,18 @@ impl IncompatibleMsrv {
|
|||
version
|
||||
}
|
||||
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
}
|
||||
let version = self.get_def_id_version(cx.tcx, def_id);
|
||||
if self.msrv.meets(version) {
|
||||
if self.msrv.meets(version) || is_in_test_function(cx.tcx, node) {
|
||||
return;
|
||||
}
|
||||
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {
|
||||
// Desugared expressions get to cheat and stability is ignored.
|
||||
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
|
||||
return;
|
||||
}
|
||||
self.emit_lint_for(cx, span, version);
|
||||
|
|
@ -117,14 +123,14 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
|||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, span);
|
||||
self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, [_]) => {
|
||||
if let ExprKind::Path(qpath) = call.kind
|
||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
|
|||
&& let hir::Node::Expr(maybe_addrof_expr) = cx.tcx.parent_hir_node(parent_id)
|
||||
&& let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind
|
||||
{
|
||||
use_info.index_use.push((index_value, cx.tcx.hir().span(parent_expr.hir_id)));
|
||||
use_info
|
||||
.index_use
|
||||
.push((index_value, cx.tcx.hir().span(parent_expr.hir_id)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
// only `usize` index is legal in rust array index
|
||||
// leave other type to rustc
|
||||
if let Constant::Int(off) = constant
|
||||
&& off <= usize::MAX as u128
|
||||
&& let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind()
|
||||
&& *utype == ty::UintTy::Usize
|
||||
&& let ty::Array(_, s) = ty.kind()
|
||||
|
|
|
|||
|
|
@ -385,7 +385,6 @@ impl LateLintPass<'_> for ItemNameRepetitions {
|
|||
assert!(last.is_some());
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let item_name = item.ident.name.as_str();
|
||||
let item_camel = to_camel_case(item_name);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator {
|
|||
if matches!(name, "iter" | "iter_mut")
|
||||
&& !matches!(
|
||||
get_parent_node(cx.tcx, item.hir_id()),
|
||||
Some(Node::Item(Item { kind: ItemKind::Impl(i), .. })) if i.of_trait.is_some()
|
||||
Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some()
|
||||
)
|
||||
{
|
||||
if let ImplItemKind::Fn(fn_sig, _) = &item.kind {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
clippy::missing_docs_in_private_items,
|
||||
clippy::must_use_candidate,
|
||||
rustc::diagnostic_outside_of_impl,
|
||||
rustc::untranslatable_diagnostic,
|
||||
rustc::untranslatable_diagnostic
|
||||
)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// warn on lints, that are included in `rust-lang/rust`s bootstrap
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{fn_def_id, is_lint_allowed};
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
|
||||
use hir::intravisit::{walk_expr, Visitor};
|
||||
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node};
|
||||
use rustc_ast::Label;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
|
||||
use super::INFINITE_LOOP;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'_>,
|
||||
expr: &Expr<'tcx>,
|
||||
loop_block: &'tcx hir::Block<'_>,
|
||||
label: Option<Label>,
|
||||
) {
|
||||
|
|
@ -34,6 +35,10 @@ pub(super) fn check<'tcx>(
|
|||
return;
|
||||
}
|
||||
|
||||
if in_external_macro(cx.sess(), expr.span) || is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut loop_visitor = LoopVisitor {
|
||||
cx,
|
||||
label,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
|
|||
|
|
@ -64,38 +64,50 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
let min_index = usize::min(lindex, rindex);
|
||||
let max_index = usize::max(lindex, rindex);
|
||||
|
||||
let mut local_map: HirIdMap<HirId> = HirIdMap::default();
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
if let Some(a_id) = path_to_local(a)
|
||||
&& let Some(b_id) = path_to_local(b)
|
||||
&& let entry = match local_map.entry(a_id) {
|
||||
HirIdMapEntry::Vacant(entry) => entry,
|
||||
// check if using the same bindings as before
|
||||
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
|
||||
}
|
||||
let check_eq_with_pat = |expr_a: &Expr<'_>, expr_b: &Expr<'_>| {
|
||||
let mut local_map: HirIdMap<HirId> = HirIdMap::default();
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
if let Some(a_id) = path_to_local(a)
|
||||
&& let Some(b_id) = path_to_local(b)
|
||||
&& let entry = match local_map.entry(a_id) {
|
||||
HirIdMapEntry::Vacant(entry) => entry,
|
||||
// check if using the same bindings as before
|
||||
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
|
||||
}
|
||||
// the names technically don't have to match; this makes the lint more conservative
|
||||
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
|
||||
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
|
||||
&& pat_contains_local(lhs.pat, a_id)
|
||||
&& pat_contains_local(rhs.pat, b_id)
|
||||
{
|
||||
entry.insert(b_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
// Arms with a guard are ignored, those can’t always be merged together
|
||||
// If both arms overlap with an arm in between then these can't be merged either.
|
||||
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
|
||||
&& lhs.guard.is_none()
|
||||
&& rhs.guard.is_none()
|
||||
&& SpanlessEq::new(cx)
|
||||
.expr_fallback(eq_fallback)
|
||||
.eq_expr(lhs.body, rhs.body)
|
||||
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
|
||||
&& pat_contains_local(lhs.pat, a_id)
|
||||
&& pat_contains_local(rhs.pat, b_id)
|
||||
{
|
||||
entry.insert(b_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
SpanlessEq::new(cx)
|
||||
.expr_fallback(eq_fallback)
|
||||
.eq_expr(expr_a, expr_b)
|
||||
// these checks could be removed to allow unused bindings
|
||||
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
|
||||
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
|
||||
};
|
||||
|
||||
let check_same_guard = || match (&lhs.guard, &rhs.guard) {
|
||||
(None, None) => true,
|
||||
(Some(lhs_guard), Some(rhs_guard)) => check_eq_with_pat(lhs_guard, rhs_guard),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let check_same_body = || check_eq_with_pat(lhs.body, rhs.body);
|
||||
|
||||
// Arms with different guard are ignored, those can’t always be merged together
|
||||
// If both arms overlap with an arm in between then these can't be merged either.
|
||||
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
|
||||
&& check_same_guard()
|
||||
&& check_same_body()
|
||||
};
|
||||
|
||||
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
|
||||
|
|
|
|||
|
|
@ -123,37 +123,35 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
|
|||
/// Manually check for coercion casting by checking if the type of the match operand or let expr
|
||||
/// differs with the assigned local variable or the function return type.
|
||||
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
|
||||
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
|
||||
match p_node {
|
||||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::Local(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(item) => {
|
||||
if let ItemKind::Fn(..) = item.kind {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
Node::Block(block) => {
|
||||
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
|
||||
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
|
||||
}
|
||||
},
|
||||
// recursively call on `if xxx {..}` etc.
|
||||
Node::Expr(p_expr) => {
|
||||
return expr_ty_matches_p_ty(cx, expr, p_expr);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
match get_parent_node(cx.tcx, p_expr.hir_id) {
|
||||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::Local(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(item) => {
|
||||
if let ItemKind::Fn(..) = item.kind {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
Node::Block(block) => {
|
||||
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
|
||||
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
|
||||
}
|
||||
},
|
||||
// recursively call on `if xxx {..}` etc.
|
||||
Node::Expr(p_expr) => {
|
||||
return expr_ty_matches_p_ty(cx, expr, p_expr);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use clippy_utils::visitors::{for_each_expr, is_local_used};
|
|||
use rustc_ast::{BorrowKind, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind};
|
||||
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
|
@ -269,7 +269,11 @@ fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..),
|
||||
)
|
||||
},
|
||||
ExprKind::AddrOf(..) | ExprKind::Array(..) | ExprKind::Tup(..) | ExprKind::Struct(..) => true,
|
||||
ExprKind::AddrOf(..)
|
||||
| ExprKind::Array(..)
|
||||
| ExprKind::Tup(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Unary(UnOp::Neg, _) => true,
|
||||
ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true,
|
||||
_ => false,
|
||||
} {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,12 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
fn set_diagnostic<'tcx>(diag: &mut DiagnosticBuilder<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
|
||||
fn set_diagnostic<'tcx>(
|
||||
diag: &mut DiagnosticBuilder<'_, ()>,
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
found: FoundSigDrop,
|
||||
) {
|
||||
if found.lint_suggestion == LintSuggestion::MoveAndClone {
|
||||
// If our suggestion is to move and clone, then we want to leave it to the user to
|
||||
// decide how to address this lint, since it may be that cloning is inappropriate.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lin
|
|||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_non_aggregate_primitive_type;
|
||||
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core};
|
||||
use clippy_utils::{
|
||||
is_default_equivalent, is_expr_used_or_unified, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
|
|
@ -232,7 +234,7 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
|
|||
// Check that second argument is `Option::None`
|
||||
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
|
||||
check_replace_option_with_none(cx, dest, expr.span);
|
||||
} else if self.msrv.meets(msrvs::MEM_TAKE) {
|
||||
} else if self.msrv.meets(msrvs::MEM_TAKE) && is_expr_used_or_unified(cx.tcx, expr) {
|
||||
check_replace_with_default(cx, src, dest, expr.span);
|
||||
}
|
||||
check_replace_with_uninit(cx, src, dest, expr.span);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ pub(super) fn check<'tcx>(
|
|||
&& ext_str.starts_with('.')
|
||||
&& (ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|
||||
|| ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()))
|
||||
&& !ext_str.chars().skip(1).all(|c| c.is_ascii_digit())
|
||||
&& let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs()
|
||||
&& (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ pub(super) fn check(
|
|||
|
||||
if is_copy(cx, ty) {
|
||||
let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Expr(parent)) => match parent.kind {
|
||||
Node::Expr(parent) => match parent.kind {
|
||||
// &*x is a nop, &x.clone() is not
|
||||
ExprKind::AddrOf(..) => return,
|
||||
// (*x).func() is useless, x.clone().func() can work in case func borrows self
|
||||
|
|
@ -70,7 +70,7 @@ pub(super) fn check(
|
|||
_ => false,
|
||||
},
|
||||
// local binding capturing a reference
|
||||
Some(Node::Local(l)) if matches!(l.pat.kind, PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..)) => {
|
||||
Node::Local(l) if matches!(l.pat.kind, PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..)) => {
|
||||
return;
|
||||
},
|
||||
_ => false,
|
||||
|
|
|
|||
|
|
@ -28,104 +28,102 @@ pub(super) fn check<'tcx>(
|
|||
iter_expr: &'tcx Expr<'tcx>,
|
||||
call_span: Span,
|
||||
) {
|
||||
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
|
||||
match parent {
|
||||
Node::Expr(parent) => {
|
||||
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
|
||||
match get_parent_node(cx.tcx, collect_expr.hir_id) {
|
||||
Node::Expr(parent) => {
|
||||
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
|
||||
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
let collect_ty = cx.typeck_results().expr_ty(collect_expr);
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
let collect_ty = cx.typeck_results().expr_ty(collect_expr);
|
||||
|
||||
let sugg: String = match name {
|
||||
"len" => {
|
||||
if let Some(adt) = collect_ty.ty_adt_def()
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
|
||||
)
|
||||
{
|
||||
"count()".into()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
"is_empty"
|
||||
if is_is_empty_sig(cx, parent.hir_id)
|
||||
&& iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
|
||||
let sugg: String = match name {
|
||||
"len" => {
|
||||
if let Some(adt) = collect_ty.ty_adt_def()
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
|
||||
)
|
||||
{
|
||||
"next().is_none()".into()
|
||||
},
|
||||
"contains" => {
|
||||
if is_contains_sig(cx, parent.hir_id, iter_expr)
|
||||
&& let Some(arg) = args.first()
|
||||
{
|
||||
let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
|
||||
(arg.span, "")
|
||||
} else {
|
||||
(arg.span, "*")
|
||||
};
|
||||
let snip = snippet_with_applicability(cx, span, "??", &mut app);
|
||||
format!("any(|x| x == {prefix}{snip})")
|
||||
"count()".into()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
"is_empty"
|
||||
if is_is_empty_sig(cx, parent.hir_id)
|
||||
&& iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
|
||||
{
|
||||
"next().is_none()".into()
|
||||
},
|
||||
"contains" => {
|
||||
if is_contains_sig(cx, parent.hir_id, iter_expr)
|
||||
&& let Some(arg) = args.first()
|
||||
{
|
||||
let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
|
||||
(arg.span, "")
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
(arg.span, "*")
|
||||
};
|
||||
let snip = snippet_with_applicability(cx, span, "??", &mut app);
|
||||
format!("any(|x| x == {prefix}{snip})")
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_hi(parent.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"replace with",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_hi(parent.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"replace with",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::Local(l) => {
|
||||
if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None) = l.pat.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(collect_expr)
|
||||
&& [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList]
|
||||
.into_iter()
|
||||
.any(|item| is_type_diagnostic_item(cx, ty, item))
|
||||
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
|
||||
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
|
||||
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
|
||||
&& let [iter_call] = &*iter_calls
|
||||
{
|
||||
let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 };
|
||||
walk_block(&mut used_count_visitor, block);
|
||||
if used_count_visitor.count > 1 {
|
||||
return;
|
||||
}
|
||||
},
|
||||
Node::Local(l) => {
|
||||
if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None) = l.pat.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(collect_expr)
|
||||
&& [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList]
|
||||
.into_iter()
|
||||
.any(|item| is_type_diagnostic_item(cx, ty, item))
|
||||
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
|
||||
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
|
||||
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
|
||||
&& let [iter_call] = &*iter_calls
|
||||
{
|
||||
let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 };
|
||||
walk_block(&mut used_count_visitor, block);
|
||||
if used_count_visitor.count > 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suggest replacing iter_call with iter_replacement, and removing stmt
|
||||
let mut span = MultiSpan::from_span(name_span);
|
||||
span.push_span_label(iter_call.span, "the iterator could be used here instead");
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
super::NEEDLESS_COLLECT,
|
||||
collect_expr.hir_id,
|
||||
span,
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
|diag| {
|
||||
let iter_replacement =
|
||||
format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
|
||||
diag.multipart_suggestion(
|
||||
iter_call.get_suggestion_text(),
|
||||
vec![(l.span, String::new()), (iter_call.span, iter_replacement)],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
// Suggest replacing iter_call with iter_replacement, and removing stmt
|
||||
let mut span = MultiSpan::from_span(name_span);
|
||||
span.push_span_label(iter_call.span, "the iterator could be used here instead");
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
super::NEEDLESS_COLLECT,
|
||||
collect_expr.hir_id,
|
||||
span,
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
|diag| {
|
||||
let iter_replacement =
|
||||
format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
|
||||
diag.multipart_suggestion(
|
||||
iter_call.get_suggestion_text(),
|
||||
vec![(l.span, String::new()), (iter_call.span, iter_replacement)],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
|
|
@ -10,17 +10,71 @@ use rustc_span::{sym, Span};
|
|||
|
||||
use super::UNNECESSARY_FALLIBLE_CONVERSIONS;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum SpansKind {
|
||||
TraitFn { trait_span: Span, fn_span: Span },
|
||||
Fn { fn_span: Span },
|
||||
}
|
||||
|
||||
/// What function is being called and whether that call is written as a method call or a function
|
||||
/// call
|
||||
#[derive(Copy, Clone)]
|
||||
#[expect(clippy::enum_variant_names)]
|
||||
enum FunctionKind {
|
||||
/// `T::try_from(U)`
|
||||
TryFromFunction,
|
||||
TryFromFunction(Option<SpansKind>),
|
||||
/// `t.try_into()`
|
||||
TryIntoMethod,
|
||||
/// `U::try_into(t)`
|
||||
TryIntoFunction,
|
||||
TryIntoFunction(Option<SpansKind>),
|
||||
}
|
||||
|
||||
impl FunctionKind {
|
||||
fn appl_sugg(&self, parent_unwrap_call: Option<Span>, primary_span: Span) -> (Applicability, Vec<(Span, String)>) {
|
||||
let Some(unwrap_span) = parent_unwrap_call else {
|
||||
return (Applicability::Unspecified, self.default_sugg(primary_span));
|
||||
};
|
||||
|
||||
match &self {
|
||||
FunctionKind::TryFromFunction(None) | FunctionKind::TryIntoFunction(None) => {
|
||||
(Applicability::Unspecified, self.default_sugg(primary_span))
|
||||
},
|
||||
_ => (
|
||||
Applicability::MachineApplicable,
|
||||
self.machine_applicable_sugg(primary_span, unwrap_span),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_sugg(&self, primary_span: Span) -> Vec<(Span, String)> {
|
||||
let replacement = match *self {
|
||||
FunctionKind::TryFromFunction(_) => "From::from",
|
||||
FunctionKind::TryIntoFunction(_) => "Into::into",
|
||||
FunctionKind::TryIntoMethod => "into",
|
||||
};
|
||||
|
||||
vec![(primary_span, String::from(replacement))]
|
||||
}
|
||||
|
||||
fn machine_applicable_sugg(&self, primary_span: Span, unwrap_span: Span) -> Vec<(Span, String)> {
|
||||
let (trait_name, fn_name) = match self {
|
||||
FunctionKind::TryFromFunction(_) => ("From".to_owned(), "from".to_owned()),
|
||||
FunctionKind::TryIntoFunction(_) | FunctionKind::TryIntoMethod => ("Into".to_owned(), "into".to_owned()),
|
||||
};
|
||||
|
||||
let mut sugg = match *self {
|
||||
FunctionKind::TryFromFunction(Some(spans)) | FunctionKind::TryIntoFunction(Some(spans)) => match spans {
|
||||
SpansKind::TraitFn { trait_span, fn_span } => vec![(trait_span, trait_name), (fn_span, fn_name)],
|
||||
SpansKind::Fn { fn_span } => vec![(fn_span, fn_name)],
|
||||
},
|
||||
FunctionKind::TryIntoMethod => vec![(primary_span, fn_name)],
|
||||
// Or the suggestion is not machine-applicable
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
sugg.push((unwrap_span, String::new()));
|
||||
sugg
|
||||
}
|
||||
}
|
||||
|
||||
fn check<'tcx>(
|
||||
|
|
@ -35,8 +89,8 @@ fn check<'tcx>(
|
|||
&& self_ty != other_ty
|
||||
&& let Some(self_ty) = self_ty.as_type()
|
||||
&& let Some(from_into_trait) = cx.tcx.get_diagnostic_item(match kind {
|
||||
FunctionKind::TryFromFunction => sym::From,
|
||||
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction => sym::Into,
|
||||
FunctionKind::TryFromFunction(_) => sym::From,
|
||||
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => sym::Into,
|
||||
})
|
||||
// If `T: TryFrom<U>` and `T: From<U>` both exist, then that means that the `TryFrom`
|
||||
// _must_ be from the blanket impl and cannot have been manually implemented
|
||||
|
|
@ -45,49 +99,37 @@ fn check<'tcx>(
|
|||
&& implements_trait(cx, self_ty, from_into_trait, &[other_ty])
|
||||
&& let Some(other_ty) = other_ty.as_type()
|
||||
{
|
||||
// Extend the span to include the unwrap/expect call:
|
||||
// `foo.try_into().expect("..")`
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
//
|
||||
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
|
||||
// so that can be machine-applicable
|
||||
let parent_unwrap_call = get_parent_expr(cx, expr).and_then(|parent| {
|
||||
if let ExprKind::MethodCall(path, .., span) = parent.kind
|
||||
&& let sym::unwrap | sym::expect = path.ident.name
|
||||
{
|
||||
Some(span)
|
||||
// include `.` before `unwrap`/`expect`
|
||||
Some(span.with_lo(expr.span.hi()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let (source_ty, target_ty, sugg, span, applicability) = match kind {
|
||||
FunctionKind::TryIntoMethod if let Some(unwrap_span) = parent_unwrap_call => {
|
||||
// Extend the span to include the unwrap/expect call:
|
||||
// `foo.try_into().expect("..")`
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
//
|
||||
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
|
||||
// so that can be machine-applicable
|
||||
|
||||
(
|
||||
self_ty,
|
||||
other_ty,
|
||||
"into()",
|
||||
primary_span.with_hi(unwrap_span.hi()),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
},
|
||||
FunctionKind::TryFromFunction => (
|
||||
other_ty,
|
||||
self_ty,
|
||||
"From::from",
|
||||
primary_span,
|
||||
Applicability::Unspecified,
|
||||
),
|
||||
FunctionKind::TryIntoFunction => (
|
||||
self_ty,
|
||||
other_ty,
|
||||
"Into::into",
|
||||
primary_span,
|
||||
Applicability::Unspecified,
|
||||
),
|
||||
FunctionKind::TryIntoMethod => (self_ty, other_ty, "into", primary_span, Applicability::Unspecified),
|
||||
// If there is an unwrap/expect call, extend the span to include the call
|
||||
let span = if let Some(unwrap_call) = parent_unwrap_call {
|
||||
primary_span.with_hi(unwrap_call.hi())
|
||||
} else {
|
||||
primary_span
|
||||
};
|
||||
|
||||
let (source_ty, target_ty) = match kind {
|
||||
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => (self_ty, other_ty),
|
||||
FunctionKind::TryFromFunction(_) => (other_ty, self_ty),
|
||||
};
|
||||
|
||||
let (applicability, sugg) = kind.appl_sugg(parent_unwrap_call, primary_span);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNNECESSARY_FALLIBLE_CONVERSIONS,
|
||||
|
|
@ -97,7 +139,7 @@ fn check<'tcx>(
|
|||
with_forced_trimmed_paths!({
|
||||
diag.note(format!("converting `{source_ty}` to `{target_ty}` cannot fail"));
|
||||
});
|
||||
diag.span_suggestion(span, "use", sugg, applicability);
|
||||
diag.multipart_suggestion("use", sugg, applicability);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -125,13 +167,30 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Exp
|
|||
&& let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id()
|
||||
&& let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id)
|
||||
{
|
||||
let qpath_spans = match qpath {
|
||||
QPath::Resolved(_, path) => {
|
||||
if let [trait_seg, fn_seg] = path.segments {
|
||||
Some(SpansKind::TraitFn {
|
||||
trait_span: trait_seg.ident.span,
|
||||
fn_span: fn_seg.ident.span,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
QPath::TypeRelative(_, seg) => Some(SpansKind::Fn {
|
||||
fn_span: seg.ident.span,
|
||||
}),
|
||||
QPath::LangItem(_, _) => unreachable!("`TryFrom` and `TryInto` are not lang items"),
|
||||
};
|
||||
|
||||
check(
|
||||
cx,
|
||||
expr,
|
||||
cx.typeck_results().node_args(callee.hir_id),
|
||||
match cx.tcx.get_diagnostic_name(trait_def_id) {
|
||||
Some(sym::TryFrom) => FunctionKind::TryFromFunction,
|
||||
Some(sym::TryInto) => FunctionKind::TryIntoFunction,
|
||||
Some(sym::TryFrom) => FunctionKind::TryFromFunction(qpath_spans),
|
||||
Some(sym::TryInto) => FunctionKind::TryIntoFunction(qpath_spans),
|
||||
_ => return,
|
||||
},
|
||||
callee.span,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
|
|||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs};
|
||||
use clippy_utils::ty::{
|
||||
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
|
||||
};
|
||||
use clippy_utils::visitors::find_all_ret_expressions;
|
||||
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -16,7 +18,8 @@ use rustc_lint::LateContext;
|
|||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
|
||||
use rustc_middle::ty::{
|
||||
self, ClauseKind, GenericArg, GenericArgKind, GenericArgsRef, ParamTy, ProjectionPredicate, TraitPredicate, Ty,
|
||||
self, ClauseKind, GenericArg, GenericArgKind, GenericArgsRef, ImplPolarity, ParamTy, ProjectionPredicate,
|
||||
TraitPredicate, Ty,
|
||||
};
|
||||
use rustc_span::{sym, Symbol};
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
|
|
@ -53,6 +56,8 @@ pub fn check<'tcx>(
|
|||
}
|
||||
check_other_call_arg(cx, expr, method_name, receiver);
|
||||
}
|
||||
} else {
|
||||
check_borrow_predicate(cx, expr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -590,3 +595,92 @@ fn is_to_string_on_string_like<'a>(
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
is_type_diagnostic_item(cx, ty, sym::HashSet)
|
||||
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
|
||||
|| is_type_diagnostic_item(cx, ty, sym::BTreeMap)
|
||||
|| is_type_diagnostic_item(cx, ty, sym::BTreeSet)
|
||||
}
|
||||
|
||||
fn is_str_and_string(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
|
||||
original_arg_ty.is_str() && is_type_lang_item(cx, arg_ty, LangItem::String)
|
||||
}
|
||||
|
||||
fn is_slice_and_vec(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
|
||||
(original_arg_ty.is_slice() || original_arg_ty.is_array() || original_arg_ty.is_array_slice())
|
||||
&& is_type_diagnostic_item(cx, arg_ty, sym::Vec)
|
||||
}
|
||||
|
||||
// This function will check the following:
|
||||
// 1. The argument is a non-mutable reference.
|
||||
// 2. It calls `to_owned()`, `to_string()` or `to_vec()`.
|
||||
// 3. That the method is called on `String` or on `Vec` (only types supported for the moment).
|
||||
fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx>) {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = arg.kind
|
||||
&& let ExprKind::MethodCall(method_path, caller, &[], _) = expr.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let method_name = method_path.ident.name.as_str()
|
||||
&& match method_name {
|
||||
"to_owned" => cx.tcx.is_diagnostic_item(sym::to_owned_method, method_def_id),
|
||||
"to_string" => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
|
||||
"to_vec" => cx
|
||||
.tcx
|
||||
.impl_of_method(method_def_id)
|
||||
.filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice())
|
||||
.is_some(),
|
||||
_ => false,
|
||||
}
|
||||
&& let original_arg_ty = cx.typeck_results().node_type(caller.hir_id).peel_refs()
|
||||
&& let arg_ty = cx.typeck_results().expr_ty(arg)
|
||||
&& let ty::Ref(_, arg_ty, Mutability::Not) = arg_ty.kind()
|
||||
// FIXME: try to fix `can_change_type` to make it work in this case.
|
||||
// && can_change_type(cx, caller, *arg_ty)
|
||||
&& let arg_ty = arg_ty.peel_refs()
|
||||
// For now we limit this lint to `String` and `Vec`.
|
||||
&& (is_str_and_string(cx, arg_ty, original_arg_ty) || is_slice_and_vec(cx, arg_ty, original_arg_ty))
|
||||
&& let Some(snippet) = snippet_opt(cx, caller.span)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_TO_OWNED,
|
||||
arg.span,
|
||||
&format!("unnecessary use of `{method_name}`"),
|
||||
"replace it with",
|
||||
if original_arg_ty.is_array() {
|
||||
format!("{snippet}.as_slice()")
|
||||
} else {
|
||||
snippet
|
||||
},
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// In std "map types", the getters all expect a `Borrow<Key>` generic argument. So in here, we
|
||||
// check that:
|
||||
// 1. This is a method with only one argument that doesn't come from a trait.
|
||||
// 2. That it has `Borrow` in its generic predicates.
|
||||
// 3. `Self` is a std "map type" (ie `HashSet`, `HashMap`, BTreeSet`, `BTreeMap`).
|
||||
fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& cx.tcx.trait_of_item(method_def_id).is_none()
|
||||
&& let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow)
|
||||
&& cx.tcx.predicates_of(method_def_id).predicates.iter().any(|(pred, _)| {
|
||||
if let ClauseKind::Trait(trait_pred) = pred.kind().skip_binder()
|
||||
&& trait_pred.polarity == ImplPolarity::Positive
|
||||
&& trait_pred.trait_ref.def_id == borrow_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
&& let caller_ty = cx.typeck_results().expr_ty(caller)
|
||||
// For now we limit it to "map types".
|
||||
&& is_a_std_map_type(cx, caller_ty)
|
||||
{
|
||||
check_if_applicable_to_argument(cx, &arg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use clippy_utils::diagnostics::span_lint;
|
|||
use clippy_utils::is_from_proc_macro;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::intravisit::{walk_item, Visitor};
|
||||
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind};
|
||||
use rustc_hir::intravisit::{walk_item, walk_trait_item, Visitor};
|
||||
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, UsePath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -53,7 +53,7 @@ impl MinIdentChars {
|
|||
&& str.len() <= self.min_ident_chars_threshold as usize
|
||||
&& !str.starts_with('_')
|
||||
&& !str.is_empty()
|
||||
&& self.allowed_idents_below_min_chars.get(&str.to_owned()).is_none()
|
||||
&& !self.allowed_idents_below_min_chars.contains(str)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +66,14 @@ impl LateLintPass<'_> for MinIdentChars {
|
|||
walk_item(&mut IdentVisitor { conf: self, cx }, item);
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
|
||||
if self.min_ident_chars_threshold == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
walk_trait_item(&mut IdentVisitor { conf: self, cx }, item);
|
||||
}
|
||||
|
||||
// This is necessary as `Node::Pat`s are not visited in `visit_id`. :/
|
||||
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
|
||||
if let PatKind::Binding(_, _, ident, ..) = pat.kind
|
||||
|
|
@ -105,11 +113,26 @@ impl Visitor<'_> for IdentVisitor<'_, '_> {
|
|||
|
||||
let str = ident.as_str();
|
||||
if conf.is_ident_too_short(cx, str, ident.span) {
|
||||
if let Node::Item(item) = node
|
||||
&& let ItemKind::Use(..) = item.kind
|
||||
// Check whether the node is part of a `use` statement. We don't want to emit a warning if the user
|
||||
// has no control over the type.
|
||||
let usenode = opt_as_use_node(node).or_else(|| {
|
||||
cx.tcx
|
||||
.hir()
|
||||
.parent_iter(hir_id)
|
||||
.find_map(|(_, node)| opt_as_use_node(node))
|
||||
});
|
||||
|
||||
// If the name of the identifier is the same as the one of the imported item, this means that we
|
||||
// found a `use foo::bar`. We can early-return to not emit the warning.
|
||||
// If however the identifier is different, this means it is an alias (`use foo::bar as baz`). In
|
||||
// this case, we need to emit the warning for `baz`.
|
||||
if let Some(imported_item_path) = usenode
|
||||
&& let Some(Res::Def(_, imported_item_defid)) = imported_item_path.res.first()
|
||||
&& cx.tcx.item_name(*imported_item_defid).as_str() == str
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// `struct Awa<T>(T)`
|
||||
// ^
|
||||
if let Node::PathSegment(path) = node {
|
||||
|
|
@ -160,3 +183,16 @@ fn emit_min_ident_chars(conf: &MinIdentChars, cx: &impl LintContext, ident: &str
|
|||
};
|
||||
span_lint(cx, MIN_IDENT_CHARS, span, &help);
|
||||
}
|
||||
|
||||
/// Attempt to convert the node to an [`ItemKind::Use`] node.
|
||||
///
|
||||
/// If it is, return the [`UsePath`] contained within.
|
||||
fn opt_as_use_node(node: Node<'_>) -> Option<&'_ UsePath<'_>> {
|
||||
if let Node::Item(item) = node
|
||||
&& let ItemKind::Use(path, _) = item.kind
|
||||
{
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
|
|||
fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
|
||||
matches!(
|
||||
get_parent_node(cx.tcx, id),
|
||||
Some(Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }))
|
||||
Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,11 +236,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
// #11182; do not lint if mutability is required elsewhere
|
||||
if let ExprKind::Path(..) = expr.kind
|
||||
&& let Some(parent) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
&& let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(expr).kind()
|
||||
&& let Some(def_id) = def_id.as_local()
|
||||
{
|
||||
if let Node::Expr(e) = parent
|
||||
if let Node::Expr(e) = get_parent_node(cx.tcx, expr.hir_id)
|
||||
&& let ExprKind::Call(call, _) = e.kind
|
||||
&& call.hir_id == expr.hir_id
|
||||
{
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
|
|||
if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
|
||||
let name = impl_item.ident.name;
|
||||
let id = impl_item.owner_id;
|
||||
if sig.header.constness == hir::Constness::Const {
|
||||
// can't be implemented by default
|
||||
return;
|
||||
}
|
||||
if sig.header.unsafety == hir::Unsafety::Unsafe {
|
||||
// can't be implemented for unsafe new
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ use clippy_utils::{any_parent_is_automatically_derived, get_parent_node, is_lint
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, Node, PatKind, Stmt,
|
||||
StmtKind, UnsafeSource,
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind,
|
||||
Stmt, StmtKind, UnsafeSource,
|
||||
};
|
||||
use rustc_infer::infer::TyCtxtInferExt as _;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -43,10 +43,6 @@ declare_clippy_lint! {
|
|||
/// executed. However, as they have no effect and shouldn't be used further on, all they
|
||||
/// do is make the code less readable.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Further usage of this variable is not checked, which can lead to false positives if it is
|
||||
/// used later in the code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// let _i_serve_no_purpose = 1;
|
||||
|
|
@ -144,7 +140,7 @@ impl NoEffect {
|
|||
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
|
||||
if let Node::Item(item) = parent.1
|
||||
&& let ItemKind::Fn(..) = item.kind
|
||||
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
|
||||
&& let Node::Block(block) = get_parent_node(cx.tcx, stmt.hir_id)
|
||||
&& let [.., final_stmt] = block.stmts
|
||||
&& final_stmt.hir_id == stmt.hir_id
|
||||
{
|
||||
|
|
@ -180,6 +176,7 @@ impl NoEffect {
|
|||
}
|
||||
} else if let StmtKind::Local(local) = stmt.kind {
|
||||
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id)
|
||||
&& !matches!(local.source, LocalSource::AsyncFn)
|
||||
&& let Some(init) = local.init
|
||||
&& local.els.is_none()
|
||||
&& !local.pat.span.from_expansion()
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL
|
|||
impl LateLintPass<'_> for NonCanonicalImpls {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
let Some(Node::Item(item)) = get_parent_node(cx.tcx, impl_item.hir_id()) else {
|
||||
let Node::Item(item) = get_parent_node(cx.tcx, impl_item.hir_id()) else {
|
||||
return;
|
||||
};
|
||||
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
|
|||
|
||||
// this list contains lists of names that are allowed to be similar
|
||||
// the assumption is that no name is ever contained in multiple lists.
|
||||
#[rustfmt::skip]
|
||||
const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
|
||||
&["parsed", "parser"],
|
||||
&["lhs", "rhs"],
|
||||
|
|
@ -132,6 +131,14 @@ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
|
|||
&["iter", "item"],
|
||||
];
|
||||
|
||||
/// Characters that look visually similar
|
||||
const SIMILAR_CHARS: &[(char, char)] = &[('l', 'i'), ('l', '1'), ('i', '1'), ('u', 'v')];
|
||||
|
||||
/// Return true if two characters are visually similar
|
||||
fn chars_are_similar(a: char, b: char) -> bool {
|
||||
a == b || SIMILAR_CHARS.contains(&(a, b)) || SIMILAR_CHARS.contains(&(b, a))
|
||||
}
|
||||
|
||||
struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
|
||||
|
||||
impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
||||
|
|
@ -189,7 +196,6 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_ident(&mut self, ident: Ident) {
|
||||
let interned_name = ident.name.as_str();
|
||||
if interned_name.chars().any(char::is_uppercase) {
|
||||
|
|
@ -219,71 +225,28 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
|||
if allowed_to_be_similar(interned_name, existing_name.exemptions) {
|
||||
continue;
|
||||
}
|
||||
match existing_name.len.cmp(&count) {
|
||||
Ordering::Greater => {
|
||||
if existing_name.len - count != 1
|
||||
|| levenstein_not_1(interned_name, existing_name.interned.as_str())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Ordering::Less => {
|
||||
if count - existing_name.len != 1
|
||||
|| levenstein_not_1(existing_name.interned.as_str(), interned_name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Ordering::Equal => {
|
||||
let mut interned_chars = interned_name.chars();
|
||||
let interned_str = existing_name.interned.as_str();
|
||||
let mut existing_chars = interned_str.chars();
|
||||
let first_i = interned_chars.next().expect("we know we have at least one char");
|
||||
let first_e = existing_chars.next().expect("we know we have at least one char");
|
||||
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
|
||||
|
||||
if eq_or_numeric((first_i, first_e)) {
|
||||
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
|
||||
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
|
||||
if eq_or_numeric((last_i, last_e)) {
|
||||
if interned_chars
|
||||
.zip(existing_chars)
|
||||
.filter(|&ie| !eq_or_numeric(ie))
|
||||
.count()
|
||||
!= 1
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let second_last_i = interned_chars
|
||||
.next_back()
|
||||
.expect("we know we have at least three chars");
|
||||
let second_last_e = existing_chars
|
||||
.next_back()
|
||||
.expect("we know we have at least three chars");
|
||||
if !eq_or_numeric((second_last_i, second_last_e))
|
||||
|| second_last_i == '_'
|
||||
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
|
||||
{
|
||||
// allowed similarity foo_x, foo_y
|
||||
// or too many chars differ (foo_x, boo_y) or (foox, booy)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let second_i = interned_chars.next().expect("we know we have at least two chars");
|
||||
let second_e = existing_chars.next().expect("we know we have at least two chars");
|
||||
if !eq_or_numeric((second_i, second_e))
|
||||
|| second_i == '_'
|
||||
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
|
||||
{
|
||||
// allowed similarity x_foo, y_foo
|
||||
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
let existing_str = existing_name.interned.as_str();
|
||||
|
||||
// The first char being different is usually enough to set identifiers apart, as long
|
||||
// as the characters aren't too similar.
|
||||
if !chars_are_similar(
|
||||
interned_name.chars().next().expect("len >= 1"),
|
||||
existing_str.chars().next().expect("len >= 1"),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dissimilar = match existing_name.len.cmp(&count) {
|
||||
Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
|
||||
Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),
|
||||
Ordering::Equal => Self::equal_length_strs_not_similar(interned_name, existing_str),
|
||||
};
|
||||
|
||||
if dissimilar {
|
||||
continue;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
self.0.cx,
|
||||
SIMILAR_NAMES,
|
||||
|
|
@ -302,6 +265,57 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
|||
len: count,
|
||||
});
|
||||
}
|
||||
|
||||
fn equal_length_strs_not_similar(interned_name: &str, existing_name: &str) -> bool {
|
||||
let mut interned_chars = interned_name.chars();
|
||||
let mut existing_chars = existing_name.chars();
|
||||
let first_i = interned_chars.next().expect("we know we have at least one char");
|
||||
let first_e = existing_chars.next().expect("we know we have at least one char");
|
||||
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
|
||||
|
||||
if eq_or_numeric((first_i, first_e)) {
|
||||
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
|
||||
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
|
||||
if eq_or_numeric((last_i, last_e)) {
|
||||
if interned_chars
|
||||
.zip(existing_chars)
|
||||
.filter(|&ie| !eq_or_numeric(ie))
|
||||
.count()
|
||||
!= 1
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
let second_last_i = interned_chars
|
||||
.next_back()
|
||||
.expect("we know we have at least three chars");
|
||||
let second_last_e = existing_chars
|
||||
.next_back()
|
||||
.expect("we know we have at least three chars");
|
||||
if !eq_or_numeric((second_last_i, second_last_e))
|
||||
|| second_last_i == '_'
|
||||
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
|
||||
{
|
||||
// allowed similarity foo_x, foo_y
|
||||
// or too many chars differ (foo_x, boo_y) or (foox, booy)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let second_i = interned_chars.next().expect("we know we have at least two chars");
|
||||
let second_e = existing_chars.next().expect("we know we have at least two chars");
|
||||
if !eq_or_numeric((second_i, second_e))
|
||||
|| second_i == '_'
|
||||
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
|
||||
{
|
||||
// allowed similarity x_foo, y_foo
|
||||
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {
|
||||
|
|
|
|||
|
|
@ -228,23 +228,23 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
|
|||
// `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions.
|
||||
// It can't be renamed, and it can't be removed without removing it from multiple functions.
|
||||
let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) {
|
||||
Some(Node::Item(i)) => (i.owner_id.to_def_id(), FnKind::Fn, 0),
|
||||
Some(Node::TraitItem(&TraitItem {
|
||||
Node::Item(i) => (i.owner_id.to_def_id(), FnKind::Fn, 0),
|
||||
Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(ref sig, _),
|
||||
owner_id,
|
||||
..
|
||||
})) => (
|
||||
}) => (
|
||||
owner_id.to_def_id(),
|
||||
FnKind::TraitFn,
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
),
|
||||
Some(Node::ImplItem(&ImplItem {
|
||||
Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(ref sig, _),
|
||||
owner_id,
|
||||
..
|
||||
})) => {
|
||||
}) => {
|
||||
#[allow(trivial_casts)]
|
||||
if let Some(Node::Item(item)) = get_parent_node(cx.tcx, owner_id.into())
|
||||
if let Node::Item(item) = get_parent_node(cx.tcx, owner_id.into())
|
||||
&& let Some(trait_ref) = cx
|
||||
.tcx
|
||||
.impl_trait_ref(item.owner_id)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use rustc_span::Span;
|
|||
|
||||
use super::DOUBLE_COMPARISONS;
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
|
||||
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
|
||||
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use rustc_middle::ty::{self, Ty};
|
|||
|
||||
use super::OP_REF;
|
||||
|
||||
#[expect(clippy::similar_names, clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(crate) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_config::types::PubUnderscoreFieldsBehaviour;
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::is_path_lang_item;
|
||||
use rustc_hir::{FieldDef, Item, ItemKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -69,13 +69,15 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
|
|||
// We ignore fields that are `PhantomData`.
|
||||
&& !is_path_lang_item(cx, field.ty, LangItem::PhantomData)
|
||||
{
|
||||
span_lint_and_help(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
PUB_UNDERSCORE_FIELDS,
|
||||
field.hir_id,
|
||||
field.vis_span.to(field.ident.span),
|
||||
"field marked as public but also inferred as unused because it's prefixed with `_`",
|
||||
None,
|
||||
"consider removing the underscore, or making the field private",
|
||||
|diag| {
|
||||
diag.help("consider removing the underscore, or making the field private");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ impl QuestionMark {
|
|||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
|
||||
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
|
||||
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Node::Stmt(_));
|
||||
let sugg = format!(
|
||||
"{receiver_str}{}?{}",
|
||||
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::rustc_lint::LintContext;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{get_parent_expr, get_parent_node};
|
||||
use hir::Param;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -200,11 +200,11 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
|||
hint = hint.asyncify();
|
||||
}
|
||||
|
||||
let is_in_fn_call_arg =
|
||||
clippy_utils::get_parent_node(cx.tcx, expr.hir_id).is_some_and(|x| match x {
|
||||
Node::Expr(expr) => matches!(expr.kind, hir::ExprKind::Call(_, _)),
|
||||
_ => false,
|
||||
});
|
||||
let is_in_fn_call_arg = if let Node::Expr(expr) = get_parent_node(cx.tcx, expr.hir_id) {
|
||||
matches!(expr.kind, hir::ExprKind::Call(_, _))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// avoid clippy::double_parens
|
||||
if !is_in_fn_call_arg {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ struct ExistingName {
|
|||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
||||
let mut map = FxHashMap::<Res, ExistingName>::default();
|
||||
|
||||
|
|
@ -75,24 +74,23 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
|
||||
match of_trait {
|
||||
Some(trait_ref) => {
|
||||
let mut methods_in_trait: BTreeSet<Symbol> =
|
||||
if let Node::TraitRef(TraitRef { path, .. }) =
|
||||
cx.tcx.hir_node(trait_ref.hir_ref_id)
|
||||
&& let Res::Def(DefKind::Trait, did) = path.res
|
||||
{
|
||||
// FIXME: if
|
||||
// `rustc_middle::ty::assoc::AssocItems::items` is public,
|
||||
// we can iterate its keys instead of `in_definition_order`,
|
||||
// which's more efficient
|
||||
cx.tcx
|
||||
.associated_items(did)
|
||||
.in_definition_order()
|
||||
.filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
|
||||
.map(|assoc_item| assoc_item.name)
|
||||
.collect()
|
||||
} else {
|
||||
BTreeSet::new()
|
||||
};
|
||||
let mut methods_in_trait: BTreeSet<Symbol> = if let Node::TraitRef(TraitRef { path, .. }) =
|
||||
cx.tcx.hir_node(trait_ref.hir_ref_id)
|
||||
&& let Res::Def(DefKind::Trait, did) = path.res
|
||||
{
|
||||
// FIXME: if
|
||||
// `rustc_middle::ty::assoc::AssocItems::items` is public,
|
||||
// we can iterate its keys instead of `in_definition_order`,
|
||||
// which's more efficient
|
||||
cx.tcx
|
||||
.associated_items(did)
|
||||
.in_definition_order()
|
||||
.filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
|
||||
.map(|assoc_item| assoc_item.name)
|
||||
.collect()
|
||||
} else {
|
||||
BTreeSet::new()
|
||||
};
|
||||
|
||||
let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
|
||||
if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) {
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
|
|||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
let span = match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Block(&Block {
|
||||
Node::Block(&Block {
|
||||
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
|
||||
span,
|
||||
..
|
||||
})) if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => span,
|
||||
}) if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => span,
|
||||
_ => expr.span,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
@ -53,6 +54,8 @@ impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
|
|||
}) = it.kind
|
||||
&& let Some(trait_did) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
|
||||
&& let Some(display_did) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& !implements_trait(cx, cx.tcx.type_of(it.owner_id).instantiate_identity(), display_did, &[])
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -153,10 +153,7 @@ fn all_bindings_are_for_conv<'tcx>(
|
|||
let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::<Option<Vec<_>>>() else {
|
||||
return false;
|
||||
};
|
||||
let local_parents = locals
|
||||
.iter()
|
||||
.map(|l| cx.tcx.parent_hir_node(*l))
|
||||
.collect::<Vec<_>>();
|
||||
let local_parents = locals.iter().map(|l| cx.tcx.parent_hir_node(*l)).collect::<Vec<_>>();
|
||||
|
||||
local_parents
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -340,47 +340,43 @@ fn block_parents_have_safety_comment(
|
|||
cx: &LateContext<'_>,
|
||||
id: hir::HirId,
|
||||
) -> bool {
|
||||
if let Some(node) = get_parent_node(cx.tcx, id) {
|
||||
let (span, hir_id) = match node {
|
||||
Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Local(hir::Local { span, hir_id, .. })) => (*span, *hir_id),
|
||||
Some(Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
})) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => {
|
||||
if is_branchy(expr) {
|
||||
return false;
|
||||
}
|
||||
(expr.span, expr.hir_id)
|
||||
},
|
||||
},
|
||||
Node::Stmt(hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Local(hir::Local { span, hir_id, .. })
|
||||
| hir::StmtKind::Expr(hir::Expr { span, hir_id, .. })
|
||||
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
|
||||
..
|
||||
})
|
||||
| Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
let (span, hir_id) = match get_parent_node(cx.tcx, id) {
|
||||
Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => return false,
|
||||
};
|
||||
// if unsafe block is part of a let/const/static statement,
|
||||
// and accept_comment_above_statement is set to true
|
||||
// we accept the safety comment in the line the precedes this statement.
|
||||
accept_comment_above_statement
|
||||
&& span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
_ => {
|
||||
if is_branchy(expr) {
|
||||
return false;
|
||||
}
|
||||
(expr.span, expr.hir_id)
|
||||
},
|
||||
},
|
||||
Node::Stmt(hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Local(hir::Local { span, hir_id, .. })
|
||||
| hir::StmtKind::Expr(hir::Expr { span, hir_id, .. })
|
||||
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
|
||||
..
|
||||
})
|
||||
| Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => return false,
|
||||
};
|
||||
// if unsafe block is part of a let/const/static statement,
|
||||
// and accept_comment_above_statement is set to true
|
||||
// we accept the safety comment in the line the precedes this statement.
|
||||
accept_comment_above_statement
|
||||
&& span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
|
||||
}
|
||||
|
||||
/// Extends `span` to also include its attributes, then checks if that span has a safety comment.
|
||||
|
|
@ -449,53 +445,49 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
|
|||
if item.span.ctxt() != SyntaxContext::root() {
|
||||
return HasSafetyComment::No;
|
||||
}
|
||||
if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
|
||||
let comment_start = match parent_node {
|
||||
Node::Crate(parent_mod) => {
|
||||
comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
|
||||
},
|
||||
Node::Item(parent_item) => {
|
||||
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
|
||||
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
|
||||
} else {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
Node::Stmt(stmt) => {
|
||||
if let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) {
|
||||
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
|
||||
} else {
|
||||
// Problem getting the parent node. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let comment_start = match get_parent_node(cx.tcx, item.hir_id()) {
|
||||
Node::Crate(parent_mod) => comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item),
|
||||
Node::Item(parent_item) => {
|
||||
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
|
||||
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
|
||||
} else {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
},
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
}
|
||||
},
|
||||
Node::Stmt(stmt) => {
|
||||
if let Node::Block(block) = get_parent_node(cx.tcx, stmt.hir_id) {
|
||||
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
// Problem getting the parent node. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
},
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
HasSafetyComment::Maybe
|
||||
}
|
||||
|
|
@ -512,32 +504,30 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
|
|||
return HasSafetyComment::No;
|
||||
}
|
||||
|
||||
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
|
||||
let comment_start = match parent_node {
|
||||
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
|
||||
_ => return HasSafetyComment::Maybe,
|
||||
};
|
||||
let comment_start = match get_parent_node(cx.tcx, hir_id) {
|
||||
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
|
||||
_ => return HasSafetyComment::Maybe,
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
HasSafetyComment::Maybe
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -
|
|||
return false;
|
||||
}
|
||||
while let Some(id) = locals_to_check.pop() {
|
||||
if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
|
||||
if let Node::Local(l) = get_parent_node(cx.tcx, id) {
|
||||
if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1005,8 +1005,6 @@ fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -
|
|||
}
|
||||
|
||||
fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
|
||||
let map = cx.tcx.hir();
|
||||
|
||||
match cx.tcx.parent_hir_node(hir_id) {
|
||||
hir::Node::Local(local) => Some(local),
|
||||
hir::Node::Pat(pattern) => get_parent_local_hir_id(cx, pattern.hir_id),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue