Merge commit '99edcadfd5' into clippy-subtree-update
This commit is contained in:
parent
7a3097f18d
commit
54e9e8cd38
177 changed files with 6336 additions and 967 deletions
2
.github/workflows/clippy_dev.yml
vendored
2
.github/workflows/clippy_dev.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/clippy_pr.yml
vendored
2
.github/workflows/clippy_pr.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.github/workflows/remark.yml
vendored
4
.github/workflows/remark.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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), ¶ms[..]) {
|
||||
(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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(()),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 { "" };
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
/// // ..
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
|||
105
clippy_lints/src/same_length_and_capacity.rs
Normal file
105
clippy_lints/src/same_length_and_capacity.rs
Normal 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(...)))`",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, .. } = ¶m.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.
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
561
clippy_lints_internal/src/repeated_is_diagnostic_item.rs
Normal file
561
clippy_lints_internal/src/repeated_is_diagnostic_item.rs
Normal 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(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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]`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(..) => {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
77
tests/ui-internal/repeated_is_diagnostic_item.fixed
Normal file
77
tests/ui-internal/repeated_is_diagnostic_item.fixed
Normal 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() {}
|
||||
77
tests/ui-internal/repeated_is_diagnostic_item.rs
Normal file
77
tests/ui-internal/repeated_is_diagnostic_item.rs
Normal 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() {}
|
||||
82
tests/ui-internal/repeated_is_diagnostic_item.stderr
Normal file
82
tests/ui-internal/repeated_is_diagnostic_item.stderr
Normal 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
|
||||
|
||||
213
tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs
Normal file
213
tests/ui-internal/repeated_is_diagnostic_item_unfixable.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
374
tests/ui-internal/repeated_is_diagnostic_item_unfixable.stderr
Normal file
374
tests/ui-internal/repeated_is_diagnostic_item_unfixable.stderr
Normal 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
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
|
||||
#![warn(clippy::collapsible_if)]
|
||||
#![warn(clippy::collapsible_else_if)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::eq_op, clippy::nonminimal_bool)]
|
||||
#![warn(clippy::collapsible_if)]
|
||||
#![warn(clippy::collapsible_else_if)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@ disallowed-methods = [
|
|||
# re-exports
|
||||
"conf_disallowed_methods::identity",
|
||||
"conf_disallowed_methods::renamed",
|
||||
# also used in desugaring
|
||||
"std::future::Future::poll",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -96,3 +96,8 @@ fn _f4() {
|
|||
assert!(C);
|
||||
//~^ assertions_on_constants
|
||||
}
|
||||
|
||||
fn issue_16242(var: bool) {
|
||||
// should not lint
|
||||
assert!(cfg!(feature = "hey") && var);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//@ check-pass
|
||||
//@compile-flags: -Clink-arg=-nostartfiles
|
||||
//@ignore-target: apple windows
|
||||
//@ignore-target: windows
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![no_std]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//@ignore-target: apple
|
||||
|
||||
#![feature(no_core, lang_items)]
|
||||
#![no_core]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
| ^^^^
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue