Merge commit '99edcadfd5' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-12-25 23:24:43 +01:00
parent 7a3097f18d
commit 54e9e8cd38
177 changed files with 6336 additions and 967 deletions

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -24,7 +24,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -20,9 +20,9 @@ jobs:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version: '24.x'
- name: Install remark
run: npm install remark-cli remark-lint remark-lint-maximum-line-length@^3.1.3 remark-preset-lint-recommended remark-gfm

View file

@ -6916,6 +6916,7 @@ Released 2018-09-13
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
[`same_length_and_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_length_and_capacity
[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
[`seek_from_current`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current

View file

@ -10,6 +10,7 @@ clap = { version = "4.4", features = ["derive"] }
indoc = "1.0"
itertools = "0.12"
opener = "0.7"
rustc-literal-escaper = "0.0.5"
walkdir = "2.3"
[package.metadata.rust-analyzer]

View file

@ -22,7 +22,6 @@ extern crate rustc_arena;
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
extern crate rustc_driver;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;
pub mod deprecate_lint;
pub mod dogfood;

View file

@ -3,6 +3,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node};
use clippy_utils::msrvs::Msrv;
use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::{is_inside_always_const_context, msrvs};
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind};
@ -50,6 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
_ => return,
}
&& let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn)
&& is_const_evaluatable(cx, condition)
&& let Some((Constant::Bool(assert_val), const_src)) =
ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt())
&& let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id)

View file

@ -433,7 +433,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio
},
ExprKind::MethodCall(path, receiver, args, _) => {
let type_of_receiver = cx.typeck_results().expr_ty(receiver);
if !type_of_receiver.is_diag_item(cx, sym::Option) && !type_of_receiver.is_diag_item(cx, sym::Result) {
if !matches!(type_of_receiver.opt_diag_name(cx), Some(sym::Option | sym::Result)) {
return None;
}
METHODS_WITH_NEGATION

View file

@ -23,15 +23,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
let cast_to_f64 = to_nbits == 64;
let mantissa_nbits = if cast_to_f64 { 52 } else { 23 };
let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64;
let arch_dependent_str = "on targets with 64-bit wide pointers ";
let from_nbits_str = if arch_dependent {
"64".to_owned()
} else if is_isize_or_usize(cast_from) {
// FIXME: handle 16 bits `usize` type
"32 or 64".to_owned()
let has_width = if is_isize_or_usize(cast_from) {
"can be up to 64 bits wide depending on the target architecture".to_owned()
} else {
from_nbits.to_string()
format!("is {from_nbits} bits wide")
};
span_lint(
@ -39,13 +35,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
CAST_PRECISION_LOSS,
expr.span,
format!(
"casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \
but `{1}`'s mantissa is only {4} bits wide)",
cast_from,
if cast_to_f64 { "f64" } else { "f32" },
if arch_dependent { arch_dependent_str } else { "" },
from_nbits_str,
mantissa_nbits
"casting `{cast_from}` to `{cast_to}` may cause a loss of precision \
(`{cast_from}` {has_width}, \
but `{cast_to}`'s mantissa is only {mantissa_nbits} bits wide)",
),
);
}

View file

@ -836,7 +836,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.93.0"]
pub NEEDLESS_TYPE_CAST,
pedantic,
nursery,
"binding defined with one type but always cast to another"
}

View file

@ -1,6 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use clippy_utils::visitors::{Descend, for_each_expr, for_each_expr_without_closures};
use core::ops::ControlFlow;
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -14,6 +16,7 @@ use super::NEEDLESS_TYPE_CAST;
struct BindingInfo<'a> {
source_ty: Ty<'a>,
ty_span: Span,
init: Option<&'a Expr<'a>>,
}
struct UsageInfo<'a> {
@ -73,6 +76,7 @@ fn collect_binding_from_let<'a>(
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
init: Some(let_expr.init),
},
);
}
@ -103,6 +107,7 @@ fn collect_binding_from_local<'a>(
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
init: let_stmt.init,
},
);
}
@ -182,12 +187,7 @@ fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
.iter()
.any(|p| p.kind.is_ty_or_const())
};
match res {
Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id),
// Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))),
_ => false,
}
cx.tcx.res_generics_def_id(res).is_some_and(has_type_params)
}
fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool {
@ -234,6 +234,18 @@ fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) ->
}
}
fn can_coerce_to_target_type(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Lit(lit) => matches!(
lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
),
ExprKind::Unary(rustc_hir::UnOp::Neg, inner) => can_coerce_to_target_type(inner),
ExprKind::Binary(_, lhs, rhs) => can_coerce_to_target_type(lhs) && can_coerce_to_target_type(rhs),
_ => false,
}
}
fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) {
let mut usages = Vec::new();
@ -274,7 +286,19 @@ fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId
return;
};
span_lint_and_sugg(
// Don't lint if there's exactly one use and the initializer cannot be coerced to the
// target type (i.e., would require an explicit cast). In such cases, the fix would add
// a cast to the initializer rather than eliminating one - the cast isn't truly "needless."
// See: https://github.com/rust-lang/rust-clippy/issues/16240
if usages.len() == 1
&& binding_info
.init
.is_some_and(|init| !can_coerce_to_target_type(init) && !init.span.from_expansion())
{
return;
}
span_lint_and_then(
cx,
NEEDLESS_TYPE_CAST,
binding_info.ty_span,
@ -282,8 +306,28 @@ fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId
"this binding is defined as `{}` but is always cast to `{}`",
binding_info.source_ty, first_target
),
"consider defining it as",
first_target.to_string(),
Applicability::MaybeIncorrect,
|diag| {
if let Some(init) = binding_info
.init
.filter(|i| !can_coerce_to_target_type(i) && !i.span.from_expansion())
{
let sugg = Sugg::hir(cx, init, "..").as_ty(first_target);
diag.multipart_suggestion(
format!("consider defining it as `{first_target}` and casting the initializer"),
vec![
(binding_info.ty_span, first_target.to_string()),
(init.span, sugg.to_string()),
],
Applicability::MachineApplicable,
);
} else {
diag.span_suggestion(
binding_info.ty_span,
"consider defining it as",
first_target.to_string(),
Applicability::MachineApplicable,
);
}
},
);
}

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{ExprUseNode, expr_use_ctxt, std_or_core};
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_expr_temporary_value, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability, Ty, TyKind};
use rustc_hir::{Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
@ -23,10 +23,18 @@ pub(super) fn check<'tcx>(
if matches!(cast_from.kind(), ty::Ref(..))
&& let ty::RawPtr(_, to_mutbl) = cast_to.kind()
&& let use_cx = expr_use_ctxt(cx, expr)
// TODO: only block the lint if `cast_expr` is a temporary
&& !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_))
&& let Some(std_or_core) = std_or_core(cx)
{
if let ExprKind::AddrOf(_, _, addr_inner) = cast_expr.kind
&& is_expr_temporary_value(cx, addr_inner)
&& matches!(
use_cx.use_node(cx),
ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)
)
{
return;
}
let fn_name = match to_mutbl {
Mutability::Not => "from_ref",
Mutability::Mut => "from_mut",

View file

@ -76,7 +76,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.51.0"]
pub COLLAPSIBLE_ELSE_IF,
style,
pedantic,
"nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
}
@ -267,6 +267,9 @@ impl LateLintPass<'_> for CollapsibleIf {
&& !expr.span.from_expansion()
{
if let Some(else_) = else_
// Short circuit if both `if` branches contain only a single `if {..} else {}`, as
// collapsing such blocks can lead to less readable code (#4971)
&& !(single_inner_if_else(then) && single_inner_if_else(else_))
&& let ExprKind::Block(else_, None) = else_.kind
{
self.check_collapsible_else_if(cx, then.span, else_);
@ -280,6 +283,19 @@ impl LateLintPass<'_> for CollapsibleIf {
}
}
/// Returns true if `expr` is a block that contains only one `if {..} else {}` statement
fn single_inner_if_else(expr: &Expr<'_>) -> bool {
if let ExprKind::Block(block, None) = expr.kind
&& let Some(inner_expr) = expr_block(block)
&& let ExprKind::If(_, _, else_) = inner_expr.kind
&& else_.is_some()
{
true
} else {
false
}
}
/// If `block` is a block with either one expression or a statement containing an expression,
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {

View file

@ -667,6 +667,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::returns::LET_AND_RETURN_INFO,
crate::returns::NEEDLESS_RETURN_INFO,
crate::returns::NEEDLESS_RETURN_WITH_QUESTION_MARK_INFO,
crate::same_length_and_capacity::SAME_LENGTH_AND_CAPACITY_INFO,
crate::same_name_method::SAME_NAME_METHOD_INFO,
crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO,
crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO,

View file

@ -88,6 +88,9 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.desugaring_kind().is_some() {
return;
}
let (id, span) = match &expr.kind {
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {

View file

@ -1,16 +1,18 @@
use clippy_utils::attrs::span_contains_cfg;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use rustc_data_structures::fx::FxIndexMap;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::span_contains_non_whitespace;
use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
use rustc_errors::Applicability;
use rustc_hir::def::CtorOf;
use rustc_hir::def::DefKind::Ctor;
use rustc_hir::def::Res::Def;
use rustc_hir::def::{CtorOf, DefKind};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Pat, PatKind, Path, QPath, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{BytePos, Span};
declare_clippy_lint! {
/// ### What it does
@ -118,7 +120,6 @@ impl LateLintPass<'_> for EmptyWithBrackets {
}
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
// FIXME: handle `$name {}`
if !variant.span.from_expansion()
&& !variant.ident.span.from_expansion()
&& let span_after_ident = variant.span.with_lo(variant.ident.span.hi())
@ -126,44 +127,14 @@ impl LateLintPass<'_> for EmptyWithBrackets {
{
match variant.data {
VariantData::Struct { .. } => {
// Empty struct variants can be linted immediately
span_lint_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
span_after_ident,
"enum variant has empty brackets",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
},
);
self.add_enum_variant(variant.def_id);
},
VariantData::Tuple(.., local_def_id) => {
// Don't lint reachable tuple enums
if cx.effective_visibilities.is_reachable(variant.def_id) {
return;
}
if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) {
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
// definition was encountered. Now that there's a definition, convert it
// to Usage::Unused.
if let Usage::NoDefinition { redundant_use_sites } = entry {
*entry = Usage::Unused {
redundant_use_sites: redundant_use_sites.clone(),
};
}
} else {
self.empty_tuple_enum_variants.insert(
local_def_id,
Usage::Unused {
redundant_use_sites: vec![],
},
);
}
self.add_enum_variant(local_def_id);
},
VariantData::Unit(..) => {},
}
@ -171,56 +142,58 @@ impl LateLintPass<'_> for EmptyWithBrackets {
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) {
if let Some((def_id, mut span)) = check_expr_for_enum_as_function(cx, expr) {
if span.is_empty()
&& let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr)
{
span = parentheses_span;
}
if span.is_empty() {
// The parentheses are not redundant.
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
} else {
// Do not count expressions from macro expansion as a redundant use site.
if expr.span.from_expansion() {
return;
}
match self.empty_tuple_enum_variants.get_mut(&def_id) {
Some(
&mut (Usage::Unused {
ref mut redundant_use_sites,
}
| Usage::NoDefinition {
ref mut redundant_use_sites,
}),
) => {
redundant_use_sites.push(parentheses_span);
},
None => {
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
self.empty_tuple_enum_variants.insert(
def_id,
Usage::NoDefinition {
redundant_use_sites: vec![parentheses_span],
},
);
},
_ => {},
}
} else {
// The parentheses are not redundant.
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
self.update_enum_variant_usage(def_id, span);
}
}
}
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
if !pat.span.from_expansion()
&& let Some((def_id, span)) = check_pat_for_enum_as_function(cx, pat)
{
self.update_enum_variant_usage(def_id, span);
}
}
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
for (&local_def_id, usage) in &self.empty_tuple_enum_variants {
// Ignore all variants with Usage::Used or Usage::NoDefinition
let Usage::Unused { redundant_use_sites } = usage else {
continue;
};
// Attempt to fetch the Variant from LocalDefId.
let Node::Variant(variant) = cx.tcx.hir_node(
cx.tcx
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
) else {
let variant = if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(local_def_id) {
variant
} else if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(cx.tcx.local_parent(local_def_id)) {
variant
} else {
continue;
};
// Span of the parentheses in variant definition
let span = variant.span.with_lo(variant.ident.span.hi());
let span_inner = span
.with_lo(SpanRangeExt::trim_start(span, cx).start + BytePos(1))
.with_hi(span.hi() - BytePos(1));
if span_contains_non_whitespace(cx, span_inner, false) {
continue;
}
span_lint_hir_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
@ -252,6 +225,43 @@ impl LateLintPass<'_> for EmptyWithBrackets {
}
}
impl EmptyWithBrackets {
fn add_enum_variant(&mut self, local_def_id: LocalDefId) {
self.empty_tuple_enum_variants
.entry(local_def_id)
.and_modify(|entry| {
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before
// the definition was encountered. Now that there's a
// definition, convert it to Usage::Unused.
if let Usage::NoDefinition { redundant_use_sites } = entry {
*entry = Usage::Unused {
redundant_use_sites: redundant_use_sites.clone(),
};
}
})
.or_insert_with(|| Usage::Unused {
redundant_use_sites: vec![],
});
}
fn update_enum_variant_usage(&mut self, def_id: LocalDefId, parentheses_span: Span) {
match self.empty_tuple_enum_variants.entry(def_id) {
IndexEntry::Occupied(mut e) => {
if let Usage::Unused { redundant_use_sites } | Usage::NoDefinition { redundant_use_sites } = e.get_mut()
{
redundant_use_sites.push(parentheses_span);
}
},
IndexEntry::Vacant(e) => {
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
e.insert(Usage::NoDefinition {
redundant_use_sites: vec![parentheses_span],
});
},
}
}
}
fn has_brackets(var_data: &VariantData<'_>) -> bool {
!matches!(var_data, VariantData::Unit(..))
}
@ -277,17 +287,47 @@ fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
}
// Returns the LocalDefId of the variant being called as a function if it exists.
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
if let ExprKind::Path(QPath::Resolved(
_,
Path {
res: Def(Ctor(CtorOf::Variant, _), def_id),
..
fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(LocalDefId, Span)> {
match expr.kind {
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Def(Ctor(CtorOf::Variant, _), def_id),
span,
..
},
)) => def_id.as_local().map(|id| (id, span.with_lo(expr.span.hi()))),
ExprKind::Struct(qpath, ..)
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) =>
{
let ty = cx.tcx.type_of(def_id).instantiate_identity();
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
def_id = *ctor_def_id;
}
def_id.as_local().map(|id| (id, qpath.span().with_lo(expr.span.hi())))
},
)) = expr.kind
{
def_id.as_local()
} else {
None
_ => None,
}
}
fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(LocalDefId, Span)> {
match pat.kind {
PatKind::TupleStruct(qpath, ..)
if let Def(Ctor(CtorOf::Variant, _), def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
{
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
},
PatKind::Struct(qpath, ..)
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
{
let ty = cx.tcx.type_of(def_id).instantiate_identity();
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
def_id = *ctor_def_id;
}
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
},
_ => None,
}
}

View file

@ -4,7 +4,7 @@ use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
SpanlessEq, can_move_expr_to_closure_no_visit, desugar_await, higher, is_expr_final_block_expr,
is_expr_used_or_unified, paths, peel_hir_expr_while,
is_expr_used_or_unified, paths, peel_hir_expr_while, span_contains_non_whitespace,
};
use core::fmt::{self, Write};
use rustc_errors::Applicability;
@ -167,7 +167,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}",
map_ty.entry_path(),
))
} else if let Some(insertion) = then_search.as_single_insertion() {
} else if let Some(insertion) = then_search.as_single_insertion()
&& let span_in_between = then_expr.span.shrink_to_lo().between(insertion.call.span)
&& let span_in_between = span_in_between.split_at(1).1
&& !span_contains_non_whitespace(cx, span_in_between, true)
{
let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
if contains_expr.negated {
if insertion.value.can_have_side_effects() {

View file

@ -82,7 +82,7 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if receiver_ty.is_diag_item(self.lcx, sym::Option) || receiver_ty.is_diag_item(self.lcx, sym::Result) {
if matches!(receiver_ty.opt_diag_name(self.lcx), Some(sym::Option | sym::Result)) {
self.result.push(expr.span);
}
}

View file

@ -1,10 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::std_or_core;
use rustc_errors::Applicability;
use rustc_hir::{AssignOpKind, Expr, ExprKind, LangItem, MatchSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, sym};
declare_clippy_lint! {
/// ### What it does
@ -38,7 +41,152 @@ declare_clippy_lint! {
pedantic,
"`format!(..)` appended to existing `String`"
}
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
impl_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
pub(crate) struct FormatPushString {
format_args: FormatArgsStorage,
}
enum FormatSearchResults {
/// The expression is itself a `format!()` invocation -- we can make a suggestion to replace it
Direct(Span),
/// The expression contains zero or more `format!()`s, e.g.:
/// ```ignore
/// if true {
/// format!("hello")
/// } else {
/// format!("world")
/// }
/// ```
/// or
/// ```ignore
/// match true {
/// true => format!("hello"),
/// false => format!("world"),
/// }
Nested(Vec<Span>),
}
impl FormatPushString {
pub(crate) fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
fn find_formats<'tcx>(&self, cx: &LateContext<'_>, e: &'tcx Expr<'tcx>) -> FormatSearchResults {
let expr_as_format = |e| {
if let Some(macro_call) = root_macro_call_first_node(cx, e)
&& cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
&& let Some(format_args) = self.format_args.get(cx, e, macro_call.expn)
{
Some(format_args_inputs_span(format_args))
} else {
None
}
};
let e = e.peel_blocks().peel_borrows();
if let Some(fmt) = expr_as_format(e) {
FormatSearchResults::Direct(fmt)
} else {
fn inner<'tcx>(
e: &'tcx Expr<'tcx>,
expr_as_format: &impl Fn(&'tcx Expr<'tcx>) -> Option<Span>,
out: &mut Vec<Span>,
) {
let e = e.peel_blocks().peel_borrows();
match e.kind {
_ if expr_as_format(e).is_some() => out.push(e.span),
ExprKind::Match(_, arms, MatchSource::Normal) => {
for arm in arms {
inner(arm.body, expr_as_format, out);
}
},
ExprKind::If(_, then, els) => {
inner(then, expr_as_format, out);
if let Some(els) = els {
inner(els, expr_as_format, out);
}
},
_ => {},
}
}
let mut spans = vec![];
inner(e, &expr_as_format, &mut spans);
FormatSearchResults::Nested(spans)
}
}
}
impl<'tcx> LateLintPass<'tcx> for FormatPushString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (recv, arg) = match expr.kind {
ExprKind::MethodCall(_, recv, [arg], _) => {
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id)
{
(recv, arg)
} else {
return;
}
},
ExprKind::AssignOp(op, recv, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, recv) => {
(recv, arg)
},
_ => return,
};
let Some(std_or_core) = std_or_core(cx) else {
// not even `core` is available, so can't suggest `write!`
return;
};
match self.find_formats(cx, arg) {
FormatSearchResults::Direct(format_args) => {
span_lint_and_then(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
|diag| {
let mut app = Applicability::MaybeIncorrect;
let msg = "consider using `write!` to avoid the extra allocation";
let sugg = format!(
"let _ = write!({recv}, {format_args})",
recv = snippet_with_context(cx.sess(), recv.span, expr.span.ctxt(), "_", &mut app).0,
format_args = snippet_with_applicability(cx.sess(), format_args, "..", &mut app),
);
diag.span_suggestion_verbose(expr.span, msg, sugg, app);
// TODO: omit the note if the `Write` trait is imported at point
// Tip: `TyCtxt::in_scope_traits` isn't it -- it returns a non-empty list only when called on
// the `HirId` of a `ExprKind::MethodCall` that is a call of a _trait_ method.
diag.note(format!("you may need to import the `{std_or_core}::fmt::Write` trait"));
},
);
},
FormatSearchResults::Nested(spans) => {
if !spans.is_empty() {
span_lint_and_then(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
|diag| {
diag.help("consider using `write!` to avoid the extra allocation");
diag.span_labels(spans, "`format!` used here");
// TODO: omit the note if the `Write` trait is imported at point
// Tip: `TyCtxt::in_scope_traits` isn't it -- it returns a non-empty list only when called
// on the `HirId` of a `ExprKind::MethodCall` that is a call of
// a _trait_ method.
diag.note(format!("you may need to import the `{std_or_core}::fmt::Write` trait"));
},
);
}
},
}
}
}
fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
cx.typeck_results()
@ -46,54 +194,3 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
.peel_refs()
.is_lang_item(cx, LangItem::String)
}
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
let e = e.peel_blocks().peel_borrows();
if e.span.from_expansion()
&& let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id
{
cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro)
} else if let Some(higher::If { then, r#else, .. }) = higher::If::hir(e) {
is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e))
} else {
match higher::IfLetOrMatch::parse(cx, e) {
Some(higher::IfLetOrMatch::Match(_, arms, MatchSource::Normal)) => {
arms.iter().any(|arm| is_format(cx, arm.body))
},
Some(higher::IfLetOrMatch::IfLet(_, _, then, r#else, _)) => {
is_format(cx, then) || r#else.is_some_and(|e| is_format(cx, e))
},
_ => false,
}
}
}
impl<'tcx> LateLintPass<'tcx> for FormatPushString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let arg = match expr.kind {
ExprKind::MethodCall(_, _, [arg], _) => {
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id)
{
arg
} else {
return;
}
},
ExprKind::AssignOp(op, left, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, left) => arg,
_ => return,
};
if is_format(cx, arg) {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
|diag| {
diag.help("consider using `write!` to avoid the extra allocation");
},
);
}
}
}

View file

@ -596,4 +596,8 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api);
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
result::check_expr(cx, expr, self.large_error_threshold, &self.large_error_ignored);
}
}

View file

@ -50,7 +50,7 @@ pub(super) fn check_item<'tcx>(
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
}
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored);
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false);
}
}
@ -70,7 +70,7 @@ pub(super) fn check_impl_item<'tcx>(
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
}
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored);
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false);
}
}
@ -87,7 +87,7 @@ pub(super) fn check_trait_item<'tcx>(
if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
check_result_unit_err(cx, err_ty, fn_header_span, msrv);
}
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored);
check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored, false);
}
}
}
@ -111,12 +111,15 @@ fn check_result_large_err<'tcx>(
hir_ty_span: Span,
large_err_threshold: u64,
large_err_ignored: &DefIdSet,
is_closure: bool,
) {
if let ty::Adt(adt, _) = err_ty.kind()
&& large_err_ignored.contains(&adt.did())
{
return;
}
let subject = if is_closure { "closure" } else { "function" };
if let ty::Adt(adt, subst) = err_ty.kind()
&& let Some(local_def_id) = adt.did().as_local()
&& let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id)
@ -130,7 +133,7 @@ fn check_result_large_err<'tcx>(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
format!("the `Err`-variant returned from this {subject} is very large"),
|diag| {
diag.span_label(
def.variants[first_variant.ind].span,
@ -161,7 +164,7 @@ fn check_result_large_err<'tcx>(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
format!("the `Err`-variant returned from this {subject} is very large"),
|diag: &mut Diag<'_, ()>| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
@ -170,3 +173,33 @@ fn check_result_large_err<'tcx>(
}
}
}
pub(super) fn check_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
large_err_threshold: u64,
large_err_ignored: &DefIdSet,
) {
if let hir::ExprKind::Closure(closure) = expr.kind
&& let ty::Closure(_, args) = cx.typeck_results().expr_ty(expr).kind()
&& let closure_sig = args.as_closure().sig()
&& let Ok(err_binder) = closure_sig.output().try_map_bound(|output_ty| {
if let ty::Adt(adt, args) = output_ty.kind()
&& let [_, err_arg] = args.as_slice()
&& let Some(err_ty) = err_arg.as_type()
&& adt.is_diag_item(cx, sym::Result)
{
return Ok(err_ty);
}
Err(())
})
{
let err_ty = cx.tcx.instantiate_bound_regions_with_erased(err_binder);
let hir_ty_span = match closure.fn_decl.output {
hir::FnRetTy::Return(hir_ty) => hir_ty.span,
hir::FnRetTy::DefaultReturn(_) => expr.span,
};
check_result_large_err(cx, err_ty, hir_ty_span, large_err_threshold, large_err_ignored, true);
}
}

View file

@ -1,7 +1,7 @@
use clippy_utils::consts::is_zero_integer_const;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::is_else_clause;
use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet};
use clippy_utils::source::{HasSession, indent_of, reindent_multiline, snippet_with_context};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -78,6 +78,7 @@ impl LateLintPass<'_> for IfNotElse {
// }
// ```
if !e.span.from_expansion() && !is_else_clause(cx.tcx, e) {
let mut applicability = Applicability::MachineApplicable;
match cond.kind {
ExprKind::Unary(UnOp::Not, _) | ExprKind::Binary(_, _, _) => span_lint_and_sugg(
cx,
@ -85,8 +86,16 @@ impl LateLintPass<'_> for IfNotElse {
e.span,
msg,
"try",
make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)),
Applicability::MachineApplicable,
make_sugg(
cx,
e.span,
&cond.kind,
cond_inner.span,
els.span,
"..",
&mut applicability,
),
applicability,
),
_ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help),
}
@ -97,28 +106,26 @@ impl LateLintPass<'_> for IfNotElse {
fn make_sugg<'a>(
sess: &impl HasSession,
expr_span: Span,
cond_kind: &'a ExprKind<'a>,
cond_inner: Span,
els_span: Span,
default: &'a str,
indent_relative_to: Option<Span>,
applicability: &mut Applicability,
) -> String {
let cond_inner_snip = snippet(sess, cond_inner, default);
let els_snip = snippet(sess, els_span, default);
let indent = indent_relative_to.and_then(|s| indent_of(sess, s));
let (cond_inner_snip, _) = snippet_with_context(sess, cond_inner, expr_span.ctxt(), default, applicability);
let (els_snip, _) = snippet_with_context(sess, els_span, expr_span.ctxt(), default, applicability);
let indent = indent_of(sess, expr_span);
let suggestion = match cond_kind {
ExprKind::Unary(UnOp::Not, cond_rest) => {
format!(
"if {} {} else {}",
snippet(sess, cond_rest.span, default),
els_snip,
cond_inner_snip
)
let (cond_rest_snip, _) =
snippet_with_context(sess, cond_rest.span, expr_span.ctxt(), default, applicability);
format!("if {cond_rest_snip} {els_snip} else {cond_inner_snip}")
},
ExprKind::Binary(_, lhs, rhs) => {
let lhs_snip = snippet(sess, lhs.span, default);
let rhs_snip = snippet(sess, rhs.span, default);
let (lhs_snip, _) = snippet_with_context(sess, lhs.span, expr_span.ctxt(), default, applicability);
let (rhs_snip, _) = snippet_with_context(sess, rhs.span, expr_span.ctxt(), default, applicability);
format!("if {lhs_snip} == {rhs_snip} {els_snip} else {cond_inner_snip}")
},

View file

@ -4,10 +4,12 @@ use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{
as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context,
is_none_expr, peel_blocks, sym,
as_some_expr, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_none_expr,
peel_blocks, sym,
};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -76,8 +78,14 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& !is_else_clause(cx.tcx, expr)
&& !is_in_const_context(cx)
&& self.msrv.meets(cx, msrvs::BOOL_THEN)
&& !contains_return(then_block.stmts)
&& then_block.expr.is_none_or(|expr| !contains_return(expr))
&& for_each_expr_without_closures(then_block, |e| {
if matches!(e.kind, ExprKind::Ret(..) | ExprKind::Yield(..)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})
.is_none()
{
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
sym::then_some
@ -101,13 +109,19 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
.maybe_paren()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let method_body = if let Some(first_stmt) = then_block.stmts.first()
&& let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt)
let method_body = if let Some(_) = then_block.stmts.first()
&& let Some(then_span) = walk_span_to_context(then.span, ctxt)
{
let block_snippet =
snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app);
let block_before_snippet =
snippet_with_applicability(cx, then_span.until(then_expr.span), "..", &mut app);
let block_after_snippet = snippet_with_applicability(
cx,
then_expr.span.shrink_to_hi().until(then_span.shrink_to_hi()),
"..",
&mut app,
);
let closure = if method_name == sym::then { "|| " } else { "" };
format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end())
format!("{closure}{block_before_snippet}{arg_snip}{block_after_snippet}")
} else if method_name == sym::then {
(std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned()
} else {

View file

@ -9,7 +9,7 @@ use clippy_utils::{
use core::iter;
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit};
use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, ItemKind, LetStmt, Node, Stmt, StmtKind, UseKind, intravisit};
use rustc_lint::LateContext;
use rustc_span::hygiene::walk_chain;
use rustc_span::source_map::SourceMap;
@ -108,6 +108,7 @@ struct BlockEq {
/// The name and id of every local which can be moved at the beginning and the end.
moved_locals: Vec<(HirId, Symbol)>,
}
impl BlockEq {
fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option<Span> {
match &b.stmts[..self.start_end_eq] {
@ -129,20 +130,33 @@ impl BlockEq {
}
/// If the statement is a local, checks if the bound names match the expected list of names.
fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
if let StmtKind::Let(l) = s.kind {
let mut i = 0usize;
let mut res = true;
l.pat.each_binding_or_first(&mut |_, _, _, name| {
if names.get(i).is_some_and(|&(_, n)| n == name.name) {
i += 1;
} else {
res = false;
}
});
res && i == names.len()
} else {
false
fn eq_binding_names(cx: &LateContext<'_>, s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
match s.kind {
StmtKind::Let(l) => {
let mut i = 0usize;
let mut res = true;
l.pat.each_binding_or_first(&mut |_, _, _, name| {
if names.get(i).is_some_and(|&(_, n)| n == name.name) {
i += 1;
} else {
res = false;
}
});
res && i == names.len()
},
StmtKind::Item(item_id)
if let [(_, name)] = names
&& let item = cx.tcx.hir_item(item_id)
&& let ItemKind::Static(_, ident, ..)
| ItemKind::Const(ident, ..)
| ItemKind::Fn { ident, .. }
| ItemKind::TyAlias(ident, ..)
| ItemKind::Use(_, UseKind::Single(ident))
| ItemKind::Mod(ident, _) = item.kind =>
{
*name == ident.name
},
_ => false,
}
}
@ -164,6 +178,7 @@ fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &
/// Checks if the given statement should be considered equal to the statement in the same
/// position for each block.
fn eq_stmts(
cx: &LateContext<'_>,
stmt: &Stmt<'_>,
blocks: &[&Block<'_>],
get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>,
@ -178,7 +193,7 @@ fn eq_stmts(
let new_bindings = &moved_bindings[old_count..];
blocks
.iter()
.all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(s, new_bindings)))
.all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(cx, s, new_bindings)))
} else {
true
}) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt)))
@ -218,7 +233,7 @@ fn scan_block_for_eq<'tcx>(
return true;
}
modifies_any_local(cx, stmt, &cond_locals)
|| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
|| !eq_stmts(cx, stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
})
.map_or(block.stmts.len(), |(i, stmt)| {
adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev())
@ -279,6 +294,7 @@ fn scan_block_for_eq<'tcx>(
}))
.fold(end_search_start, |init, (stmt, offset)| {
if eq_stmts(
cx,
stmt,
blocks,
|b| b.stmts.get(b.stmts.len() - offset),
@ -290,11 +306,26 @@ fn scan_block_for_eq<'tcx>(
// Clear out all locals seen at the end so far. None of them can be moved.
let stmts = &blocks[0].stmts;
for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] {
if let StmtKind::Let(l) = stmt.kind {
l.pat.each_binding_or_first(&mut |_, id, _, _| {
// FIXME(rust/#120456) - is `swap_remove` correct?
eq.locals.swap_remove(&id);
});
match stmt.kind {
StmtKind::Let(l) => {
l.pat.each_binding_or_first(&mut |_, id, _, _| {
// FIXME(rust/#120456) - is `swap_remove` correct?
eq.locals.swap_remove(&id);
});
},
StmtKind::Item(item_id) => {
let item = cx.tcx.hir_item(item_id);
if let ItemKind::Static(..)
| ItemKind::Const(..)
| ItemKind::Fn { .. }
| ItemKind::TyAlias(..)
| ItemKind::Use(..)
| ItemKind::Mod(..) = item.kind
{
eq.local_items.swap_remove(&item.owner_id.to_def_id());
}
},
_ => {},
}
}
moved_locals.truncate(moved_locals_at_start);

View file

@ -223,25 +223,20 @@ impl<'tcx> ImplicitHasherType<'tcx> {
_ => None,
})
.collect();
let params_len = params.len();
let ty = lower_ty(cx.tcx, hir_ty);
if ty.is_diag_item(cx, sym::HashMap) && params_len == 2 {
Some(ImplicitHasherType::HashMap(
match (ty.opt_diag_name(cx), &params[..]) {
(Some(sym::HashMap), [k, v]) => Some(ImplicitHasherType::HashMap(
hir_ty.span,
ty,
snippet(cx, params[0].span, "K"),
snippet(cx, params[1].span, "V"),
))
} else if ty.is_diag_item(cx, sym::HashSet) && params_len == 1 {
Some(ImplicitHasherType::HashSet(
hir_ty.span,
ty,
snippet(cx, params[0].span, "T"),
))
} else {
None
snippet(cx, k.span, "K"),
snippet(cx, v.span, "V"),
)),
(Some(sym::HashSet), [t]) => {
Some(ImplicitHasherType::HashSet(hir_ty.span, ty, snippet(cx, t.span, "T")))
},
_ => None,
}
} else {
None

View file

@ -318,6 +318,7 @@ mod replace_box;
mod reserve_after_initialization;
mod return_self_not_must_use;
mod returns;
mod same_length_and_capacity;
mod same_name_method;
mod self_named_constructors;
mod semicolon_block;
@ -739,7 +740,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(move |_| Box::new(cargo::Cargo::new(conf))),
Box::new(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())),
Box::new(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)),
Box::new(|_| Box::new(format_push_string::FormatPushString)),
{
let format_args = format_args_storage.clone();
Box::new(move |_| Box::new(format_push_string::FormatPushString::new(format_args.clone())))
},
Box::new(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))),
Box::new(|_| Box::new(strings::TrimSplitWhitespace)),
Box::new(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)),
@ -852,6 +856,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);

View file

@ -34,7 +34,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
_ => arg,
};
if ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap) {
if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) {
span_lint_and_then(
cx,
FOR_KV_MAP,

View file

@ -26,6 +26,7 @@ mod while_let_on_iterator;
use clippy_config::Conf;
use clippy_utils::msrvs::Msrv;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::{higher, sym};
use rustc_ast::Label;
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
@ -881,13 +882,44 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
manual_while_let_some::check(cx, condition, body, span);
}
if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind
&& matches!(
path.ident.name,
sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map
)
{
unused_enumerate_index::check_method(cx, expr, recv, arg);
if let ExprKind::MethodCall(path, recv, args, _) = expr.kind {
let name = path.ident.name;
let is_iterator_method = || {
cx.ty_based_def(expr)
.assoc_fn_parent(cx)
.is_diag_item(cx, sym::Iterator)
};
// is_iterator_method is a bit expensive, so we call it last in each match arm
match (name, args) {
(sym::for_each | sym::all | sym::any, [arg]) => {
if let ExprKind::Closure(closure) = arg.kind
&& is_iterator_method()
{
unused_enumerate_index::check_method(cx, recv, arg, closure);
never_loop::check_iterator_reduction(cx, expr, recv, closure);
}
},
(sym::filter_map | sym::find_map | sym::flat_map | sym::map, [arg]) => {
if let ExprKind::Closure(closure) = arg.kind
&& is_iterator_method()
{
unused_enumerate_index::check_method(cx, recv, arg, closure);
}
},
(sym::try_for_each | sym::reduce, [arg]) | (sym::fold | sym::try_fold, [_, arg]) => {
if let ExprKind::Closure(closure) = arg.kind
&& is_iterator_method()
{
never_loop::check_iterator_reduction(cx, expr, recv, closure);
}
},
_ => {},
}
}
}
}

View file

@ -3,14 +3,16 @@ use super::utils::make_iterator_snippet;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::sym;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
use rustc_errors::Applicability;
use rustc_hir::{
Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr,
Block, Closure, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind,
StructTailExpr,
};
use rustc_lint::LateContext;
use rustc_span::{BytePos, Span, sym};
use rustc_span::{BytePos, Span};
use std::iter::once;
use std::ops::ControlFlow;
@ -72,6 +74,31 @@ pub(super) fn check<'tcx>(
}
}
pub(super) fn check_iterator_reduction<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
closure: &'tcx Closure<'tcx>,
) {
let closure_body = cx.tcx.hir_body(closure.body).value;
let body_ty = cx.typeck_results().expr_ty(closure_body);
if body_ty.is_never() {
span_lint_and_then(
cx,
NEVER_LOOP,
expr.span,
"this iterator reduction never loops (closure always diverges)",
|diag| {
let mut app = Applicability::HasPlaceholders;
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "<iter>", &mut app).0;
diag.note("if you only need one element, `if let Some(x) = iter.next()` is clearer");
let sugg = format!("if let Some(x) = {recv_snip}.next() {{ ... }}");
diag.span_suggestion_verbose(expr.span, "consider this pattern", sugg, app);
},
);
}
}
fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
for_each_expr_without_closures(block, |e| match e.kind {
ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),

View file

@ -1,10 +1,10 @@
use super::UNUSED_ENUMERATE_INDEX;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{SpanRangeExt, walk_span_to_context};
use clippy_utils::{expr_or_init, pat_is_wild};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind};
use rustc_hir::{Closure, Expr, ExprKind, Pat, PatKind, TyKind};
use rustc_lint::LateContext;
use rustc_span::{Span, SyntaxContext, sym};
@ -60,14 +60,12 @@ pub(super) fn check<'tcx>(
pub(super) fn check_method<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
closure: &'tcx Closure<'tcx>,
) {
if let ExprKind::Closure(closure) = arg.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let [param] = body.params
&& cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator)
let body = cx.tcx.hir_body(closure.body);
if let [param] = body.params
&& let [input] = closure.fn_decl.inputs
&& !arg.span.from_expansion()
&& !input.span.from_expansion()

View file

@ -374,7 +374,7 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo
}
let ty = typeck_results.pat_ty(pat);
// Option and Result are allowed, everything else isn't.
if !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) {
if !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) {
has_disallowed = true;
}
});

View file

@ -3,7 +3,7 @@
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::has_let_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
@ -44,6 +44,8 @@ pub(crate) fn check_if_let<'tcx>(
{
ex_new = ex_inner;
}
let (snippet, _) = snippet_with_context(cx, ex_new.span, expr.span.ctxt(), "..", &mut applicability);
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
@ -53,11 +55,7 @@ pub(crate) fn check_if_let<'tcx>(
diag.span_suggestion_verbose(
expr.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
format!("{}matches!({snippet}, {pat})", if b0 { "" } else { "!" }),
applicability,
);
},
@ -178,6 +176,8 @@ pub(super) fn check_match<'tcx>(
{
ex_new = ex_inner;
}
let (snippet, _) = snippet_with_context(cx, ex_new.span, e.span.ctxt(), "..", &mut applicability);
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
@ -187,11 +187,7 @@ pub(super) fn check_match<'tcx>(
diag.span_suggestion_verbose(
e.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
format!("{}matches!({snippet}, {pat_and_guard})", if b0 { "" } else { "!" },),
applicability,
);
},

View file

@ -16,7 +16,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
let ty = cx.typeck_results().expr_ty(ex).peel_refs();
let adt_def = match ty.kind() {
ty::Adt(adt_def, _)
if adt_def.is_enum() && !(ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)) =>
if adt_def.is_enum() && !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) =>
{
adt_def
},

View file

@ -26,12 +26,10 @@ pub(super) fn check<'tcx>(
let arg_root = get_arg_root(cx, arg);
if contains_call(cx, arg_root) && !contains_return(arg_root) {
let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
let closure_args = if receiver_type.is_diag_item(cx, sym::Option) {
"||"
} else if receiver_type.is_diag_item(cx, sym::Result) {
"|_|"
} else {
return;
let closure_args = match receiver_type.opt_diag_name(cx) {
Some(sym::Option) => "||",
Some(sym::Result) => "|_|",
_ => return,
};
let span_replace_word = method_span.with_hi(expr.span.hi());

View file

@ -11,26 +11,17 @@ use super::ITER_COUNT;
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: Symbol) {
let ty = cx.typeck_results().expr_ty(recv);
let caller_type = if derefs_to_slice(cx, recv, ty).is_some() {
"slice"
} else if ty.is_diag_item(cx, sym::Vec) {
"Vec"
} else if ty.is_diag_item(cx, sym::VecDeque) {
"VecDeque"
} else if ty.is_diag_item(cx, sym::HashSet) {
"HashSet"
} else if ty.is_diag_item(cx, sym::HashMap) {
"HashMap"
} else if ty.is_diag_item(cx, sym::BTreeMap) {
"BTreeMap"
} else if ty.is_diag_item(cx, sym::BTreeSet) {
"BTreeSet"
} else if ty.is_diag_item(cx, sym::LinkedList) {
"LinkedList"
} else if ty.is_diag_item(cx, sym::BinaryHeap) {
"BinaryHeap"
} else {
return;
let caller_type = match ty.opt_diag_name(cx) {
_ if derefs_to_slice(cx, recv, ty).is_some() => "slice",
Some(sym::Vec) => "Vec",
Some(sym::VecDeque) => "VecDeque",
Some(sym::HashSet) => "HashSet",
Some(sym::HashMap) => "HashMap",
Some(sym::BTreeMap) => "BTreeMap",
Some(sym::BTreeSet) => "BTreeSet",
Some(sym::LinkedList) => "LinkedList",
Some(sym::BinaryHeap) => "BinaryHeap",
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(

View file

@ -38,7 +38,7 @@ pub(super) fn check<'tcx>(
_ => return,
}
&& let ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs()
&& (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap))
&& matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap))
{
let mut applicability = rustc_errors::Applicability::MachineApplicable;
let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability);

View file

@ -13,7 +13,7 @@ use super::JOIN_ABSOLUTE_PATHS;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if (ty.is_diag_item(cx, sym::Path) || ty.is_diag_item(cx, sym::PathBuf))
if matches!(ty.opt_diag_name(cx), Some(sym::Path | sym::PathBuf))
&& let ExprKind::Lit(spanned) = expr_or_init(cx, join_arg).kind
&& let LitKind::Str(symbol, _) = spanned.node
&& let sym_str = symbol.as_str()

View file

@ -25,7 +25,7 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_parent_id: De
}
// We check if it's an `Option` or a `Result`.
if let Some(ty) = method_parent_id.opt_impl_ty(cx) {
if !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result) {
if !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result)) {
return false;
}
} else {

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet;
@ -51,11 +51,8 @@ pub(super) fn check<'tcx>(
// get snippets for args to map() and unwrap_or_else()
let map_snippet = snippet(cx, map_arg.span, "..");
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
// lint, with note if neither arg is > 1 line and both map() and
// unwrap_or_else() have the same span
let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
let same_span = map_arg.span.eq_ctxt(unwrap_arg.span);
if same_span && !multiline {
// lint, with note if both map() and unwrap_or_else() have the same span
if map_arg.span.eq_ctxt(unwrap_arg.span) {
let var_snippet = snippet(cx, recv.span, "..");
span_lint_and_sugg(
cx,
@ -67,9 +64,6 @@ pub(super) fn check<'tcx>(
Applicability::MachineApplicable,
);
return true;
} else if same_span && multiline {
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
return true;
}
}

View file

@ -81,7 +81,9 @@ pub(super) fn check<'tcx>(
},
_ => return,
};
} else if let ExprKind::Index(_, index, _) = parent.kind {
} else if let ExprKind::Index(_, index, _) = parent.kind
&& cx.typeck_results().expr_ty(index).is_usize()
{
app = Applicability::MaybeIncorrect;
let snip = snippet_with_applicability(cx, index.span, "_", &mut app);
sugg = format!("nth({snip}).unwrap()");

View file

@ -1,7 +1,7 @@
use super::OBFUSCATED_IF_ELSE;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, sym};
use rustc_errors::Applicability;
@ -33,20 +33,22 @@ pub(super) fn check<'tcx>(
let if_then = match then_method_name {
sym::then if let ExprKind::Closure(closure) = then_arg.kind => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
snippet_with_context(cx, body.value.span, expr.span.ctxt(), "..", &mut applicability).0
},
sym::then_some => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability),
sym::then_some => snippet_with_context(cx, then_arg.span, expr.span.ctxt(), "..", &mut applicability).0,
_ => return,
};
let els = match unwrap {
Unwrap::Or(arg) => snippet_with_applicability(cx, arg.span, "..", &mut applicability),
Unwrap::Or(arg) => snippet_with_context(cx, arg.span, expr.span.ctxt(), "..", &mut applicability).0,
Unwrap::OrElse(arg) => match arg.kind {
ExprKind::Closure(closure) => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
snippet_with_context(cx, body.value.span, expr.span.ctxt(), "..", &mut applicability).0
},
ExprKind::Path(_) => {
snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut applicability).0 + "()"
},
ExprKind::Path(_) => snippet_with_applicability(cx, arg.span, "_", &mut applicability) + "()",
_ => return,
},
Unwrap::OrDefault => "Default::default()".into(),
@ -54,7 +56,7 @@ pub(super) fn check<'tcx>(
let sugg = format!(
"if {} {{ {} }} else {{ {} }}",
Sugg::hir_with_applicability(cx, then_recv, "..", &mut applicability),
Sugg::hir_with_context(cx, then_recv, expr.span.ctxt(), "..", &mut applicability),
if_then,
els
);

View file

@ -20,24 +20,28 @@ pub(super) fn check<'tcx>(
let title;
let or_arg_content: Span;
if ty.is_diag_item(cx, sym::Option) {
title = "found `.or(Some(…)).unwrap()`";
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
or_arg_content = content;
} else {
match ty.opt_diag_name(cx) {
Some(sym::Option) => {
title = "found `.or(Some(…)).unwrap()`";
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
or_arg_content = content;
} else {
return;
}
},
Some(sym::Result) => {
title = "found `.or(Ok(…)).unwrap()`";
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
or_arg_content = content;
} else {
return;
}
},
_ => {
// Someone has implemented a struct with .or(...).unwrap() chaining,
// but it's not an Option or a Result, so bail
return;
}
} else if ty.is_diag_item(cx, sym::Result) {
title = "found `.or(Ok(…)).unwrap()`";
if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
or_arg_content = content;
} else {
return;
}
} else {
// Someone has implemented a struct with .or(...).unwrap() chaining,
// but it's not an Option or a Result, so bail
return;
},
}
let mut applicability = Applicability::MachineApplicable;

View file

@ -1,44 +1,52 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes};
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{peel_blocks, strip_pat_refs};
use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs};
use rustc_ast::ast;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::PatKind;
use rustc_hir::def::{DefKind, Res};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, sym};
use rustc_middle::ty::{self, Ty};
use rustc_span::{Span, Symbol, sym};
use super::UNNECESSARY_FOLD;
/// Do we need to suggest turbofish when suggesting a replacement method?
/// Changing `fold` to `sum` needs it sometimes when the return type can't be
/// inferred. This checks for some common cases where it can be safely omitted
fn needs_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
let parent = cx.tcx.parent_hir_node(expr.hir_id);
// some common cases where turbofish isn't needed:
// - assigned to a local variable with a type annotation
if let hir::Node::LetStmt(local) = parent
&& local.ty.is_some()
fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
let use_cx = expr_use_ctxt(cx, expr);
if use_cx.same_ctxt
&& let use_node = use_cx.use_node(cx)
&& let Some(ty) = use_node.defined_ty(cx)
{
return false;
}
// some common cases where turbofish isn't needed:
match (use_node, ty) {
// - assigned to a local variable with a type annotation
(ExprUseNode::LetStmt(_), _) => return false,
// - part of a function call argument, can be inferred from the function signature (provided that
// the parameter is not a generic type parameter)
if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::Call(recv, args) = parent_expr.kind
&& let hir::ExprKind::Path(ref qpath) = recv.kind
&& let Some(fn_def_id) = cx.qpath_res(qpath, recv.hir_id).opt_def_id()
&& let fn_sig = cx.tcx.fn_sig(fn_def_id).skip_binder().skip_binder()
&& let Some(arg_pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
&& let Some(ty) = fn_sig.inputs().get(arg_pos)
&& !matches!(ty.kind(), ty::Param(_))
{
return false;
// - part of a function call argument, can be inferred from the function signature (provided that the
// parameter is not a generic type parameter)
(ExprUseNode::FnArg(..), DefinedTy::Mir { ty: arg_ty, .. })
if !matches!(arg_ty.skip_binder().kind(), ty::Param(_)) =>
{
return false;
},
// - the final expression in the body of a function with a simple return type
(ExprUseNode::Return(_), DefinedTy::Mir { ty: fn_return_ty, .. })
if !fn_return_ty
.skip_binder()
.walk()
.any(|generic| generic.as_type().is_some_and(Ty::is_impl_trait)) =>
{
return false;
},
_ => {},
}
}
// if it's neither of those, stay on the safe side and suggest turbofish,
@ -60,7 +68,7 @@ fn check_fold_with_op(
fold_span: Span,
op: hir::BinOpKind,
replacement: Replacement,
) {
) -> bool {
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind
// Extract the body of the closure passed to fold
&& let closure_body = cx.tcx.hir_body(body)
@ -93,7 +101,7 @@ fn check_fold_with_op(
r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
)
} else {
format!("{method}{turbofish}()", method = replacement.method_name,)
format!("{method}{turbofish}()", method = replacement.method_name)
};
span_lint_and_sugg(
@ -105,12 +113,47 @@ fn check_fold_with_op(
sugg,
applicability,
);
return true;
}
false
}
fn check_fold_with_method(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
acc: &hir::Expr<'_>,
fold_span: Span,
method: Symbol,
replacement: Replacement,
) {
// Extract the name of the function passed to `fold`
if let Res::Def(DefKind::AssocFn, fn_did) = acc.res_if_named(cx, method)
// Check if the function belongs to the operator
&& cx.tcx.is_diagnostic_item(method, fn_did)
{
let applicability = Applicability::MachineApplicable;
let turbofish = if replacement.has_generic_return {
format!("::<{}>", cx.typeck_results().expr_ty(expr))
} else {
String::new()
};
span_lint_and_sugg(
cx,
UNNECESSARY_FOLD,
fold_span.with_hi(expr.span.hi()),
"this `.fold` can be written more succinctly using another method",
"try",
format!("{method}{turbofish}()", method = replacement.method_name),
applicability,
);
}
}
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'tcx>,
init: &hir::Expr<'_>,
acc: &hir::Expr<'_>,
fold_span: Span,
@ -124,60 +167,40 @@ pub(super) fn check(
if let hir::ExprKind::Lit(lit) = init.kind {
match lit.node {
ast::LitKind::Bool(false) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Or,
Replacement {
method_name: "any",
has_args: true,
has_generic_return: false,
},
);
let replacement = Replacement {
method_name: "any",
has_args: true,
has_generic_return: false,
};
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, replacement);
},
ast::LitKind::Bool(true) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::And,
Replacement {
method_name: "all",
has_args: true,
has_generic_return: false,
},
);
let replacement = Replacement {
method_name: "all",
has_args: true,
has_generic_return: false,
};
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, replacement);
},
ast::LitKind::Int(Pu128(0), _) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Add,
Replacement {
method_name: "sum",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
},
);
let replacement = Replacement {
method_name: "sum",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
};
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, replacement) {
check_fold_with_method(cx, expr, acc, fold_span, sym::add, replacement);
}
},
ast::LitKind::Int(Pu128(1), _) => {
check_fold_with_op(
cx,
expr,
acc,
fold_span,
hir::BinOpKind::Mul,
Replacement {
method_name: "product",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
},
);
let replacement = Replacement {
method_name: "product",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
};
if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, replacement) {
check_fold_with_method(cx, expr, acc, fold_span, sym::mul, replacement);
}
},
_ => (),
}

View file

@ -11,11 +11,11 @@ use rustc_span::{Span, sym};
use super::UNNECESSARY_GET_THEN_CHECK;
fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.is_diag_item(cx, sym::HashSet) || ty.is_diag_item(cx, sym::BTreeSet)
matches!(ty.opt_diag_name(cx), Some(sym::HashSet | sym::BTreeSet))
}
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap)
matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap))
}
pub(super) fn check(

View file

@ -46,19 +46,19 @@ pub(super) fn check(
) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
let (kind, none_value, none_prefix) = if ty.is_diag_item(cx, sym::Option) && !is_err {
("an `Option`", "None", "")
} else if ty.is_diag_item(cx, sym::Result)
&& let ty::Adt(_, substs) = ty.kind()
&& let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type()
{
if is_never_like(t_or_e_ty) {
return;
}
let (kind, none_value, none_prefix) = match ty.opt_diag_name(cx) {
Some(sym::Option) if !is_err => ("an `Option`", "None", ""),
Some(sym::Result)
if let ty::Adt(_, substs) = ty.kind()
&& let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() =>
{
if is_never_like(t_or_e_ty) {
return;
}
("a `Result`", if is_err { "Ok" } else { "Err" }, "an ")
} else {
return;
("a `Result`", if is_err { "Ok" } else { "Err" }, "an ")
},
_ => return,
};
let method_suffix = if is_err { "_err" } else { "" };

View file

@ -112,10 +112,14 @@ fn should_lint<'tcx>(
if let ExprKind::MethodCall(path, recv, ..) = &expr.kind {
let recv_ty = typeck_results.expr_ty(recv).peel_refs();
if path.ident.name == sym::debug_struct && recv_ty.is_diag_item(cx, sym::Formatter) {
has_debug_struct = true;
} else if path.ident.name == sym::finish_non_exhaustive && recv_ty.is_diag_item(cx, sym::DebugStruct) {
has_finish_non_exhaustive = true;
match (path.ident.name, recv_ty.opt_diag_name(cx)) {
(sym::debug_struct, Some(sym::Formatter)) => {
has_debug_struct = true;
},
(sym::finish_non_exhaustive, Some(sym::DebugStruct)) => {
has_finish_non_exhaustive = true;
},
_ => {},
}
}
ControlFlow::<!, _>::Continue(())

View file

@ -3,13 +3,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
use hir::def::{DefKind, Res};
use hir::{BlockCheckMode, ExprKind, QPath, UnOp};
use rustc_ast::{BorrowKind, Mutability};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, TyCtxt, TypeckResults};
use rustc_session::declare_lint_pass;
use rustc_span::{DesugaringKind, Span};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -56,12 +57,16 @@ declare_clippy_lint! {
/// }
/// ```
///
/// ### Note
/// ### Notes
///
/// Taking a raw pointer to a union field is always safe and will
/// not be considered unsafe by this lint, even when linting code written
/// with a specified Rust version of 1.91 or earlier (which required
/// using an `unsafe` block).
/// - Unsafe operations only count towards the total for the innermost
/// enclosing `unsafe` block.
/// - Each call to a macro expanding to unsafe operations count for one
/// unsafe operation.
/// - Taking a raw pointer to a union field is always safe and will
/// not be considered unsafe by this lint, even when linting code written
/// with a specified Rust version of 1.91 or earlier (which required
/// using an `unsafe` block).
#[clippy::version = "1.69.0"]
pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
restriction,
@ -71,10 +76,7 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK])
impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_))
|| block.span.in_external_macro(cx.tcx.sess.source_map())
|| block.span.is_desugaring(DesugaringKind::Await)
{
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || block.span.from_expansion() {
return;
}
let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block);
@ -100,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
struct UnsafeExprCollector<'tcx> {
tcx: TyCtxt<'tcx>,
typeck_results: &'tcx TypeckResults<'tcx>,
unsafe_ops: Vec<(&'static str, Span)>,
unsafe_ops: FxHashMap<Span, &'static str>,
}
impl<'tcx> UnsafeExprCollector<'tcx> {
@ -108,10 +110,33 @@ impl<'tcx> UnsafeExprCollector<'tcx> {
let mut collector = Self {
tcx: cx.tcx,
typeck_results: cx.typeck_results(),
unsafe_ops: vec![],
unsafe_ops: FxHashMap::default(),
};
collector.visit_block(block);
collector.unsafe_ops
#[allow(
rustc::potential_query_instability,
reason = "span ordering only needed inside the one expression being walked"
)]
let mut unsafe_ops = collector
.unsafe_ops
.into_iter()
.map(|(span, msg)| (msg, span))
.collect::<Vec<_>>();
unsafe_ops.sort_unstable();
unsafe_ops
}
}
impl UnsafeExprCollector<'_> {
fn insert_span(&mut self, span: Span, message: &'static str) {
if span.from_expansion() {
self.unsafe_ops.insert(
span.source_callsite(),
"this macro call expands into one or more unsafe operations",
);
} else {
self.unsafe_ops.insert(span, message);
}
}
}
@ -126,7 +151,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
return self.visit_expr(e);
},
ExprKind::InlineAsm(_) => self.unsafe_ops.push(("inline assembly used here", expr.span)),
// Do not recurse inside an inner `unsafe` block, it will be checked on its own
ExprKind::Block(block, _) if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) => return,
ExprKind::InlineAsm(_) => self.insert_span(expr.span, "inline assembly used here"),
ExprKind::AddrOf(BorrowKind::Raw, _, mut inner) => {
while let ExprKind::Field(prefix, _) = inner.kind
@ -139,7 +167,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
ExprKind::Field(e, _) => {
if self.typeck_results.expr_ty(e).is_union() {
self.unsafe_ops.push(("union field access occurs here", expr.span));
self.insert_span(expr.span, "union field access occurs here");
}
},
@ -157,12 +185,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
..
},
)) => {
self.unsafe_ops
.push(("access of a mutable static occurs here", expr.span));
self.insert_span(expr.span, "access of a mutable static occurs here");
},
ExprKind::Unary(UnOp::Deref, e) if self.typeck_results.expr_ty(e).is_raw_ptr() => {
self.unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
self.insert_span(expr.span, "raw pointer dereference occurs here");
},
ExprKind::Call(path_expr, _) => {
@ -172,7 +199,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
_ => None,
};
if opt_sig.is_some_and(|sig| sig.safety().is_unsafe()) {
self.unsafe_ops.push(("unsafe function call occurs here", expr.span));
self.insert_span(expr.span, "unsafe function call occurs here");
}
},
@ -182,7 +209,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
.type_dependent_def_id(expr.hir_id)
.map(|def_id| self.tcx.fn_sig(def_id));
if opt_sig.is_some_and(|sig| sig.skip_binder().safety().is_unsafe()) {
self.unsafe_ops.push(("unsafe method call occurs here", expr.span));
self.insert_span(expr.span, "unsafe method call occurs here");
}
},
@ -203,8 +230,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
}
))
) {
self.unsafe_ops
.push(("modification of a mutable static occurs here", expr.span));
self.insert_span(expr.span, "modification of a mutable static occurs here");
return self.visit_expr(rhs);
}
},

View file

@ -1,7 +1,7 @@
use super::needless_pass_by_value::requires_exact_signature;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use clippy_utils::source::HasSession as _;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self};
use core::ops::ControlFlow;
@ -18,9 +18,9 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarId, UpvarPath};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
use rustc_span::symbol::kw;
use rustc_span::{BytePos, Span};
declare_clippy_lint! {
/// ### What it does
@ -269,18 +269,27 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
// If the argument is never used mutably, we emit the warning.
let sp = input.span;
if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind {
let Some(after_mut_span) = cx.sess().source_map().span_extend_to_prev_str(
inner_ty.ty.span.shrink_to_lo(),
"mut",
true,
true,
) else {
return;
};
let mut_span = after_mut_span.with_lo(after_mut_span.lo() - BytePos(3));
let is_cfged = is_cfged.get_or_insert_with(|| inherits_cfg(cx.tcx, *fn_def_id));
span_lint_hir_and_then(
cx,
NEEDLESS_PASS_BY_REF_MUT,
cx.tcx.local_def_id_to_hir_id(*fn_def_id),
sp,
"this argument is a mutable reference, but not used mutably",
"this parameter is a mutable reference but is not used mutably",
|diag| {
diag.span_suggestion(
sp,
"consider changing to".to_string(),
format!("&{}", snippet(cx, cx.tcx.hir_span(inner_ty.ty.hir_id), "_"),),
mut_span,
"consider removing this `mut`",
"",
Applicability::Unspecified,
);
if cx.effective_visibilities.is_exported(*fn_def_id) {

View file

@ -14,13 +14,14 @@ pub(super) fn check<'tcx>(
l: &Expr<'_>,
r: &Expr<'_>,
) -> bool {
let mut applicability = Applicability::MachineApplicable;
let non_null_path_snippet = match (
is_lint_allowed(cx, CMP_NULL, expr.hir_id),
is_null_path(cx, l),
is_null_path(cx, r),
) {
(false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(),
(false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(),
(false, true, false) => Sugg::hir_with_context(cx, r, expr.span.ctxt(), "..", &mut applicability).maybe_paren(),
(false, false, true) => Sugg::hir_with_context(cx, l, expr.span.ctxt(), "..", &mut applicability).maybe_paren(),
_ => return false,
};
let invert = if op == BinOpKind::Eq { "" } else { "!" };
@ -32,7 +33,7 @@ pub(super) fn check<'tcx>(
"comparing with null is better expressed by the `.is_null()` method",
"try",
format!("{invert}{non_null_path_snippet}.is_null()",),
Applicability::MachineApplicable,
applicability,
);
true
}

View file

@ -45,7 +45,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// This lint checks for equality comparisons with `ptr::null`
/// This lint checks for equality comparisons with `ptr::null` or `ptr::null_mut`
///
/// ### Why is this bad?
/// It's easier and more readable to use the inherent
@ -56,7 +56,7 @@ declare_clippy_lint! {
/// ```rust,ignore
/// use std::ptr;
///
/// if x == ptr::null {
/// if x == ptr::null() {
/// // ..
/// }
/// ```

View file

@ -0,0 +1,105 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::res::MaybeDef;
use clippy_utils::{eq_expr_value, sym};
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym as rustc_sym;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for usages of `Vec::from_raw_parts` and `String::from_raw_parts`
/// where the same expression is used for the length and the capacity.
///
/// ### Why is this bad?
///
/// If the same expression is being passed for the length and
/// capacity, it is most likely a semantic error. In the case of a
/// Vec, for example, the only way to end up with one that has
/// the same length and capacity is by going through a boxed slice,
/// e.g. `Box::from(some_vec)`, which shrinks the capacity to match
/// the length.
///
/// ### Example
///
/// ```no_run
/// #![feature(vec_into_raw_parts)]
/// let mut original: Vec::<i32> = Vec::with_capacity(20);
/// original.extend([1, 2, 3, 4, 5]);
///
/// let (ptr, mut len, cap) = original.into_raw_parts();
///
/// // I will add three more integers:
/// unsafe {
/// let ptr = ptr as *mut i32;
///
/// for i in 6..9 {
/// *ptr.add(i - 1) = i as i32;
/// len += 1;
/// }
/// }
///
/// // But I forgot the capacity was separate from the length:
/// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, len) };
/// ```
///
/// Use instead:
/// ```no_run
/// #![feature(vec_into_raw_parts)]
/// let mut original: Vec::<i32> = Vec::with_capacity(20);
/// original.extend([1, 2, 3, 4, 5]);
///
/// let (ptr, mut len, cap) = original.into_raw_parts();
///
/// // I will add three more integers:
/// unsafe {
/// let ptr = ptr as *mut i32;
///
/// for i in 6..9 {
/// *ptr.add(i - 1) = i as i32;
/// len += 1;
/// }
/// }
///
/// // This time, leverage the previously saved capacity:
/// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, cap) };
/// ```
#[clippy::version = "1.93.0"]
pub SAME_LENGTH_AND_CAPACITY,
pedantic,
"`from_raw_parts` with same length and capacity"
}
declare_lint_pass!(SameLengthAndCapacity => [SAME_LENGTH_AND_CAPACITY]);
impl<'tcx> LateLintPass<'tcx> for SameLengthAndCapacity {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Call(path_expr, args) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind
&& fn_path.ident.name == sym::from_raw_parts
&& args.len() >= 3
&& eq_expr_value(cx, &args[1], &args[2])
{
let middle_ty = cx.typeck_results().node_type(ty.hir_id);
if middle_ty.is_diag_item(cx, rustc_sym::Vec) {
span_lint_and_help(
cx,
SAME_LENGTH_AND_CAPACITY,
expr.span,
"usage of `Vec::from_raw_parts` with the same expression for length and capacity",
None,
"try `Box::from(slice::from_raw_parts(...)).into::<Vec<_>>()`",
);
} else if middle_ty.is_lang_item(cx, LangItem::String) {
span_lint_and_help(
cx,
SAME_LENGTH_AND_CAPACITY,
expr.span,
"usage of `String::from_raw_parts` with the same expression for length and capacity",
None,
"try `String::from(str::from_utf8_unchecked(slice::from_raw_parts(...)))`",
);
}
}
}
}

View file

@ -112,6 +112,16 @@ fn try_parse_op_call<'tcx>(
None
}
fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool {
// Guard on type to avoid useless potentially expansive `SpanlessEq` checks
cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr()
&& matches!(
cx.typeck_results().expr_ty(expr).peel_refs().opt_diag_name(cx),
Some(sym::HashSet | sym::BTreeSet)
)
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, expr.peel_borrows())
}
fn find_insert_calls<'tcx>(
cx: &LateContext<'tcx>,
contains_expr: &OpExpr<'tcx>,
@ -122,9 +132,14 @@ fn find_insert_calls<'tcx>(
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
{
ControlFlow::Break(insert_expr)
} else {
ControlFlow::Continue(())
return ControlFlow::Break(Some(insert_expr));
}
if is_set_mutated(cx, contains_expr, e) {
return ControlFlow::Break(None);
}
ControlFlow::Continue(())
})
.flatten()
}

View file

@ -8,7 +8,6 @@ use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::sym;
declare_clippy_lint! {
@ -84,43 +83,38 @@ impl_lint_pass!(UncheckedTimeSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_T
impl LateLintPass<'_> for UncheckedTimeSubtraction {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
let (lhs, rhs) = match expr.kind {
ExprKind::Binary(op, lhs, rhs) if matches!(op.node, BinOpKind::Sub,) => (lhs, rhs),
ExprKind::MethodCall(fn_name, lhs, [rhs], _) if cx.ty_based_def(expr).is_diag_item(cx, sym::sub) => {
(lhs, rhs)
},
lhs,
rhs,
) = expr.kind
{
let typeck = cx.typeck_results();
let lhs_ty = typeck.expr_ty(lhs);
let rhs_ty = typeck.expr_ty(rhs);
_ => return,
};
let typeck = cx.typeck_results();
let lhs_name = typeck.expr_ty(lhs).opt_diag_name(cx);
let rhs_name = typeck.expr_ty(rhs).opt_diag_name(cx);
if lhs_ty.is_diag_item(cx, sym::Instant) {
// Instant::now() - instant
if is_instant_now_call(cx, lhs)
&& rhs_ty.is_diag_item(cx, sym::Instant)
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
{
print_manual_instant_elapsed_sugg(cx, expr, sugg);
}
// instant - duration
else if rhs_ty.is_diag_item(cx, sym::Duration)
&& !expr.span.from_expansion()
&& self.msrv.meets(cx, msrvs::TRY_FROM)
{
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
}
if lhs_name == Some(sym::Instant) {
// Instant::now() - instant
if is_instant_now_call(cx, lhs) && rhs_name == Some(sym::Instant) {
print_manual_instant_elapsed_sugg(cx, expr, rhs);
}
// duration - duration
else if lhs_ty.is_diag_item(cx, sym::Duration)
&& rhs_ty.is_diag_item(cx, sym::Duration)
// instant - duration
else if rhs_name == Some(sym::Duration)
&& !expr.span.from_expansion()
&& self.msrv.meets(cx, msrvs::TRY_FROM)
{
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
}
}
// duration - duration
else if lhs_name == Some(sym::Duration)
&& rhs_name == Some(sym::Duration)
&& !expr.span.from_expansion()
&& self.msrv.meets(cx, msrvs::TRY_FROM)
{
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
}
}
}
@ -150,10 +144,12 @@ fn is_chained_time_subtraction(cx: &LateContext<'_>, lhs: &Expr<'_>) -> bool {
/// Returns true if the type is Duration or Instant
fn is_time_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.is_diag_item(cx, sym::Duration) || ty.is_diag_item(cx, sym::Instant)
matches!(ty.opt_diag_name(cx), Some(sym::Duration | sym::Instant))
}
fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, rhs: &Expr<'_>) {
let mut applicability = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, rhs, expr.span.ctxt(), "<instant>", &mut applicability);
span_lint_and_sugg(
cx,
MANUAL_INSTANT_ELAPSED,
@ -161,7 +157,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_paren()),
Applicability::MachineApplicable,
applicability,
);
}
@ -181,8 +177,9 @@ fn print_unchecked_duration_subtraction_sugg(
// avoid suggestions
if !is_chained_time_subtraction(cx, left_expr) {
let mut applicability = Applicability::MachineApplicable;
let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "<left>", &mut applicability);
let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "<right>", &mut applicability);
let left_sugg = Sugg::hir_with_context(cx, left_expr, expr.span.ctxt(), "<left>", &mut applicability);
let right_sugg =
Sugg::hir_with_context(cx, right_expr, expr.span.ctxt(), "<right>", &mut applicability);
diag.span_suggestion(
expr.span,

View file

@ -1,6 +1,6 @@
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_integer_literal;
use clippy_utils::is_integer_const;
use clippy_utils::res::{MaybeDef, MaybeResPath};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
// Catching:
// `std::mem::transmute(0 as *const i32)`
if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind
&& is_integer_literal(inner_expr, 0)
&& is_integer_const(cx, inner_expr, 0)
{
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
return true;
@ -42,10 +42,5 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
return true;
}
// FIXME:
// Also catch transmutations of variables which are known nulls.
// To do this, MIR const propagation seems to be the better tool.
// Whenever MIR const prop routines are more developed, this will
// become available. As of this writing (25/03/19) it is not yet.
false
}

View file

@ -10,13 +10,14 @@ use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty};
use rustc_hir::{
self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParamKind, HirId, Impl,
ImplItemImplKind, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind,
ImplItemImplKind, ImplItemKind, Item, ItemKind, Node, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty as MiddleTy;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use std::iter;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
@ -213,6 +214,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
path.res,
Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::Def(DefKind::TyParam, _)
)
&& !ty_is_in_generic_args(cx, hir_ty)
&& !types_to_skip.contains(&hir_ty.hir_id)
&& let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty())
&& let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity()
@ -312,6 +314,38 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
}
}
fn ty_is_in_generic_args<'tcx>(cx: &LateContext<'tcx>, hir_ty: &Ty<'tcx, AmbigArg>) -> bool {
cx.tcx.hir_parent_iter(hir_ty.hir_id).any(|(_, parent)| {
matches!(parent, Node::ImplItem(impl_item) if impl_item.generics.params.iter().any(|param| {
let GenericParamKind::Const { ty: const_ty, .. } = &param.kind else {
return false;
};
ty_contains_ty(const_ty, hir_ty)
}))
})
}
fn ty_contains_ty<'tcx>(outer: &Ty<'tcx>, inner: &Ty<'tcx, AmbigArg>) -> bool {
struct ContainsVisitor<'tcx> {
inner: &'tcx Ty<'tcx, AmbigArg>,
}
impl<'tcx> Visitor<'tcx> for ContainsVisitor<'tcx> {
type Result = ControlFlow<()>;
fn visit_ty(&mut self, t: &'tcx Ty<'tcx, AmbigArg>) -> Self::Result {
if t.hir_id == self.inner.hir_id {
return ControlFlow::Break(());
}
walk_ty(self, t)
}
}
let mut visitor = ContainsVisitor { inner };
visitor.visit_ty_unambig(outer).is_break()
}
/// Checks whether types `a` and `b` have the same lifetime parameters.
///
/// This function does not check that types `a` and `b` are the same types.

View file

@ -1,37 +1,43 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::MacroCall;
use clippy_utils::source::expand_past_previous_comma;
use clippy_utils::sym;
use clippy_utils::{span_extract_comments, sym};
use rustc_ast::{FormatArgs, FormatArgsPiece};
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_lint::{LateContext, LintContext};
use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING};
pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] {
let mut span = format_args.span;
let lint = if name == "writeln" {
span = expand_past_previous_comma(cx, span);
WRITELN_EMPTY_STRING
} else {
PRINTLN_EMPTY_STRING
};
let is_writeln = name == "writeln";
span_lint_and_then(
cx,
lint,
if is_writeln {
WRITELN_EMPTY_STRING
} else {
PRINTLN_EMPTY_STRING
},
macro_call.span,
format!("empty string literal in `{name}!`"),
|diag| {
diag.span_suggestion(
span,
"remove the empty string",
String::new(),
Applicability::MachineApplicable,
);
if span_extract_comments(cx.sess().source_map(), macro_call.span).is_empty() {
let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before(
macro_call.span.shrink_to_hi(),
')',
false,
);
let mut span = format_args.span.with_hi(closing_paren.lo());
if is_writeln {
span = expand_past_previous_comma(cx, span);
}
diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable);
} else {
// If there is a comment in the span of macro call, we don't provide an auto-fix suggestion.
diag.span_note(format_args.span, "remove the empty string");
}
},
);
}

View file

@ -49,7 +49,7 @@ impl LateLintPass<'_> for ZeroSizedMapValues {
&& !in_trait_impl(cx, hir_ty.hir_id)
// We don't care about infer vars
&& let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty())
&& (ty.is_diag_item(cx, sym::HashMap) || ty.is_diag_item(cx, sym::BTreeMap))
&& matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap))
&& let ty::Adt(_, args) = ty.kind()
&& let ty = args.type_at(1)
// Ensure that no type information is missing, to avoid a delayed bug in the compiler if this is not the case.

View file

@ -1,9 +1,11 @@
use crate::lint_without_lint_pass::is_lint_ref_type;
use clippy_utils::diagnostics::span_lint_and_help;
use regex::Regex;
use rustc_ast::token::DocFragmentKind;
use rustc_hir::{Attribute, Item, ItemKind, Mutability};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, Symbol};
declare_tool_lint! {
/// ### What it does
@ -46,28 +48,22 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let mut check_next = false;
if let ItemKind::Static(Mutability::Not, _, ty, _) = item.kind {
let lines = cx
.tcx
.hir_attrs(item.hir_id())
.iter()
.filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr)));
let lines = cx.tcx.hir_attrs(item.hir_id()).iter().filter_map(doc_attr);
if is_lint_ref_type(cx, ty) {
for (line, attr) in lines {
for (line, span) in lines {
let cur_line = line.as_str().trim();
if check_next && !cur_line.is_empty() {
for formulation in &self.standard_formulations {
let starts_with_correct_formulation = cur_line.starts_with(formulation.correction);
if !starts_with_correct_formulation && formulation.wrong_pattern.is_match(cur_line) {
if let Some(ident) = attr.ident() {
span_lint_and_help(
cx,
ALMOST_STANDARD_LINT_FORMULATION,
ident.span,
"non-standard lint formulation",
None,
format!("consider using `{}`", formulation.correction),
);
}
span_lint_and_help(
cx,
ALMOST_STANDARD_LINT_FORMULATION,
span,
"non-standard lint formulation",
None,
format!("consider using `{}`", formulation.correction),
);
return;
}
}
@ -84,3 +80,10 @@ impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation {
}
}
}
fn doc_attr(attr: &Attribute) -> Option<(Symbol, Span)> {
match Attribute::doc_str_and_fragment_kind(attr) {
Some((symbol, DocFragmentKind::Raw(span))) => Some((symbol, span)),
_ => None,
}
}

View file

@ -17,6 +17,7 @@ pub static TY_CTXT: PathLookup = type_path!(rustc_middle::ty::TyCtxt);
// Paths in clippy itself
pub static CLIPPY_SYM_MODULE: PathLookup = type_path!(clippy_utils::sym);
pub static MAYBE_DEF: PathLookup = type_path!(clippy_utils::res::MaybeDef);
pub static MSRV_STACK: PathLookup = type_path!(clippy_utils::msrvs::MsrvStack);
pub static PATH_LOOKUP_NEW: PathLookup = value_path!(clippy_utils::paths::PathLookup::new);
pub static SPAN_LINT_AND_THEN: PathLookup = value_path!(clippy_utils::diagnostics::span_lint_and_then);

View file

@ -38,6 +38,7 @@ mod lint_without_lint_pass;
mod msrv_attr_impl;
mod outer_expn_data_pass;
mod produce_ice;
mod repeated_is_diagnostic_item;
mod symbols;
mod unnecessary_def_path;
mod unsorted_clippy_utils_paths;
@ -77,4 +78,5 @@ pub fn register_lints(store: &mut LintStore) {
store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl));
store.register_late_pass(|_| Box::new(almost_standard_lint_formulation::AlmostStandardFormulation::new()));
store.register_late_pass(|_| Box::new(unusual_names::UnusualNames));
store.register_late_pass(|_| Box::new(repeated_is_diagnostic_item::RepeatedIsDiagnosticItem));
}

View file

@ -0,0 +1,561 @@
use std::iter;
use std::ops::ControlFlow;
use crate::internal_paths::MAYBE_DEF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::{snippet_indent, snippet_with_applicability};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, if_sequence, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Node, StmtKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_tool_lint! {
/// ### What it does
/// Checks for repeated use of `MaybeDef::is_diag_item`/`TyCtxt::is_diagnostic_item`;
/// suggests to first call `MaybDef::opt_diag_name`/`TyCtxt::get_diagnostic_name` and then
/// compare the output with all the `Symbol`s.
///
/// ### Why is this bad?
/// Each of such calls ultimately invokes the `diagnostic_items` query.
/// While the query is cached, it's still better to avoid calling it multiple times if possible.
///
/// ### Example
/// ```no_run
/// ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result)
/// cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did)
///
/// if ty.is_diag_item(cx, sym::Option) {
/// ..
/// } else if ty.is_diag_item(cx, sym::Result) {
/// ..
/// } else {
/// ..
/// }
///
/// if cx.tcx.is_diagnostic_item(sym::Option, did) {
/// ..
/// } else if cx.tcx.is_diagnostic_item(sym::Result, did) {
/// ..
/// } else {
/// ..
/// }
///
/// {
/// if ty.is_diag_item(cx, sym::Option) {
/// ..
/// }
/// if ty.is_diag_item(cx, sym::Result) {
/// ..
/// }
/// }
///
/// {
/// if cx.tcx.is_diagnostic_item(sym::Option, did) {
/// ..
/// }
/// if cx.tcx.is_diagnostic_item(sym::Result, did) {
/// ..
/// }
/// }
/// ```
/// Use instead:
/// ```no_run
/// matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result))
/// matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result))
///
/// match ty.opt_diag_name(cx) {
/// Some(sym::Option) => {
/// ..
/// }
/// Some(sym::Result) => {
/// ..
/// }
/// _ => {
/// ..
/// }
/// }
///
/// match cx.tcx.get_diagnostic_name(did) {
/// Some(sym::Option) => {
/// ..
/// }
/// Some(sym::Result) => {
/// ..
/// }
/// _ => {
/// ..
/// }
/// }
///
/// {
/// let name = ty.opt_diag_name(cx);
/// if name == Some(sym::Option) {
/// ..
/// }
/// if name == Some(sym::Result) {
/// ..
/// }
/// }
///
/// {
/// let name = cx.tcx.get_diagnostic_name(did);
/// if name == Some(sym::Option) {
/// ..
/// }
/// if name == Some(sym::Result) {
/// ..
/// }
/// }
/// ```
pub clippy::REPEATED_IS_DIAGNOSTIC_ITEM,
Warn,
"repeated use of `MaybeDef::is_diag_item`/`TyCtxt::is_diagnostic_item`"
}
declare_lint_pass!(RepeatedIsDiagnosticItem => [REPEATED_IS_DIAGNOSTIC_ITEM]);
const NOTE: &str = "each call performs the same compiler query -- it's faster to query once, and reuse the results";
impl<'tcx> LateLintPass<'tcx> for RepeatedIsDiagnosticItem {
#[expect(clippy::too_many_lines)]
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
for [(cond1, stmt1_span), (cond2, stmt2_span)] in block
.stmts
.windows(2)
.filter_map(|pair| {
if let [if1, if2] = pair
&& let StmtKind::Expr(e1) | StmtKind::Semi(e1) = if1.kind
&& let ExprKind::If(cond1, ..) = e1.kind
&& let StmtKind::Expr(e2) | StmtKind::Semi(e2) = if2.kind
&& let ExprKind::If(cond2, ..) = e2.kind
{
Some([(cond1, if1.span), (cond2, if2.span)])
} else {
None
}
})
.chain(
if let Some(if1) = block.stmts.last()
&& let StmtKind::Expr(e1) | StmtKind::Semi(e1) = if1.kind
&& let ExprKind::If(cond1, ..) = e1.kind
&& let Some(e2) = block.expr
&& let ExprKind::If(cond2, ..) = e2.kind
{
Some([(cond1, if1.span), (cond2, e2.span)])
} else {
None
},
)
{
let lint_span = stmt1_span.to(stmt2_span);
// if recv1.is_diag_item(cx, sym1) && .. {
// ..
// }
// if recv2.is_diag_item(cx, sym2) && .. {
// ..
// }
if let Some(first @ (span1, (cx1, recv1, _))) = extract_nested_is_diag_item(cx, cond1)
&& let Some(second @ (span2, (cx2, recv2, _))) = extract_nested_is_diag_item(cx, cond2)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
let recv_ty = recv_ty.trim_end_matches("<'_>");
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
lint_span,
format!("repeated calls to `{recv_ty}::is_diag_item`"),
|diag| {
diag.span_labels([span1, span2], "called here");
diag.note(NOTE);
let mut app = Applicability::HasPlaceholders;
let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app);
let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app);
let indent = snippet_indent(cx, stmt1_span).unwrap_or_default();
let sugg: Vec<_> = iter::once((
stmt1_span.shrink_to_lo(),
format!("let /* name */ = {recv}.opt_diag_name({cx_str});\n{indent}"),
)) // call `opt_diag_name` once
.chain([first, second].into_iter().map(|(expr_span, (_, _, sym))| {
let sym = snippet_with_applicability(cx, sym.span, "_", &mut app);
(expr_span, format!("/* name */ == Some({sym})"))
}))
.collect();
diag.multipart_suggestion_verbose(
format!("call `{recv_ty}::opt_diag_name`, and reuse the results"),
sugg,
app,
);
},
);
return;
}
// if cx.tcx.is_diagnostic_item(sym1, did) && .. {
// ..
// }
// if cx.tcx.is_diagnostic_item(sym2, did) && .. {
// ..
// }
if let Some(first @ (span1, (tcx1, did1, _))) = extract_nested_is_diagnostic_item(cx, cond1)
&& let Some(second @ (span2, (tcx2, did2, _))) = extract_nested_is_diagnostic_item(cx, cond2)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
{
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
lint_span,
"repeated calls to `TyCtxt::is_diagnostic_item`",
|diag| {
diag.span_labels([span1, span2], "called here");
diag.note(NOTE);
let mut app = Applicability::HasPlaceholders;
let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app);
let did = snippet_with_applicability(cx, did1.span, "_", &mut app);
let indent = snippet_indent(cx, stmt1_span).unwrap_or_default();
let sugg: Vec<_> = iter::once((
stmt1_span.shrink_to_lo(),
format!("let /* name */ = {tcx}.get_diagnostic_name({did});\n{indent}"),
)) // call `get_diagnostic_name` once
.chain([first, second].into_iter().map(|(expr_span, (_, _, sym))| {
let sym = snippet_with_applicability(cx, sym.span, "_", &mut app);
(expr_span, format!("/* name */ == Some({sym})"))
}))
.collect();
diag.multipart_suggestion_verbose(
"call `TyCtxt::get_diagnostic_name`, and reuse the results",
sugg,
app,
);
},
);
}
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(op, left, right) = expr.kind {
if op.node == BinOpKind::Or {
check_ors(cx, expr.span, left, right);
} else if op.node == BinOpKind::And
&& let ExprKind::Unary(UnOp::Not, left) = left.kind
&& let ExprKind::Unary(UnOp::Not, right) = right.kind
{
check_ands(cx, expr.span, left, right);
}
} else if let (conds, _) = if_sequence(expr)
&& !conds.is_empty()
{
check_if_chains(cx, expr, conds);
}
}
}
fn check_ors(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) {
// recv1.is_diag_item(cx, sym1) || recv2.is_diag_item(cx, sym2)
if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left)
&& let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
let recv_ty = recv_ty.trim_end_matches("<'_>");
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
span,
format!("repeated calls to `{recv_ty}::is_diag_item`"),
|diag| {
diag.note(NOTE);
let mut app = Applicability::MachineApplicable;
let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app);
let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app);
let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app);
let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app);
diag.span_suggestion_verbose(
span,
format!("call `{recv_ty}::opt_diag_name`, and reuse the results"),
format!("matches!({recv}.opt_diag_name({cx_str}), Some({sym1} | {sym2}))"),
app,
);
},
);
return;
}
// cx.tcx.is_diagnostic_item(sym1, did) || cx.tcx.is_diagnostic_item(sym2, did)
if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left)
&& let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
{
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
span,
"repeated calls to `TyCtxt::is_diagnostic_item`",
|diag| {
diag.note(NOTE);
let mut app = Applicability::MachineApplicable;
let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app);
let did = snippet_with_applicability(cx, did1.span, "_", &mut app);
let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app);
let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app);
diag.span_suggestion_verbose(
span,
"call `TyCtxt::get_diagnostic_name`, and reuse the results",
format!("matches!({tcx}.get_diagnostic_name({did}), Some({sym1} | {sym2}))"),
app,
);
},
);
}
}
fn check_ands(cx: &LateContext<'_>, span: Span, left: &Expr<'_>, right: &Expr<'_>) {
// !recv1.is_diag_item(cx, sym1) && !recv2.is_diag_item(cx, sym2)
if let Some((cx1, recv1, sym1)) = extract_is_diag_item(cx, left)
&& let Some((cx2, recv2, sym2)) = extract_is_diag_item(cx, right)
&& eq_expr_value(cx, cx1, cx2)
&& eq_expr_value(cx, recv1, recv2)
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
let recv_ty = recv_ty.trim_end_matches("<'_>");
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
span,
format!("repeated calls to `{recv_ty}::is_diag_item`"),
|diag| {
diag.note(NOTE);
let mut app = Applicability::MachineApplicable;
let cx_str = snippet_with_applicability(cx, cx1.span, "_", &mut app);
let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app);
let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app);
let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app);
diag.span_suggestion_verbose(
span,
format!("call `{recv_ty}::opt_diag_name`, and reuse the results"),
format!("!matches!({recv}.opt_diag_name({cx_str}), Some({sym1} | {sym2}))"),
app,
);
},
);
return;
}
// !cx.tcx.is_diagnostic_item(sym1, did) && !cx.tcx.is_diagnostic_item(sym2, did)
if let Some((tcx1, did1, sym1)) = extract_is_diagnostic_item(cx, left)
&& let Some((tcx2, did2, sym2)) = extract_is_diagnostic_item(cx, right)
&& eq_expr_value(cx, tcx1, tcx2)
&& eq_expr_value(cx, did1, did2)
{
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
span,
"repeated calls to `TyCtxt::is_diagnostic_item`",
|diag| {
diag.note(NOTE);
let mut app = Applicability::MachineApplicable;
let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app);
let did = snippet_with_applicability(cx, did1.span, "_", &mut app);
let sym1 = snippet_with_applicability(cx, sym1.span, "_", &mut app);
let sym2 = snippet_with_applicability(cx, sym2.span, "_", &mut app);
diag.span_suggestion_verbose(
span,
"call `TyCtxt::get_diagnostic_name`, and reuse the results",
format!("!matches!({tcx}.get_diagnostic_name({did}), Some({sym1} | {sym2}))"),
app,
);
},
);
}
}
fn check_if_chains<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, conds: Vec<&'tcx Expr<'_>>) {
// if ty.is_diag_item(cx, sym1) {
// ..
// } else if ty.is_diag_item(cx, sym2) {
// ..
// } else {
// ..
// }
let mut found = conds.iter().filter_map(|cond| extract_nested_is_diag_item(cx, cond));
if let Some(first @ (_, (cx_1, recv1, _))) = found.next()
&& let other =
found.filter(|(_, (cx_, recv, _))| eq_expr_value(cx, cx_, cx_1) && eq_expr_value(cx, recv, recv1))
&& let results = iter::once(first).chain(other).collect::<Vec<_>>()
&& results.len() > 1
{
let recv_ty =
with_forced_trimmed_paths!(format!("{}", cx.typeck_results().expr_ty_adjusted(recv1).peel_refs()));
let recv_ty = recv_ty.trim_end_matches("<'_>");
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
expr.span,
format!("repeated calls to `{recv_ty}::is_diag_item`"),
|diag| {
diag.span_labels(results.iter().map(|(span, _)| *span), "called here");
diag.note(NOTE);
let mut app = Applicability::HasPlaceholders;
let cx_str = snippet_with_applicability(cx, cx_1.span, "_", &mut app);
let recv = snippet_with_applicability(cx, recv1.span, "_", &mut app);
let span_before = if let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(expr.hir_id) {
let_stmt.span
} else {
expr.span
};
let indent = snippet_indent(cx, span_before).unwrap_or_default();
let sugg: Vec<_> = iter::once((
span_before.shrink_to_lo(),
format!("let /* name */ = {recv}.opt_diag_name({cx_str});\n{indent}"),
)) // call `opt_diag_name` once
.chain(results.into_iter().map(|(expr_span, (_, _, sym))| {
let sym = snippet_with_applicability(cx, sym.span, "_", &mut app);
(expr_span, format!("/* name */ == Some({sym})"))
}))
.collect();
diag.multipart_suggestion_verbose(
format!("call `{recv_ty}::opt_diag_name`, and reuse the results"),
sugg,
app,
);
},
);
}
// if cx.tcx.is_diagnostic_item(sym1, did) {
// ..
// } else if cx.tcx.is_diagnostic_item(sym2, did) {
// ..
// } else {
// ..
// }
let mut found = conds
.into_iter()
.filter_map(|cond| extract_nested_is_diagnostic_item(cx, cond));
if let Some(first @ (_, (tcx1, did1, _))) = found.next()
&& let other = found.filter(|(_, (tcx, did, _))| eq_expr_value(cx, tcx, tcx1) && eq_expr_value(cx, did, did1))
&& let results = iter::once(first).chain(other).collect::<Vec<_>>()
&& results.len() > 1
{
span_lint_and_then(
cx,
REPEATED_IS_DIAGNOSTIC_ITEM,
expr.span,
"repeated calls to `TyCtxt::is_diagnostic_item`",
|diag| {
diag.span_labels(results.iter().map(|(span, _)| *span), "called here");
diag.note(NOTE);
let mut app = Applicability::HasPlaceholders;
let tcx = snippet_with_applicability(cx, tcx1.span, "_", &mut app);
let recv = snippet_with_applicability(cx, did1.span, "_", &mut app);
let span_before = if let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(expr.hir_id) {
let_stmt.span
} else {
expr.span
};
let indent = snippet_indent(cx, span_before).unwrap_or_default();
let sugg: Vec<_> = iter::once((
span_before.shrink_to_lo(),
format!("let /* name */ = {tcx}.get_diagnostic_name({recv});\n{indent}"),
)) // call `get_diagnostic_name` once
.chain(results.into_iter().map(|(expr_span, (_, _, sym))| {
let sym = snippet_with_applicability(cx, sym.span, "_", &mut app);
(expr_span, format!("/* name */ == Some({sym})"))
}))
.collect();
diag.multipart_suggestion_verbose(
"call `TyCtxt::get_diagnostic_name`, and reuse the results",
sugg,
app,
);
},
);
}
}
fn extract_is_diag_item<'tcx>(
cx: &LateContext<'_>,
expr: &'tcx Expr<'tcx>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
if let ExprKind::MethodCall(is_diag_item, recv, [cx_, sym], _) = expr.kind
&& is_diag_item.ident.name == sym::is_diag_item
// Whether this a method from the `MaybeDef` trait
&& let Some(did) = cx.ty_based_def(expr).opt_parent(cx).opt_def_id()
&& MAYBE_DEF.matches(cx, did)
{
Some((cx_, recv, sym))
} else {
None
}
}
fn extract_is_diagnostic_item<'tcx>(
cx: &LateContext<'_>,
expr: &'tcx Expr<'tcx>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
if let ExprKind::MethodCall(is_diag_item, tcx, [sym, did], _) = expr.kind
&& is_diag_item.ident.name == sym::is_diagnostic_item
// Whether this is an inherent method on `TyCtxt`
&& cx
.ty_based_def(expr)
.opt_parent(cx)
.opt_impl_ty(cx)
.is_diag_item(cx, sym::TyCtxt)
{
Some((tcx, did, sym))
} else {
None
}
}
fn extract_nested_is_diag_item<'tcx>(
cx: &LateContext<'tcx>,
cond: &'tcx Expr<'_>,
) -> Option<(Span, (&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>))> {
for_each_expr(cx, cond, |cond_part| {
if let Some(res) = extract_is_diag_item(cx, cond_part) {
ControlFlow::Break((cond_part.span, res))
} else {
ControlFlow::Continue(())
}
})
}
fn extract_nested_is_diagnostic_item<'tcx>(
cx: &LateContext<'tcx>,
cond: &'tcx Expr<'_>,
) -> Option<(Span, (&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>))> {
for_each_expr(cx, cond, |cond_part| {
if let Some(res) = extract_is_diagnostic_item(cx, cond_part) {
ControlFlow::Break((cond_part.span, res))
} else {
ControlFlow::Continue(())
}
})
}

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-12-11
nightly-2025-12-25
```
<!-- end autogenerated nightly -->

View file

@ -86,7 +86,7 @@ pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
/// Checks whether `attrs` contain `#[doc(hidden)]`
pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
attrs.iter().any(|attr| attr.is_doc_hidden())
attrs.iter().any(AttributeExt::is_doc_hidden)
}
/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]`

View file

@ -809,10 +809,12 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
| sym::i128_legacy_const_max
)
) || self.tcx.opt_parent(did).is_some_and(|parent| {
parent.is_diag_item(&self.tcx, sym::f16_consts_mod)
|| parent.is_diag_item(&self.tcx, sym::f32_consts_mod)
|| parent.is_diag_item(&self.tcx, sym::f64_consts_mod)
|| parent.is_diag_item(&self.tcx, sym::f128_consts_mod)
matches!(
parent.opt_diag_name(&self.tcx),
Some(
sym::f16_consts_mod | sym::f32_consts_mod | sym::f64_consts_mod | sym::f128_consts_mod
)
)
})) =>
{
did
@ -1139,7 +1141,9 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx
ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value),
ConstItemRhs::TypeConst(const_arg) => match const_arg.kind {
ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value),
ConstArgKind::Struct(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None,
ConstArgKind::Struct(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => {
None
},
},
}
}

View file

@ -4,14 +4,17 @@ use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context};
use crate::tokenize_with_text;
use rustc_ast::ast;
use rustc_ast::ast::InlineAsmTemplatePiece;
use rustc_data_structures::fx::FxHasher;
use rustc_data_structures::fx::{FxHasher, FxIndexMap};
use rustc_hir::MatchSource::TryDesugar;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, ByRef, Closure, ConstArg, ConstArgKind, Expr,
ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime,
LifetimeKind, Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
StructTailExpr, TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind,
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, ByRef, Closure, ConstArg, ConstArgKind, ConstItemRhs,
Expr, ExprField, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericArgs, GenericBound, GenericBounds,
GenericParam, GenericParamKind, GenericParamSource, Generics, HirId, HirIdMap, InlineAsmOperand, ItemId, ItemKind,
LetExpr, Lifetime, LifetimeKind, LifetimeParamKind, Node, ParamName, Pat, PatExpr, PatExprKind, PatField, PatKind,
Path, PathSegment, PreciseCapturingArgKind, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, TraitBoundModifiers, Ty,
TyKind, TyPat, TyPatKind, UseKind, WherePredicate, WherePredicateKind,
};
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
use rustc_lint::LateContext;
@ -106,6 +109,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
left_ctxt: SyntaxContext::root(),
right_ctxt: SyntaxContext::root(),
locals: HirIdMap::default(),
local_items: FxIndexMap::default(),
}
}
@ -144,6 +148,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> {
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
// these blocks are considered equal since `x` is mapped to `y`.
pub locals: HirIdMap<HirId>,
pub local_items: FxIndexMap<DefId, DefId>,
}
impl HirEqInterExpr<'_, '_, '_> {
@ -168,6 +173,189 @@ impl HirEqInterExpr<'_, '_, '_> {
&& self.eq_pat(l.pat, r.pat)
},
(StmtKind::Expr(l), StmtKind::Expr(r)) | (StmtKind::Semi(l), StmtKind::Semi(r)) => self.eq_expr(l, r),
(StmtKind::Item(l), StmtKind::Item(r)) => self.eq_item(*l, *r),
_ => false,
}
}
pub fn eq_item(&mut self, l: ItemId, r: ItemId) -> bool {
let left = self.inner.cx.tcx.hir_item(l);
let right = self.inner.cx.tcx.hir_item(r);
let eq = match (left.kind, right.kind) {
(
ItemKind::Const(l_ident, l_generics, l_ty, ConstItemRhs::Body(l_body)),
ItemKind::Const(r_ident, r_generics, r_ty, ConstItemRhs::Body(r_body)),
) => {
l_ident.name == r_ident.name
&& self.eq_generics(l_generics, r_generics)
&& self.eq_ty(l_ty, r_ty)
&& self.eq_body(l_body, r_body)
},
(ItemKind::Static(l_mut, l_ident, l_ty, l_body), ItemKind::Static(r_mut, r_ident, r_ty, r_body)) => {
l_mut == r_mut && l_ident.name == r_ident.name && self.eq_ty(l_ty, r_ty) && self.eq_body(l_body, r_body)
},
(
ItemKind::Fn {
sig: l_sig,
ident: l_ident,
generics: l_generics,
body: l_body,
has_body: l_has_body,
},
ItemKind::Fn {
sig: r_sig,
ident: r_ident,
generics: r_generics,
body: r_body,
has_body: r_has_body,
},
) => {
l_ident.name == r_ident.name
&& (l_has_body == r_has_body)
&& self.eq_fn_sig(&l_sig, &r_sig)
&& self.eq_generics(l_generics, r_generics)
&& self.eq_body(l_body, r_body)
},
(ItemKind::TyAlias(l_ident, l_generics, l_ty), ItemKind::TyAlias(r_ident, r_generics, r_ty)) => {
l_ident.name == r_ident.name && self.eq_generics(l_generics, r_generics) && self.eq_ty(l_ty, r_ty)
},
(ItemKind::Use(l_path, l_kind), ItemKind::Use(r_path, r_kind)) => {
self.eq_path_segments(l_path.segments, r_path.segments)
&& match (l_kind, r_kind) {
(UseKind::Single(l_ident), UseKind::Single(r_ident)) => l_ident.name == r_ident.name,
(UseKind::Glob, UseKind::Glob) | (UseKind::ListStem, UseKind::ListStem) => true,
_ => false,
}
},
(ItemKind::Mod(l_ident, l_mod), ItemKind::Mod(r_ident, r_mod)) => {
l_ident.name == r_ident.name && over(l_mod.item_ids, r_mod.item_ids, |l, r| self.eq_item(*l, *r))
},
_ => false,
};
if eq {
self.local_items.insert(l.owner_id.to_def_id(), r.owner_id.to_def_id());
}
eq
}
fn eq_fn_sig(&mut self, left: &FnSig<'_>, right: &FnSig<'_>) -> bool {
left.header.safety == right.header.safety
&& left.header.constness == right.header.constness
&& left.header.asyncness == right.header.asyncness
&& left.header.abi == right.header.abi
&& self.eq_fn_decl(left.decl, right.decl)
}
fn eq_fn_decl(&mut self, left: &FnDecl<'_>, right: &FnDecl<'_>) -> bool {
over(left.inputs, right.inputs, |l, r| self.eq_ty(l, r))
&& (match (left.output, right.output) {
(FnRetTy::DefaultReturn(_), FnRetTy::DefaultReturn(_)) => true,
(FnRetTy::Return(l_ty), FnRetTy::Return(r_ty)) => self.eq_ty(l_ty, r_ty),
_ => false,
})
&& left.c_variadic == right.c_variadic
&& left.implicit_self == right.implicit_self
&& left.lifetime_elision_allowed == right.lifetime_elision_allowed
}
fn eq_generics(&mut self, left: &Generics<'_>, right: &Generics<'_>) -> bool {
self.eq_generics_param(left.params, right.params)
&& self.eq_generics_predicate(left.predicates, right.predicates)
}
fn eq_generics_predicate(&mut self, left: &[WherePredicate<'_>], right: &[WherePredicate<'_>]) -> bool {
over(left, right, |l, r| match (l.kind, r.kind) {
(WherePredicateKind::BoundPredicate(l_bound), WherePredicateKind::BoundPredicate(r_bound)) => {
l_bound.origin == r_bound.origin
&& self.eq_ty(l_bound.bounded_ty, r_bound.bounded_ty)
&& self.eq_generics_param(l_bound.bound_generic_params, r_bound.bound_generic_params)
&& self.eq_generics_bound(l_bound.bounds, r_bound.bounds)
},
(WherePredicateKind::RegionPredicate(l_region), WherePredicateKind::RegionPredicate(r_region)) => {
Self::eq_lifetime(l_region.lifetime, r_region.lifetime)
&& self.eq_generics_bound(l_region.bounds, r_region.bounds)
},
(WherePredicateKind::EqPredicate(l_eq), WherePredicateKind::EqPredicate(r_eq)) => {
self.eq_ty(l_eq.lhs_ty, r_eq.lhs_ty)
},
_ => false,
})
}
fn eq_generics_bound(&mut self, left: GenericBounds<'_>, right: GenericBounds<'_>) -> bool {
over(left, right, |l, r| match (l, r) {
(GenericBound::Trait(l_trait), GenericBound::Trait(r_trait)) => {
l_trait.modifiers == r_trait.modifiers
&& self.eq_path(l_trait.trait_ref.path, r_trait.trait_ref.path)
&& self.eq_generics_param(l_trait.bound_generic_params, r_trait.bound_generic_params)
},
(GenericBound::Outlives(l_lifetime), GenericBound::Outlives(r_lifetime)) => {
Self::eq_lifetime(l_lifetime, r_lifetime)
},
(GenericBound::Use(l_capture, _), GenericBound::Use(r_capture, _)) => {
over(l_capture, r_capture, |l, r| match (l, r) {
(PreciseCapturingArgKind::Lifetime(l_lifetime), PreciseCapturingArgKind::Lifetime(r_lifetime)) => {
Self::eq_lifetime(l_lifetime, r_lifetime)
},
(PreciseCapturingArgKind::Param(l_param), PreciseCapturingArgKind::Param(r_param)) => {
l_param.ident == r_param.ident && l_param.res == r_param.res
},
_ => false,
})
},
_ => false,
})
}
fn eq_generics_param(&mut self, left: &[GenericParam<'_>], right: &[GenericParam<'_>]) -> bool {
over(left, right, |l, r| {
(match (l.name, r.name) {
(ParamName::Plain(l_ident), ParamName::Plain(r_ident))
| (ParamName::Error(l_ident), ParamName::Error(r_ident)) => l_ident.name == r_ident.name,
(ParamName::Fresh, ParamName::Fresh) => true,
_ => false,
}) && l.pure_wrt_drop == r.pure_wrt_drop
&& self.eq_generics_param_kind(&l.kind, &r.kind)
&& (matches!(
(l.source, r.source),
(GenericParamSource::Generics, GenericParamSource::Generics)
| (GenericParamSource::Binder, GenericParamSource::Binder)
))
})
}
fn eq_generics_param_kind(&mut self, left: &GenericParamKind<'_>, right: &GenericParamKind<'_>) -> bool {
match (left, right) {
(GenericParamKind::Lifetime { kind: l_kind }, GenericParamKind::Lifetime { kind: r_kind }) => {
match (l_kind, r_kind) {
(LifetimeParamKind::Explicit, LifetimeParamKind::Explicit)
| (LifetimeParamKind::Error, LifetimeParamKind::Error) => true,
(LifetimeParamKind::Elided(l_lifetime_kind), LifetimeParamKind::Elided(r_lifetime_kind)) => {
l_lifetime_kind == r_lifetime_kind
},
_ => false,
}
},
(
GenericParamKind::Type {
default: l_default,
synthetic: l_synthetic,
},
GenericParamKind::Type {
default: r_default,
synthetic: r_synthetic,
},
) => both(*l_default, *r_default, |l, r| self.eq_ty(l, r)) && l_synthetic == r_synthetic,
(
GenericParamKind::Const {
ty: l_ty,
default: l_default,
},
GenericParamKind::Const {
ty: r_ty,
default: r_default,
},
) => self.eq_ty(l_ty, r_ty) && both(*l_default, *r_default, |l, r| self.eq_const_arg(l, r)),
_ => false,
}
}
@ -479,16 +667,20 @@ impl HirEqInterExpr<'_, '_, '_> {
(ConstArgKind::Infer(..), ConstArgKind::Infer(..)) => true,
(ConstArgKind::Struct(path_a, inits_a), ConstArgKind::Struct(path_b, inits_b)) => {
self.eq_qpath(path_a, path_b)
&& inits_a.iter().zip(*inits_b).all(|(init_a, init_b)| {
self.eq_const_arg(init_a.expr, init_b.expr)
})
}
&& inits_a
.iter()
.zip(*inits_b)
.all(|(init_a, init_b)| self.eq_const_arg(init_a.expr, init_b.expr))
},
// Use explicit match for now since ConstArg is undergoing flux.
(ConstArgKind::Path(..), _)
| (ConstArgKind::Anon(..), _)
| (ConstArgKind::Infer(..), _)
| (ConstArgKind::Struct(..), _)
| (ConstArgKind::Error(..), _) => false,
(
ConstArgKind::Path(..)
| ConstArgKind::Anon(..)
| ConstArgKind::Infer(..)
| ConstArgKind::Struct(..)
| ConstArgKind::Error(..),
_,
) => false,
}
}
@ -570,6 +762,17 @@ impl HirEqInterExpr<'_, '_, '_> {
match (left.res, right.res) {
(Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
(Res::Local(_), _) | (_, Res::Local(_)) => false,
(Res::Def(l_kind, l), Res::Def(r_kind, r))
if l_kind == r_kind
&& let DefKind::Const
| DefKind::Static { .. }
| DefKind::Fn
| DefKind::TyAlias
| DefKind::Use
| DefKind::Mod = l_kind =>
{
(l == r || self.local_items.get(&l) == Some(&r)) && self.eq_path_segments(left.segments, right.segments)
},
_ => self.eq_path_segments(left.segments, right.segments),
}
}
@ -1344,7 +1547,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
for init in *inits {
self.hash_const_arg(init.expr);
}
}
},
ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {},
}
}

View file

@ -2490,7 +2490,7 @@ pub enum DefinedTy<'tcx> {
/// in the context of its definition site. We also track the `def_id` of its
/// definition site.
///
/// WARNING: As the `ty` in in the scope of the definition, not of the function
/// WARNING: As the `ty` is in the scope of the definition, not of the function
/// using it, you must be very careful with how you use it. Using it in the wrong
/// scope easily results in ICEs.
Mir {
@ -2719,7 +2719,6 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtx
moved_before_use,
same_ctxt,
},
Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow<!>"),
None => ExprUseCtxt {
node: Node::Crate(cx.tcx.hir_root_module()),
child_id: HirId::INVALID,

View file

@ -13,8 +13,8 @@ use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
use rustc_span::source_map::{SourceMap, original_sp};
use rustc_span::{
BytePos, DUMMY_SP, DesugaringKind, Pos, RelativeBytePos, SourceFile, SourceFileAndLine,
Span, SpanData, SyntaxContext, hygiene,
BytePos, DUMMY_SP, DesugaringKind, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData,
SyntaxContext, hygiene,
};
use std::borrow::Cow;
use std::fmt;

View file

@ -58,6 +58,7 @@ generate! {
LowerHex,
MAX,
MIN,
MaybeDef,
MsrvStack,
Octal,
OpenOptions,
@ -167,6 +168,7 @@ generate! {
from_ne_bytes,
from_ptr,
from_raw,
from_raw_parts,
from_str_radix,
fs,
fuse,
@ -192,6 +194,8 @@ generate! {
io,
is_ascii,
is_char_boundary,
is_diag_item,
is_diagnostic_item,
is_digit,
is_empty,
is_err,
@ -275,6 +279,7 @@ generate! {
read_to_string,
read_unaligned,
read_volatile,
reduce,
redundant_imports,
redundant_pub_crate,
regex,
@ -282,6 +287,7 @@ generate! {
repeat,
replace,
replacen,
res,
reserve,
resize,
restriction,
@ -364,6 +370,7 @@ generate! {
trim_start,
trim_start_matches,
truncate,
try_fold,
try_for_each,
unreachable_pub,
unsafe_removed_from_name,

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-12-11"
channel = "nightly-2025-12-25"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -0,0 +1,77 @@
#![feature(rustc_private)]
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_span;
use clippy_utils::res::MaybeDef;
use clippy_utils::sym;
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_middle::ty::{AdtDef, Ty, TyCtxt};
use rustc_span::Symbol;
fn binops(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) {
let did = ty.opt_def_id().unwrap();
let _ = matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
let _ = !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
let _ = matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
let _ = !matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
let _ = matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
let _ = !matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result));
//~^ repeated_is_diagnostic_item
// Don't lint: `is_diagnostic_item` is called not on `TyCtxt`
struct FakeTyCtxt;
impl FakeTyCtxt {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool {
unimplemented!()
}
}
let f = FakeTyCtxt;
let _ = f.is_diagnostic_item(sym::Option, did) || f.is_diagnostic_item(sym::Result, did);
// Don't lint: `is_diagnostic_item` on `TyCtxt` comes from a(n unrelated) trait
trait IsDiagnosticItem {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool;
}
impl IsDiagnosticItem for TyCtxt<'_> {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool {
unimplemented!()
}
}
let _ = IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Option, did)
|| IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Result, did);
// Don't lint: `is_diag_item` is an inherent method
struct DoesntImplMaybeDef;
impl DoesntImplMaybeDef {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool {
unimplemented!()
}
}
let d = DoesntImplMaybeDef;
let _ = d.is_diag_item(cx, sym::Option) || d.is_diag_item(cx, sym::Result);
// Don't lint: `is_diag_item` comes from a trait other than `MaybeDef`
trait FakeMaybeDef {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool;
}
struct Bar;
impl FakeMaybeDef for Bar {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool {
unimplemented!()
}
}
let b = Bar;
let _ = b.is_diag_item(cx, sym::Option) || b.is_diag_item(cx, sym::Result);
}
fn main() {}

View file

@ -0,0 +1,77 @@
#![feature(rustc_private)]
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_span;
use clippy_utils::res::MaybeDef;
use clippy_utils::sym;
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_middle::ty::{AdtDef, Ty, TyCtxt};
use rustc_span::Symbol;
fn binops(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) {
let did = ty.opt_def_id().unwrap();
let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result);
//~^ repeated_is_diagnostic_item
let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result);
//~^ repeated_is_diagnostic_item
let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result);
//~^ repeated_is_diagnostic_item
let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result);
//~^ repeated_is_diagnostic_item
let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did);
//~^ repeated_is_diagnostic_item
let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did);
//~^ repeated_is_diagnostic_item
// Don't lint: `is_diagnostic_item` is called not on `TyCtxt`
struct FakeTyCtxt;
impl FakeTyCtxt {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool {
unimplemented!()
}
}
let f = FakeTyCtxt;
let _ = f.is_diagnostic_item(sym::Option, did) || f.is_diagnostic_item(sym::Result, did);
// Don't lint: `is_diagnostic_item` on `TyCtxt` comes from a(n unrelated) trait
trait IsDiagnosticItem {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool;
}
impl IsDiagnosticItem for TyCtxt<'_> {
fn is_diagnostic_item(&self, sym: Symbol, did: DefId) -> bool {
unimplemented!()
}
}
let _ = IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Option, did)
|| IsDiagnosticItem::is_diagnostic_item(&cx.tcx, sym::Result, did);
// Don't lint: `is_diag_item` is an inherent method
struct DoesntImplMaybeDef;
impl DoesntImplMaybeDef {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool {
unimplemented!()
}
}
let d = DoesntImplMaybeDef;
let _ = d.is_diag_item(cx, sym::Option) || d.is_diag_item(cx, sym::Result);
// Don't lint: `is_diag_item` comes from a trait other than `MaybeDef`
trait FakeMaybeDef {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool;
}
struct Bar;
impl FakeMaybeDef for Bar {
fn is_diag_item(&self, cx: &LateContext, sym: Symbol) -> bool {
unimplemented!()
}
}
let b = Bar;
let _ = b.is_diag_item(cx, sym::Option) || b.is_diag_item(cx, sym::Result);
}
fn main() {}

View file

@ -0,0 +1,82 @@
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:18:13
|
LL | let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
= note: `-D clippy::repeated-is-diagnostic-item` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::repeated_is_diagnostic_item)]`
help: call `Ty::opt_diag_name`, and reuse the results
|
LL - let _ = ty.is_diag_item(cx, sym::Option) || ty.is_diag_item(cx, sym::Result);
LL + let _ = matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result));
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:20:13
|
LL | let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL - let _ = !ty.is_diag_item(cx, sym::Option) && !ty.is_diag_item(cx, sym::Result);
LL + let _ = !matches!(ty.opt_diag_name(cx), Some(sym::Option | sym::Result));
|
error: repeated calls to `AdtDef::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:22:13
|
LL | let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `AdtDef::opt_diag_name`, and reuse the results
|
LL - let _ = adt_def.is_diag_item(cx, sym::Option) || adt_def.is_diag_item(cx, sym::Result);
LL + let _ = matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result));
|
error: repeated calls to `AdtDef::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:24:13
|
LL | let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `AdtDef::opt_diag_name`, and reuse the results
|
LL - let _ = !adt_def.is_diag_item(cx, sym::Option) && !adt_def.is_diag_item(cx, sym::Result);
LL + let _ = !matches!(adt_def.opt_diag_name(cx), Some(sym::Option | sym::Result));
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:26:13
|
LL | let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL - let _ = cx.tcx.is_diagnostic_item(sym::Option, did) || cx.tcx.is_diagnostic_item(sym::Result, did);
LL + let _ = matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result));
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item.rs:28:13
|
LL | let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL - let _ = !cx.tcx.is_diagnostic_item(sym::Option, did) && !cx.tcx.is_diagnostic_item(sym::Result, did);
LL + let _ = !matches!(cx.tcx.get_diagnostic_name(did), Some(sym::Option | sym::Result));
|
error: aborting due to 6 previous errors

View file

@ -0,0 +1,213 @@
//@no-rustfix
#![feature(rustc_private)]
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_span;
use clippy_utils::res::MaybeDef;
use clippy_utils::sym;
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_middle::ty::{AdtDef, Ty, TyCtxt};
use rustc_span::Symbol;
fn main() {}
// if-chains with repeated calls on the same `ty`
fn if_chains(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) {
let did = ty.opt_def_id().unwrap();
let _ = if ty.is_diag_item(cx, sym::Option) {
//~^ repeated_is_diagnostic_item
"Option"
} else if ty.is_diag_item(cx, sym::Result) {
"Result"
} else {
return;
};
// should ideally suggest the following:
// let _ = match ty.opt_diag_name() {
// Some(sym::Option) => {
// "Option"
// }
// Some(sym::Result) => {
// "Result"
// }
// _ => {
// return;
// }
// };
// same but in a stmt
if ty.is_diag_item(cx, sym::Option) {
//~^ repeated_is_diagnostic_item
eprintln!("Option");
} else if ty.is_diag_item(cx, sym::Result) {
eprintln!("Result");
}
// should ideally suggest the following:
// match ty.opt_diag_name() {
// Some(sym::Option) => {
// "Option"
// }
// Some(sym::Result) => {
// "Result"
// }
// _ => {}
// };
// nested conditions
let _ = if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
//~^ repeated_is_diagnostic_item
"Option"
} else if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
"Result"
} else {
return;
};
let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) {
//~^ repeated_is_diagnostic_item
"Option"
} else if cx.tcx.is_diagnostic_item(sym::Result, did) {
"Result"
} else {
return;
};
// should ideally suggest the following:
// let _ = match cx.get_diagnostic_name(did) {
// Some(sym::Option) => {
// "Option"
// }
// Some(sym::Result) => {
// "Result"
// }
// _ => {
// return;
// }
// };
// same but in a stmt
if cx.tcx.is_diagnostic_item(sym::Option, did) {
//~^ repeated_is_diagnostic_item
eprintln!("Option");
} else if cx.tcx.is_diagnostic_item(sym::Result, did) {
eprintln!("Result");
}
// should ideally suggest the following:
// match cx.tcx.get_diagnostic_name(did) {
// Some(sym::Option) => {
// "Option"
// }
// Some(sym::Result) => {
// "Result"
// }
// _ => {}
// };
// nested conditions
let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
//~^ repeated_is_diagnostic_item
"Option"
} else if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
"Result"
} else {
return;
};
}
// if-chains with repeated calls on the same `ty`
fn consecutive_ifs(cx: &LateContext<'_>, ty: Ty<'_>, adt_def: &AdtDef<'_>) {
let did = ty.opt_def_id().unwrap();
{
if ty.is_diag_item(cx, sym::Option) {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if ty.is_diag_item(cx, sym::Result) {
println!("Result");
}
println!("done!")
}
// nested conditions
{
if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
println!("Result");
}
println!("done!")
}
{
if cx.tcx.is_diagnostic_item(sym::Option, did) {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if cx.tcx.is_diagnostic_item(sym::Result, did) {
println!("Result");
}
println!("done!")
}
// nested conditions
{
if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
println!("Result");
}
println!("done!")
}
// All the same, but the second if is the final expression
{
if ty.is_diag_item(cx, sym::Option) {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if ty.is_diag_item(cx, sym::Result) {
println!("Result");
}
}
// nested conditions
{
if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
println!("Result");
}
}
{
if cx.tcx.is_diagnostic_item(sym::Option, did) {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if cx.tcx.is_diagnostic_item(sym::Result, did) {
println!("Result");
}
}
// nested conditions
{
if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
//~^ repeated_is_diagnostic_item
println!("Option");
}
if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
println!("Result");
}
}
}

View file

@ -0,0 +1,374 @@
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:22:13
|
LL | let _ = if ty.is_diag_item(cx, sym::Option) {
| ^ -------------------------------- called here
| _____________|
| |
LL | |
LL | | "Option"
LL | | } else if ty.is_diag_item(cx, sym::Result) {
| | -------------------------------- called here
... |
LL | | return;
LL | | };
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
= note: `-D clippy::repeated-is-diagnostic-item` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::repeated_is_diagnostic_item)]`
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ let _ = if /* name */ == Some(sym::Option) {
LL |
LL | "Option"
LL ~ } else if /* name */ == Some(sym::Result) {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:44:5
|
LL | if ty.is_diag_item(cx, sym::Option) {
| ^ -------------------------------- called here
| _____|
| |
LL | |
LL | | eprintln!("Option");
LL | | } else if ty.is_diag_item(cx, sym::Result) {
| | -------------------------------- called here
LL | | eprintln!("Result");
LL | | }
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | eprintln!("Option");
LL ~ } else if /* name */ == Some(sym::Result) {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:62:13
|
LL | let _ = if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
| ^ -------------------------------- called here
| _____________|
| |
LL | |
LL | | "Option"
LL | | } else if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
| | -------------------------------- called here
... |
LL | | return;
LL | | };
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ let _ = if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | "Option"
LL ~ } else if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:71:13
|
LL | let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) {
| ^ ------------------------------------------- called here
| _____________|
| |
LL | |
LL | | "Option"
LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) {
| | ------------------------------------------- called here
... |
LL | | return;
LL | | };
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ let _ = if /* name */ == Some(sym::Option) {
LL |
LL | "Option"
LL ~ } else if /* name */ == Some(sym::Result) {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:93:5
|
LL | if cx.tcx.is_diagnostic_item(sym::Option, did) {
| ^ ------------------------------------------- called here
| _____|
| |
LL | |
LL | | eprintln!("Option");
LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) {
| | ------------------------------------------- called here
LL | | eprintln!("Result");
LL | | }
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | eprintln!("Option");
LL ~ } else if /* name */ == Some(sym::Result) {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:111:13
|
LL | let _ = if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
| ^ ------------------------------------------- called here
| _____________|
| |
LL | |
LL | | "Option"
LL | | } else if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
| | ------------------------------------------- called here
... |
LL | | return;
LL | | };
| |_____^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ let _ = if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | "Option"
LL ~ } else if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:126:9
|
LL | if ty.is_diag_item(cx, sym::Option) {
| ^ -------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if ty.is_diag_item(cx, sym::Result) {
| | -------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:138:9
|
LL | if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
| ^ -------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
| | -------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:149:9
|
LL | if cx.tcx.is_diagnostic_item(sym::Option, did) {
| ^ ------------------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) {
| | ------------------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:161:9
|
LL | if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
| ^ ------------------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
| | ------------------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:173:9
|
LL | if ty.is_diag_item(cx, sym::Option) {
| ^ -------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if ty.is_diag_item(cx, sym::Result) {
| | -------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) {
|
error: repeated calls to `Ty::is_diag_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:184:9
|
LL | if ty.is_diag_item(cx, sym::Option) && 4 == 5 {
| ^ -------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if ty.is_diag_item(cx, sym::Result) && 4 == 5 {
| | -------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `Ty::opt_diag_name`, and reuse the results
|
LL ~ let /* name */ = ty.opt_diag_name(cx);
LL ~ if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:194:9
|
LL | if cx.tcx.is_diagnostic_item(sym::Option, did) {
| ^ ------------------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) {
| | ------------------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ if /* name */ == Some(sym::Option) {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) {
|
error: repeated calls to `TyCtxt::is_diagnostic_item`
--> tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs:205:9
|
LL | if cx.tcx.is_diagnostic_item(sym::Option, did) && 4 == 5 {
| ^ ------------------------------------------- called here
| _________|
| |
LL | |
LL | | println!("Option");
LL | | }
LL | | if cx.tcx.is_diagnostic_item(sym::Result, did) && 4 == 5 {
| | ------------------------------------------- called here
LL | | println!("Result");
LL | | }
| |_________^
|
= note: each call performs the same compiler query -- it's faster to query once, and reuse the results
help: call `TyCtxt::get_diagnostic_name`, and reuse the results
|
LL ~ let /* name */ = cx.tcx.get_diagnostic_name(did);
LL ~ if /* name */ == Some(sym::Option) && 4 == 5 {
LL |
LL | println!("Option");
LL | }
LL ~ if /* name */ == Some(sym::Result) && 4 == 5 {
|
error: aborting due to 14 previous errors

View file

@ -1,5 +1,5 @@
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
#![warn(clippy::collapsible_if)]
#![warn(clippy::collapsible_else_if)]
#[rustfmt::skip]
fn main() {

View file

@ -1,5 +1,5 @@
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
#![warn(clippy::collapsible_if)]
#![warn(clippy::collapsible_else_if)]
#[rustfmt::skip]
fn main() {

View file

@ -3,7 +3,7 @@
// Should warn
pub fn pub_foo(s: &Vec<u32>, b: &u32, x: &mut u32) {
//~^ ERROR: this argument is a mutable reference, but not used mutably
//~^ needless_pass_by_ref_mut
*x += *b + s.len() as u32;
}

View file

@ -3,7 +3,7 @@
// Should warn
pub fn pub_foo(s: &mut Vec<u32>, b: &u32, x: &mut u32) {
//~^ ERROR: this argument is a mutable reference, but not used mutably
//~^ needless_pass_by_ref_mut
*x += *b + s.len() as u32;
}

View file

@ -1,8 +1,10 @@
error: this argument is a mutable reference, but not used mutably
error: this parameter is a mutable reference but is not used mutably
--> tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs:5:19
|
LL | pub fn pub_foo(s: &mut Vec<u32>, b: &u32, x: &mut u32) {
| ^^^^^^^^^^^^^ help: consider changing to: `&Vec<u32>`
| ^----^^^^^^^^
| |
| help: consider removing this `mut`
|
= warning: changing this function will impact semver compatibility
= note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings`

View file

@ -17,4 +17,6 @@ disallowed-methods = [
# re-exports
"conf_disallowed_methods::identity",
"conf_disallowed_methods::renamed",
# also used in desugaring
"std::future::Future::poll",
]

View file

@ -80,3 +80,19 @@ fn main() {
renamed(1);
//~^ disallowed_methods
}
mod issue16185 {
use std::pin::Pin;
use std::task::Context;
async fn test(f: impl Future<Output = ()>) {
// Should not lint even though desugaring uses
// disallowed method `std::future::Future::poll()`.
f.await
}
fn explicit<F: Future<Output = ()>>(f: Pin<&mut F>, cx: &mut Context<'_>) {
f.poll(cx);
//~^ disallowed_methods
}
}

View file

@ -99,5 +99,11 @@ error: use of a disallowed method `conf_disallowed_methods::renamed`
LL | renamed(1);
| ^^^^^^^
error: aborting due to 16 previous errors
error: use of a disallowed method `std::future::Future::poll`
--> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:95:11
|
LL | f.poll(cx);
| ^^^^
error: aborting due to 17 previous errors

View file

@ -96,3 +96,8 @@ fn _f4() {
assert!(C);
//~^ assertions_on_constants
}
fn issue_16242(var: bool) {
// should not lint
assert!(cfg!(feature = "hey") && var);
}

View file

@ -300,3 +300,65 @@ fn issue15004() {
//~^ branches_sharing_code
};
}
pub fn issue15347<T>() -> isize {
if false {
static A: isize = 4;
return A;
} else {
static A: isize = 5;
return A;
}
if false {
//~^ branches_sharing_code
type ISize = isize;
return ISize::MAX;
} else {
type ISize = isize;
return ISize::MAX;
}
if false {
//~^ branches_sharing_code
fn foo() -> isize {
4
}
return foo();
} else {
fn foo() -> isize {
4
}
return foo();
}
if false {
//~^ branches_sharing_code
use std::num::NonZeroIsize;
return NonZeroIsize::new(4).unwrap().get();
} else {
use std::num::NonZeroIsize;
return NonZeroIsize::new(4).unwrap().get();
}
if false {
//~^ branches_sharing_code
const B: isize = 5;
return B;
} else {
const B: isize = 5;
return B;
}
// Should not lint!
const A: isize = 1;
if false {
const B: isize = A;
return B;
} else {
const C: isize = A;
return C;
}
todo!()
}

View file

@ -202,5 +202,73 @@ LL ~ }
LL ~ 1;
|
error: aborting due to 12 previous errors
error: all if blocks contain the same code at the start
--> tests/ui/branches_sharing_code/shared_at_bottom.rs:313:5
|
LL | / if false {
LL | |
LL | | type ISize = isize;
LL | | return ISize::MAX;
| |__________________________^
|
help: consider moving these statements before the if
|
LL ~ type ISize = isize;
LL + return ISize::MAX;
LL + if false {
|
error: all if blocks contain the same code at the start
--> tests/ui/branches_sharing_code/shared_at_bottom.rs:322:5
|
LL | / if false {
LL | |
LL | | fn foo() -> isize {
LL | | 4
LL | | }
LL | | return foo();
| |_____________________^
|
help: consider moving these statements before the if
|
LL ~ fn foo() -> isize {
LL + 4
LL + }
LL + return foo();
LL + if false {
|
error: all if blocks contain the same code at the start
--> tests/ui/branches_sharing_code/shared_at_bottom.rs:335:5
|
LL | / if false {
LL | |
LL | | use std::num::NonZeroIsize;
LL | | return NonZeroIsize::new(4).unwrap().get();
| |___________________________________________________^
|
help: consider moving these statements before the if
|
LL ~ use std::num::NonZeroIsize;
LL + return NonZeroIsize::new(4).unwrap().get();
LL + if false {
|
error: all if blocks contain the same code at the start
--> tests/ui/branches_sharing_code/shared_at_bottom.rs:344:5
|
LL | / if false {
LL | |
LL | | const B: isize = 5;
LL | | return B;
| |_________________^
|
help: consider moving these statements before the if
|
LL ~ const B: isize = 5;
LL + return B;
LL + if false {
|
error: aborting due to 16 previous errors

View file

@ -1,4 +1,4 @@
error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast.rs:23:5
|
LL | x0 as f32;
@ -7,31 +7,31 @@ LL | x0 as f32;
= note: `-D clippy::cast-precision-loss` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]`
error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `i64` to `f32` may cause a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast.rs:27:5
|
LL | x1 as f32;
| ^^^^^^^^^
error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `i64` to `f64` may cause a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast.rs:30:5
|
LL | x1 as f64;
| ^^^^^^^^^
error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `u32` to `f32` may cause a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast.rs:34:5
|
LL | x2 as f32;
| ^^^^^^^^^
error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `u64` to `f32` may cause a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast.rs:38:5
|
LL | x3 as f32;
| ^^^^^^^^^
error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `u64` to `f64` may cause a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast.rs:41:5
|
LL | x3 as f64;

View file

@ -13,7 +13,7 @@ LL - 1isize as i8;
LL + i8::try_from(1isize);
|
error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `isize` to `f32` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:24:5
|
LL | x0 as f32;
@ -22,19 +22,19 @@ LL | x0 as f32;
= note: `-D clippy::cast-precision-loss` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]`
error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `usize` to `f32` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:26:5
|
LL | x1 as f32;
| ^^^^^^^^^
error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `isize` to `f64` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:28:5
|
LL | x0 as f64;
| ^^^^^^^^^
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:30:5
|
LL | x1 as f64;
@ -165,13 +165,13 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit
LL | 1u32 as isize;
| ^^^^^^^^^^^^^
error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:61:5
|
LL | 999_999_999 as f32;
| ^^^^^^^^^^^^^^^^^^
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:63:5
|
LL | 9_999_999_999_999_999usize as f64;

View file

@ -13,7 +13,7 @@ LL - 1isize as i8;
LL + i8::try_from(1isize);
|
error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `isize` to `f32` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:24:5
|
LL | x0 as f32;
@ -22,19 +22,19 @@ LL | x0 as f32;
= note: `-D clippy::cast-precision-loss` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]`
error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `usize` to `f32` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:26:5
|
LL | x1 as f32;
| ^^^^^^^^^
error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `isize` to `f64` may cause a loss of precision (`isize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:28:5
|
LL | x0 as f64;
| ^^^^^^^^^
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:30:5
|
LL | x1 as f64;
@ -165,13 +165,13 @@ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit
LL | 1u32 as isize;
| ^^^^^^^^^^^^^
error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
error: casting `i32` to `f32` may cause a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> tests/ui/cast_size.rs:61:5
|
LL | 999_999_999 as f32;
| ^^^^^^^^^^^^^^^^^^
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
error: casting `usize` to `f64` may cause a loss of precision (`usize` can be up to 64 bits wide depending on the target architecture, but `f64`'s mantissa is only 52 bits wide)
--> tests/ui/cast_size.rs:63:5
|
LL | 9_999_999_999_999_999usize as f64;

View file

@ -38,3 +38,23 @@ fn issue15010() {
debug_assert!(!f.is_null());
//~^ cmp_null
}
fn issue16281() {
use std::ptr;
struct Container {
value: *const i32,
}
let x = Container { value: ptr::null() };
macro_rules! dot_value {
($obj:expr) => {
$obj.value
};
}
if dot_value!(x).is_null() {
//~^ cmp_null
todo!()
}
}

View file

@ -38,3 +38,23 @@ fn issue15010() {
debug_assert!(f != std::ptr::null_mut());
//~^ cmp_null
}
fn issue16281() {
use std::ptr;
struct Container {
value: *const i32,
}
let x = Container { value: ptr::null() };
macro_rules! dot_value {
($obj:expr) => {
$obj.value
};
}
if dot_value!(x) == ptr::null() {
//~^ cmp_null
todo!()
}
}

View file

@ -37,5 +37,11 @@ error: comparing with null is better expressed by the `.is_null()` method
LL | debug_assert!(f != std::ptr::null_mut());
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()`
error: aborting due to 6 previous errors
error: comparing with null is better expressed by the `.is_null()` method
--> tests/ui/cmp_null.rs:56:8
|
LL | if dot_value!(x) == ptr::null() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dot_value!(x).is_null()`
error: aborting due to 7 previous errors

View file

@ -1,5 +1,5 @@
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)]
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
#![warn(clippy::collapsible_else_if)]
#[rustfmt::skip]
fn main() {
@ -70,6 +70,17 @@ fn main() {
}
//~^^^^^^^^ collapsible_else_if
if x == "hello" {
if y == "world" {
print!("Hello ");
} else {
println!("world");
}
} else if let Some(42) = Some(42) {
println!("42");
}
//~^^^^^ collapsible_else_if
if x == "hello" {
print!("Hello ");
} else {
@ -78,6 +89,21 @@ fn main() {
println!("world!")
}
}
if x == "hello" {
if y == "world" {
print!("Hello ");
} else {
println!("world");
}
} else {
if let Some(42) = Some(42) {
println!("42");
} else {
println!("!");
}
}
}
#[rustfmt::skip]
@ -88,30 +114,12 @@ fn issue_7318() {
}
fn issue_13365() {
// all the `expect`s that we should fulfill
// ensure we fulfill `#[expect]`
if true {
} else {
#[expect(clippy::collapsible_else_if)]
if false {}
}
if true {
} else {
#[expect(clippy::style)]
if false {}
}
if true {
} else {
#[expect(clippy::all)]
if false {}
}
if true {
} else {
#[expect(warnings)]
if false {}
}
}
fn issue14799() {

View file

@ -1,5 +1,5 @@
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)]
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
#![warn(clippy::collapsible_else_if)]
#[rustfmt::skip]
fn main() {
@ -84,6 +84,19 @@ fn main() {
}
//~^^^^^^^^ collapsible_else_if
if x == "hello" {
if y == "world" {
print!("Hello ");
} else {
println!("world");
}
} else {
if let Some(42) = Some(42) {
println!("42");
}
}
//~^^^^^ collapsible_else_if
if x == "hello" {
print!("Hello ");
} else {
@ -92,6 +105,21 @@ fn main() {
println!("world!")
}
}
if x == "hello" {
if y == "world" {
print!("Hello ");
} else {
println!("world");
}
} else {
if let Some(42) = Some(42) {
println!("42");
} else {
println!("!");
}
}
}
#[rustfmt::skip]
@ -104,30 +132,12 @@ fn issue_7318() {
}
fn issue_13365() {
// all the `expect`s that we should fulfill
// ensure we fulfill `#[expect]`
if true {
} else {
#[expect(clippy::collapsible_else_if)]
if false {}
}
if true {
} else {
#[expect(clippy::style)]
if false {}
}
if true {
} else {
#[expect(clippy::all)]
if false {}
}
if true {
} else {
#[expect(warnings)]
if false {}
}
}
fn issue14799() {

View file

@ -142,7 +142,25 @@ LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:100:10
--> tests/ui/collapsible_else_if.rs:93:12
|
LL | } else {
| ____________^
LL | | if let Some(42) = Some(42) {
LL | | println!("42");
LL | | }
LL | | }
| |_____^
|
help: collapse nested if block
|
LL ~ } else if let Some(42) = Some(42) {
LL + println!("42");
LL + }
|
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:128:10
|
LL | }else{
| __________^
@ -151,7 +169,7 @@ LL | | }
| |_____^ help: collapse nested if block: `if false {}`
error: this `else { if .. }` block can be collapsed
--> tests/ui/collapsible_else_if.rs:157:12
--> tests/ui/collapsible_else_if.rs:167:12
|
LL | } else {
| ____________^
@ -159,5 +177,5 @@ LL | | (if y == "world" { println!("world") } else { println!("!") })
LL | | }
| |_____^ help: collapse nested if block: `if y == "world" { println!("world") } else { println!("!") }`
error: aborting due to 9 previous errors
error: aborting due to 10 previous errors

View file

@ -2,7 +2,7 @@ error: empty string literal in `println!`
--> tests/ui/crashes/ice-10148.rs:8:5
|
LL | println!(with_span!(""something ""));
| ^^^^^^^^^^^^^^^^^^^^-----------^^^^^
| ^^^^^^^^^^^^^^^^^^^^---------------^
| |
| help: remove the empty string
|

View file

@ -1,6 +1,6 @@
//@ check-pass
//@compile-flags: -Clink-arg=-nostartfiles
//@ignore-target: apple windows
//@ignore-target: windows
#![crate_type = "lib"]
#![no_std]

View file

@ -1,5 +1,3 @@
//@ignore-target: apple
#![feature(no_core, lang_items)]
#![no_core]
#![allow(clippy::missing_safety_doc)]

View file

@ -1,5 +1,5 @@
error: methods called `as_*` usually take `self` by reference or `self` by mutable reference
--> tests/ui/def_id_nocore.rs:33:19
--> tests/ui/def_id_nocore.rs:31:19
|
LL | pub fn as_ref(self) -> &'static str {
| ^^^^

View file

@ -1,5 +1,6 @@
#![warn(clippy::empty_enum_variants_with_brackets)]
#![allow(dead_code)]
#![feature(more_qualified_paths)]
pub enum PublicTestEnum {
NonEmptyBraces { x: i32, y: i32 }, // No error
@ -102,4 +103,62 @@ pub enum PubFoo {
Variant3(),
}
fn issue16157() {
enum E {
V,
//~^ empty_enum_variants_with_brackets
}
let E::V = E::V;
<E>::V = E::V;
<E>::V = E::V;
}
fn variant_with_braces() {
enum E {
V,
//~^ empty_enum_variants_with_brackets
}
E::V = E::V;
E::V = E::V;
<E>::V = <E>::V;
enum F {
U,
//~^ empty_enum_variants_with_brackets
}
F::U = F::U;
<F>::U = F::U;
}
fn variant_with_comments_and_cfg() {
enum E {
V(
// This is a comment
),
}
E::V() = E::V();
enum F {
U {
// This is a comment
},
}
F::U {} = F::U {};
enum G {
V(#[cfg(target_os = "cuda")] String),
}
G::V() = G::V();
enum H {
U {
#[cfg(target_os = "cuda")]
value: String,
},
}
H::U {} = H::U {};
}
fn main() {}

View file

@ -1,5 +1,6 @@
#![warn(clippy::empty_enum_variants_with_brackets)]
#![allow(dead_code)]
#![feature(more_qualified_paths)]
pub enum PublicTestEnum {
NonEmptyBraces { x: i32, y: i32 }, // No error
@ -102,4 +103,62 @@ pub enum PubFoo {
Variant3(),
}
fn issue16157() {
enum E {
V(),
//~^ empty_enum_variants_with_brackets
}
let E::V() = E::V();
<E>::V() = E::V();
<E>::V {} = E::V();
}
fn variant_with_braces() {
enum E {
V(),
//~^ empty_enum_variants_with_brackets
}
E::V() = E::V();
E::V() = E::V {};
<E>::V {} = <E>::V {};
enum F {
U {},
//~^ empty_enum_variants_with_brackets
}
F::U {} = F::U {};
<F>::U {} = F::U {};
}
fn variant_with_comments_and_cfg() {
enum E {
V(
// This is a comment
),
}
E::V() = E::V();
enum F {
U {
// This is a comment
},
}
F::U {} = F::U {};
enum G {
V(#[cfg(target_os = "cuda")] String),
}
G::V() = G::V();
enum H {
U {
#[cfg(target_os = "cuda")]
value: String,
},
}
H::U {} = H::U {};
}
fn main() {}

Some files were not shown because too many files have changed in this diff Show more