Merge commit '1db89a1b1c' into clippy-subtree-update
This commit is contained in:
commit
a0b18b3318
166 changed files with 3824 additions and 1159 deletions
|
|
@ -20,16 +20,26 @@ jobs:
|
|||
# of the pull request, as malicious code would be able to access the private
|
||||
# GitHub token.
|
||||
steps:
|
||||
- name: Check PR Changes
|
||||
id: pr-changes
|
||||
run: echo "::set-output name=changes::${{ toJson(github.event.pull_request.changed_files) }}"
|
||||
|
||||
- name: Create Comment
|
||||
if: steps.pr-changes.outputs.changes != '[]'
|
||||
run: |
|
||||
# Use GitHub API to create a comment on the PR
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
COMMENT="**Seems that you are trying to add a new lint!**\nWe are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team\n\n@rustbot note Feature-freeze\n@rustbot blocked\n@rustbot label +A-lint\n"
|
||||
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
|
||||
COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments"
|
||||
curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}"
|
||||
- name: Add freeze warning comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
COMMENT=$(echo "**Seems that you are trying to add a new lint!**\n\
|
||||
\n\
|
||||
We are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and [focusing on bugfixes](https://github.com/rust-lang/rust-clippy/issues/15086).\n\
|
||||
\n\
|
||||
Thanks a lot for your contribution, and sorry for the inconvenience.\n\
|
||||
\n\
|
||||
With ❤ from the Clippy team.\n\
|
||||
\n\
|
||||
@rustbot note Feature-freeze\n\
|
||||
@rustbot blocked\n\
|
||||
@rustbot label +A-lint"
|
||||
)
|
||||
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/vnd.github.raw+json" \
|
||||
-X POST \
|
||||
--data "{\"body\":\"${COMMENT}\"}" \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments"
|
||||
|
|
|
|||
2
src/tools/clippy/.gitignore
vendored
2
src/tools/clippy/.gitignore
vendored
|
|
@ -19,8 +19,10 @@ out
|
|||
|
||||
# Generated by Cargo
|
||||
*Cargo.lock
|
||||
!/clippy_test_deps/Cargo.lock
|
||||
/target
|
||||
/clippy_lints/target
|
||||
/clippy_lints_internal/target
|
||||
/clippy_utils/target
|
||||
/clippy_dev/target
|
||||
/lintcheck/target
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::utils::{
|
|||
walk_dir_no_dot_or_target,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_lexer::{TokenKind, tokenize};
|
||||
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::io::{self, Read};
|
||||
|
|
@ -92,7 +92,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
|
|||
let mut fields = Vec::new();
|
||||
let mut state = State::Start;
|
||||
|
||||
for (i, t) in tokenize(conf)
|
||||
for (i, t) in tokenize(conf, FrontmatterAllowed::No)
|
||||
.map(|x| {
|
||||
let start = pos;
|
||||
pos += x.len;
|
||||
|
|
|
|||
|
|
@ -92,9 +92,11 @@ impl LateLintPass<'_> for ApproxConstant {
|
|||
impl ApproxConstant {
|
||||
fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol, module: &str) {
|
||||
let s = s.as_str();
|
||||
if s.parse::<f64>().is_ok() {
|
||||
if let Ok(maybe_constant) = s.parse::<f64>() {
|
||||
for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
|
||||
if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) {
|
||||
if is_approx_const(constant, s, maybe_constant, min_digits)
|
||||
&& msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv))
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
APPROX_CONSTANT,
|
||||
|
|
@ -112,18 +114,35 @@ impl ApproxConstant {
|
|||
|
||||
impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
|
||||
|
||||
fn count_digits_after_dot(input: &str) -> usize {
|
||||
input
|
||||
.char_indices()
|
||||
.find(|(_, ch)| *ch == '.')
|
||||
.map_or(0, |(i, _)| input.len() - i - 1)
|
||||
}
|
||||
|
||||
/// Returns `false` if the number of significant figures in `value` are
|
||||
/// less than `min_digits`; otherwise, returns true if `value` is equal
|
||||
/// to `constant`, rounded to the number of digits present in `value`.
|
||||
/// to `constant`, rounded to the number of significant digits present in `value`.
|
||||
#[must_use]
|
||||
fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
|
||||
fn is_approx_const(constant: f64, value: &str, f_value: f64, min_digits: usize) -> bool {
|
||||
if value.len() <= min_digits {
|
||||
// The value is not precise enough
|
||||
false
|
||||
} else if constant.to_string().starts_with(value) {
|
||||
// The value is a truncated constant
|
||||
} else if f_value.to_string().len() > min_digits && constant.to_string().starts_with(&f_value.to_string()) {
|
||||
// The value represents the same value
|
||||
true
|
||||
} else {
|
||||
let round_const = format!("{constant:.*}", value.len() - 2);
|
||||
// The value is a truncated constant
|
||||
|
||||
// Print constant with numeric formatting (`0`), with the length of `value` as minimum width
|
||||
// (`value_len$`), and with the same precision as `value` (`.value_prec$`).
|
||||
// See https://doc.rust-lang.org/std/fmt/index.html.
|
||||
let round_const = format!(
|
||||
"{constant:0value_len$.value_prec$}",
|
||||
value_len = value.len(),
|
||||
value_prec = count_digits_after_dot(value)
|
||||
);
|
||||
value == round_const
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ use clippy_utils::diagnostics::span_lint_and_note;
|
|||
use clippy_utils::is_cfg_test;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_hir::{
|
||||
Attribute, FieldDef, HirId, IsAuto, ImplItemId, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind,
|
||||
Variant, VariantData,
|
||||
Attribute, FieldDef, HirId, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant,
|
||||
VariantData,
|
||||
};
|
||||
use rustc_middle::ty::AssocKind;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::AssocKind;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Ident;
|
||||
|
||||
|
|
@ -469,13 +469,14 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
/// This is implemented here because `rustc_hir` is not a dependency of
|
||||
/// `clippy_config`.
|
||||
fn convert_assoc_item_kind(cx: &LateContext<'_>, owner_id: OwnerId) -> SourceItemOrderingTraitAssocItemKind {
|
||||
let kind = cx.tcx.associated_item(owner_id.def_id).kind;
|
||||
|
||||
#[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
|
||||
use SourceItemOrderingTraitAssocItemKind::*;
|
||||
|
||||
let kind = cx.tcx.associated_item(owner_id.def_id).kind;
|
||||
|
||||
match kind {
|
||||
AssocKind::Const{..} => Const,
|
||||
AssocKind::Type {..}=> Type,
|
||||
AssocKind::Const { .. } => Const,
|
||||
AssocKind::Type { .. } => Type,
|
||||
AssocKind::Fn { .. } => Fn,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync {
|
|||
diag.note(format!(
|
||||
"`Arc<{arg_ty}>` is not `Send` and `Sync` as `{arg_ty}` is {reason}"
|
||||
));
|
||||
diag.help("if the `Arc` will not used be across threads replace it with an `Rc`");
|
||||
diag.help("if the `Arc` will not be used across threads replace it with an `Rc`");
|
||||
diag.help(format!(
|
||||
"otherwise make `{arg_ty}` `Send` and `Sync` or consider a wrapper type such as `Mutex`"
|
||||
));
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
| sym::unused_braces
|
||||
| sym::unused_import_braces
|
||||
| sym::unused_imports
|
||||
| sym::redundant_imports
|
||||
)
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -59,9 +59,8 @@ fn get_const_name_and_ty_name(
|
|||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
// We allow casts from any function type to any function type.
|
||||
match cast_to.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => return,
|
||||
_ => { /* continue to checks */ },
|
||||
if cast_to.is_fn() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ty::FnDef(def_id, generics) = cast_from.kind()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
use super::{FN_TO_NUMERIC_CAST, utils};
|
||||
|
||||
|
|
@ -13,23 +13,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
|
|||
return;
|
||||
};
|
||||
|
||||
match cast_from.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
if cast_from.is_fn() {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
|
||||
if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() {
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FN_TO_NUMERIC_CAST,
|
||||
expr.span,
|
||||
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
|
||||
"try",
|
||||
format!("{from_snippet} as usize"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() {
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FN_TO_NUMERIC_CAST,
|
||||
expr.span,
|
||||
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
|
||||
"try",
|
||||
format!("{from_snippet} as usize"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@ use clippy_utils::source::snippet_with_applicability;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
use super::FN_TO_NUMERIC_CAST_ANY;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
// We allow casts from any function type to any function type.
|
||||
match cast_to.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => return,
|
||||
_ => { /* continue to checks */ },
|
||||
if cast_to.is_fn() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ty::FnDef(..) | ty::FnPtr(..) = cast_from.kind() {
|
||||
if cast_from.is_fn() {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils};
|
||||
|
||||
|
|
@ -12,23 +12,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
|
|||
let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else {
|
||||
return;
|
||||
};
|
||||
match cast_from.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
|
||||
if cast_from.is_fn() {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
|
||||
|
||||
if to_nbits < cx.tcx.data_layout.pointer_size().bits() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
|
||||
expr.span,
|
||||
format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"),
|
||||
"try",
|
||||
format!("{from_snippet} as usize"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
if to_nbits < cx.tcx.data_layout.pointer_size().bits() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
|
||||
expr.span,
|
||||
format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"),
|
||||
"try",
|
||||
format!("{from_snippet} as usize"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ use clippy_utils::source::snippet_with_applicability;
|
|||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind};
|
||||
use rustc_hir_pretty::qpath_to_string;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::PTR_AS_PTR;
|
||||
|
||||
|
|
@ -74,7 +73,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
|
|||
|
||||
let (help, final_suggestion) = if let Some(method) = omit_cast.corresponding_item() {
|
||||
// don't force absolute path
|
||||
let method = qpath_to_string(&cx.tcx, method);
|
||||
let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app);
|
||||
("try call directly", format!("{method}{turbofish}()"))
|
||||
} else {
|
||||
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
|
||||
|
|
@ -96,3 +95,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn qpath_span_without_turbofish(qpath: &QPath<'_>) -> Span {
|
||||
if let QPath::Resolved(_, path) = qpath
|
||||
&& let [.., last_ident] = path.segments
|
||||
&& last_ident.args.is_some()
|
||||
{
|
||||
return qpath.span().shrink_to_lo().to(last_ident.ident.span);
|
||||
}
|
||||
|
||||
qpath.span()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ impl CognitiveComplexity {
|
|||
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
|
||||
FnKind::Closure => {
|
||||
let header_span = body_span.with_hi(decl.output.span().lo());
|
||||
#[expect(clippy::range_plus_one)]
|
||||
if let Some(range) = header_span.map_range(cx, |_, src, range| {
|
||||
let mut idxs = src.get(range.clone())?.match_indices('|');
|
||||
Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::higher::has_let_expr;
|
||||
use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet};
|
||||
use clippy_utils::ty::{InteriorMut, needs_ordered_drop};
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
|
|
@ -11,7 +12,7 @@ use clippy_utils::{
|
|||
use core::iter;
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit};
|
||||
use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -189,24 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the given expression is a let chain.
|
||||
fn contains_let(e: &Expr<'_>) -> bool {
|
||||
match e.kind {
|
||||
ExprKind::Let(..) => true,
|
||||
ExprKind::Binary(op, lhs, rhs) if op.node == BinOpKind::And => {
|
||||
matches!(lhs.kind, ExprKind::Let(..)) || contains_let(rhs)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool {
|
||||
let mut eq = SpanlessEq::new(cx);
|
||||
blocks
|
||||
.array_windows::<2>()
|
||||
.enumerate()
|
||||
.fold(true, |all_eq, (i, &[lhs, rhs])| {
|
||||
if eq.eq_block(lhs, rhs) && !contains_let(conds[i]) && conds.get(i + 1).is_none_or(|e| !contains_let(e)) {
|
||||
if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IF_SAME_THEN_ELSE,
|
||||
|
|
|
|||
|
|
@ -432,6 +432,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
|||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::Block(block, _) = expr.kind
|
||||
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
&& block
|
||||
.span
|
||||
.source_callee()
|
||||
.and_then(|expr| expr.macro_def_id)
|
||||
.is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did))
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ pub struct DisallowedMacros {
|
|||
|
||||
// When a macro is disallowed in an early pass, it's stored
|
||||
// and emitted during the late pass. This happens for attributes.
|
||||
earlies: AttrStorage,
|
||||
early_macro_cache: AttrStorage,
|
||||
}
|
||||
|
||||
impl DisallowedMacros {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
|
||||
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, early_macro_cache: AttrStorage) -> Self {
|
||||
let (disallowed, _) = create_disallowed_map(
|
||||
tcx,
|
||||
&conf.disallowed_macros,
|
||||
|
|
@ -89,7 +89,7 @@ impl DisallowedMacros {
|
|||
disallowed,
|
||||
seen: FxHashSet::default(),
|
||||
derive_src: None,
|
||||
earlies,
|
||||
early_macro_cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]);
|
|||
impl LateLintPass<'_> for DisallowedMacros {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
// once we check a crate in the late pass we can emit the early pass lints
|
||||
if let Some(attr_spans) = self.earlies.clone().0.get() {
|
||||
if let Some(attr_spans) = self.early_macro_cache.clone().0.get() {
|
||||
for span in attr_spans {
|
||||
self.check(cx, *span, None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1232,7 +1232,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
headers
|
||||
}
|
||||
|
||||
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
|
||||
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
|
||||
if range.end < range.start {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA
|
|||
|
||||
impl LateLintPass<'_> for EmptyWithBrackets {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// FIXME: handle `struct $name {}`
|
||||
if let ItemKind::Struct(ident, _, var_data) = &item.kind
|
||||
&& !item.span.from_expansion()
|
||||
&& !ident.span.from_expansion()
|
||||
&& has_brackets(var_data)
|
||||
&& let span_after_ident = item.span.with_lo(ident.span.hi())
|
||||
&& has_no_fields(cx, var_data, span_after_ident)
|
||||
|
|
@ -116,10 +118,12 @@ impl LateLintPass<'_> for EmptyWithBrackets {
|
|||
}
|
||||
|
||||
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
|
||||
// the span of the parentheses/braces
|
||||
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
|
||||
|
||||
if has_no_fields(cx, &variant.data, span_after_ident) {
|
||||
// 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())
|
||||
&& has_no_fields(cx, &variant.data, span_after_ident)
|
||||
{
|
||||
match variant.data {
|
||||
VariantData::Struct { .. } => {
|
||||
// Empty struct variants can be linted immediately
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_hir;
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit};
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit};
|
||||
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
|
|
@ -87,16 +87,14 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
|
|||
let mut trait_self_ty = None;
|
||||
match cx.tcx.def_kind(parent_id) {
|
||||
// If the method is an impl for a trait, don't warn.
|
||||
DefKind::Impl { of_trait: true } => {
|
||||
return
|
||||
}
|
||||
DefKind::Impl { of_trait: true } => return,
|
||||
|
||||
// find `self` ty for this trait if relevant
|
||||
DefKind::Trait => {
|
||||
trait_self_ty = Some(TraitRef::identity(cx.tcx, parent_id.to_def_id()).self_ty());
|
||||
}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
let mut v = EscapeDelegate {
|
||||
|
|
|
|||
|
|
@ -29,12 +29,6 @@ declare_clippy_lint! {
|
|||
/// Needlessly creating a closure adds code for no benefit
|
||||
/// and gives the optimizer more work.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// If creating the closure inside the closure has a side-
|
||||
/// effect then moving the closure creation out will change when that side-
|
||||
/// effect runs.
|
||||
/// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// xs.map(|x| foo(x))
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
|
|||
}
|
||||
|
||||
fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Span) {
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::Expr;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
|
||||
struct FindPanicUnwrap<'a, 'tcx> {
|
||||
lcx: &'a LateContext<'tcx>,
|
||||
|
|
@ -96,10 +96,12 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp
|
|||
}
|
||||
}
|
||||
|
||||
for impl_item in cx.tcx.associated_items(item_def_id)
|
||||
for impl_item in cx
|
||||
.tcx
|
||||
.associated_items(item_def_id)
|
||||
.filter_by_name_unhygienic_and_kind(sym::from, ty::AssocTag::Fn)
|
||||
{
|
||||
let impl_item_def_id= impl_item.def_id.expect_local();
|
||||
let impl_item_def_id = impl_item.def_id.expect_local();
|
||||
|
||||
// check the body for `begin_panic` or `unwrap`
|
||||
let body = cx.tcx.hir_body_owned_by(impl_item_def_id);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use rustc_ast::{
|
|||
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
|
||||
FormatPlaceholder, FormatTrait,
|
||||
};
|
||||
use rustc_attr_data_structures::RustcVersion;
|
||||
use rustc_attr_data_structures::{AttributeKind, RustcVersion, find_attr};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
|
||||
|
|
@ -30,7 +30,6 @@ use rustc_span::edition::Edition::Edition2021;
|
|||
use rustc_span::{Span, Symbol, sym};
|
||||
use rustc_trait_selection::infer::TyCtxtInferExt;
|
||||
use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -129,6 +128,7 @@ declare_clippy_lint! {
|
|||
/// # let width = 1;
|
||||
/// # let prec = 2;
|
||||
/// format!("{}", var);
|
||||
/// format!("{:?}", var);
|
||||
/// format!("{v:?}", v = var);
|
||||
/// format!("{0} {0}", var);
|
||||
/// format!("{0:1$}", var, width);
|
||||
|
|
@ -141,6 +141,7 @@ declare_clippy_lint! {
|
|||
/// # let prec = 2;
|
||||
/// format!("{var}");
|
||||
/// format!("{var:?}");
|
||||
/// format!("{var:?}");
|
||||
/// format!("{var} {var}");
|
||||
/// format!("{var:width$}");
|
||||
/// format!("{var:.prec$}");
|
||||
|
|
@ -164,7 +165,7 @@ declare_clippy_lint! {
|
|||
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
|
||||
#[clippy::version = "1.66.0"]
|
||||
pub UNINLINED_FORMAT_ARGS,
|
||||
style,
|
||||
pedantic,
|
||||
"using non-inlined variables in `format!` calls"
|
||||
}
|
||||
|
||||
|
|
@ -657,7 +658,10 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
|
|||
};
|
||||
let selection = SelectionContext::new(&infcx).select(&obligation);
|
||||
let derived = if let Ok(Some(Selection::UserDefined(data))) = selection {
|
||||
find_attr!(tcx.get_all_attrs(data.impl_def_id), AttributeKind::AutomaticallyDerived(..))
|
||||
find_attr!(
|
||||
tcx.get_all_attrs(data.impl_def_id),
|
||||
AttributeKind::AutomaticallyDerived(..)
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use clippy_utils::source::SpanRangeExt;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{Visitor, walk_path};
|
||||
use rustc_hir::{
|
||||
FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemId, Item, ItemKind, PatKind, Path,
|
||||
FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemId, ImplItemKind, Item, ItemKind, PatKind, Path,
|
||||
PathSegment, Ty, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use rustc_span::{Span, sym};
|
|||
|
||||
use clippy_utils::attrs::is_proc_macro;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::source::snippet_indent;
|
||||
use clippy_utils::ty::is_must_use_ty;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{return_ty, trait_ref_of_method};
|
||||
|
|
@ -28,6 +28,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
|
|||
if let hir::ItemKind::Fn {
|
||||
ref sig,
|
||||
body: ref body_id,
|
||||
ident,
|
||||
..
|
||||
} = item.kind
|
||||
{
|
||||
|
|
@ -51,8 +52,8 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
|
|||
sig.decl,
|
||||
cx.tcx.hir_body(*body_id),
|
||||
item.span,
|
||||
ident.span,
|
||||
item.owner_id,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this function could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
|
|
@ -84,8 +85,8 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
|
|||
sig.decl,
|
||||
cx.tcx.hir_body(*body_id),
|
||||
item.span,
|
||||
item.ident.span,
|
||||
item.owner_id,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
|
|
@ -120,8 +121,8 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
|
|||
sig.decl,
|
||||
body,
|
||||
item.span,
|
||||
item.ident.span,
|
||||
item.owner_id,
|
||||
item.span.with_hi(sig.decl.output.span().hi()),
|
||||
"this method could have a `#[must_use]` attribute",
|
||||
);
|
||||
}
|
||||
|
|
@ -198,8 +199,8 @@ fn check_must_use_candidate<'tcx>(
|
|||
decl: &'tcx hir::FnDecl<'_>,
|
||||
body: &'tcx hir::Body<'_>,
|
||||
item_span: Span,
|
||||
ident_span: Span,
|
||||
item_id: hir::OwnerId,
|
||||
fn_span: Span,
|
||||
msg: &'static str,
|
||||
) {
|
||||
if has_mutable_arg(cx, body)
|
||||
|
|
@ -208,18 +209,18 @@ fn check_must_use_candidate<'tcx>(
|
|||
|| returns_unit(decl)
|
||||
|| !cx.effective_visibilities.is_exported(item_id.def_id)
|
||||
|| is_must_use_ty(cx, return_ty(cx, item_id))
|
||||
|| item_span.from_expansion()
|
||||
{
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
|
||||
if let Some(snippet) = fn_span.get_source_text(cx) {
|
||||
diag.span_suggestion(
|
||||
fn_span,
|
||||
"add the attribute",
|
||||
format!("#[must_use] {snippet}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
span_lint_and_then(cx, MUST_USE_CANDIDATE, ident_span, msg, |diag| {
|
||||
let indent = snippet_indent(cx, item_span).unwrap_or_default();
|
||||
diag.span_suggestion(
|
||||
item_span.shrink_to_lo(),
|
||||
"add the attribute",
|
||||
format!("#[must_use] \n{indent}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use clippy_utils::msrvs::{self, Msrv};
|
|||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks,
|
||||
contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor,
|
||||
path_res, peel_blocks,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
|
|
@ -92,6 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
|
|||
expr.span,
|
||||
format!("this could be simplified with `bool::{method_name}`"),
|
||||
|diag| {
|
||||
if expr_adjustment_requires_coercion(cx, then_arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
|
||||
.maybe_paren()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_in_test;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::{is_in_const_context, is_in_test};
|
||||
use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind, HirId, QPath};
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -33,15 +34,54 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// To fix this problem, either increase your MSRV or use another item
|
||||
/// available in your current MSRV.
|
||||
///
|
||||
/// You can also locally change the MSRV that should be checked by Clippy,
|
||||
/// for example if a feature in your crate (e.g., `modern_compiler`) should
|
||||
/// allow you to use an item:
|
||||
///
|
||||
/// ```no_run
|
||||
/// //! This crate has a MSRV of 1.3.0, but we also have an optional feature
|
||||
/// //! `sleep_well` which requires at least Rust 1.4.0.
|
||||
///
|
||||
/// // When the `sleep_well` feature is set, do not warn for functions available
|
||||
/// // in Rust 1.4.0 and below.
|
||||
/// #![cfg_attr(feature = "sleep_well", clippy::msrv = "1.4.0")]
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// #[cfg(feature = "sleep_well")]
|
||||
/// fn sleep_for_some_time() {
|
||||
/// std::thread::sleep(Duration::new(1, 0)); // Will not trigger the lint
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You can also increase the MSRV in tests, by using:
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Use a much higher MSRV for tests while keeping the main one low
|
||||
/// #![cfg_attr(test, clippy::msrv = "1.85.0")]
|
||||
///
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// // The tests can use items introduced in Rust 1.85.0 and lower
|
||||
/// // without triggering the `incompatible_msrv` lint.
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub INCOMPATIBLE_MSRV,
|
||||
suspicious,
|
||||
"ensures that all items used in the crate are available for the current MSRV"
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Availability {
|
||||
FeatureEnabled,
|
||||
Since(RustcVersion),
|
||||
}
|
||||
|
||||
pub struct IncompatibleMsrv {
|
||||
msrv: Msrv,
|
||||
is_above_msrv: FxHashMap<DefId, RustcVersion>,
|
||||
availability_cache: FxHashMap<(DefId, bool), Availability>,
|
||||
check_in_tests: bool,
|
||||
}
|
||||
|
||||
|
|
@ -51,38 +91,50 @@ impl IncompatibleMsrv {
|
|||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
msrv: conf.msrv,
|
||||
is_above_msrv: FxHashMap::default(),
|
||||
availability_cache: FxHashMap::default(),
|
||||
check_in_tests: conf.check_incompatible_msrv_in_tests,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
|
||||
if let Some(version) = self.is_above_msrv.get(&def_id) {
|
||||
return *version;
|
||||
/// Returns the availability of `def_id`, whether it is enabled through a feature or
|
||||
/// available since a given version (the default being Rust 1.0.0). `needs_const` requires
|
||||
/// the `const`-stability to be looked up instead of the stability in non-`const` contexts.
|
||||
fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId, needs_const: bool) -> Availability {
|
||||
if let Some(availability) = self.availability_cache.get(&(def_id, needs_const)) {
|
||||
return *availability;
|
||||
}
|
||||
let version = if let Some(version) = tcx
|
||||
.lookup_stability(def_id)
|
||||
.and_then(|stability| match stability.level {
|
||||
StabilityLevel::Stable {
|
||||
since: StableSince::Version(version),
|
||||
..
|
||||
} => Some(version),
|
||||
_ => None,
|
||||
}) {
|
||||
version
|
||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
|
||||
self.get_def_id_version(tcx, parent_def_id)
|
||||
let (feature, stability_level) = if needs_const {
|
||||
tcx.lookup_const_stability(def_id)
|
||||
.map(|stability| (stability.feature, stability.level))
|
||||
.unzip()
|
||||
} else {
|
||||
RustcVersion {
|
||||
tcx.lookup_stability(def_id)
|
||||
.map(|stability| (stability.feature, stability.level))
|
||||
.unzip()
|
||||
};
|
||||
let version = if feature.is_some_and(|feature| tcx.features().enabled(feature)) {
|
||||
Availability::FeatureEnabled
|
||||
} else if let Some(StableSince::Version(version)) =
|
||||
stability_level.as_ref().and_then(StabilityLevel::stable_since)
|
||||
{
|
||||
Availability::Since(version)
|
||||
} else if needs_const {
|
||||
// Fallback to regular stability
|
||||
self.get_def_id_availability(tcx, def_id, false)
|
||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
|
||||
self.get_def_id_availability(tcx, parent_def_id, needs_const)
|
||||
} else {
|
||||
Availability::Since(RustcVersion {
|
||||
major: 1,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
}
|
||||
})
|
||||
};
|
||||
self.is_above_msrv.insert(def_id, version);
|
||||
self.availability_cache.insert((def_id, needs_const), version);
|
||||
version
|
||||
}
|
||||
|
||||
/// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV.
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
|
|
@ -108,18 +160,28 @@ impl IncompatibleMsrv {
|
|||
return;
|
||||
}
|
||||
|
||||
let needs_const = cx.enclosing_body.is_some()
|
||||
&& is_in_const_context(cx)
|
||||
&& matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn);
|
||||
|
||||
if (self.check_in_tests || !is_in_test(cx.tcx, node))
|
||||
&& let Some(current) = self.msrv.current(cx)
|
||||
&& let version = self.get_def_id_version(cx.tcx, def_id)
|
||||
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const)
|
||||
&& version > current
|
||||
{
|
||||
span_lint(
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
INCOMPATIBLE_MSRV,
|
||||
span,
|
||||
format!(
|
||||
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`"
|
||||
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable{} since `{version}`",
|
||||
if needs_const { " in a `const` context" } else { "" },
|
||||
),
|
||||
|diag| {
|
||||
if is_under_cfg_attribute(cx, node) {
|
||||
diag.note_once("you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -133,17 +195,38 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
|||
self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, _) => {
|
||||
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should
|
||||
// not be linted as they will not be generated in older compilers if the function is not available,
|
||||
// and the compiler is allowed to call unstable functions.
|
||||
if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = call.kind
|
||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span);
|
||||
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should
|
||||
// not be linted as they will not be generated in older compilers if the function is not available,
|
||||
// and the compiler is allowed to call unstable functions.
|
||||
ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) => {
|
||||
if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() {
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ty(&mut self, cx: &LateContext<'tcx>, hir_ty: &'tcx hir::Ty<'tcx, AmbigArg>) {
|
||||
if let hir::TyKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = hir_ty.kind
|
||||
&& let Some(ty_def_id) = cx.qpath_res(&qpath, hir_ty.hir_id).opt_def_id()
|
||||
// `CStr` and `CString` have been moved around but have been available since Rust 1.0.0
|
||||
&& !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type))
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Heuristic checking if the node `hir_id` is under a `#[cfg()]` or `#[cfg_attr()]`
|
||||
/// attribute.
|
||||
fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool {
|
||||
cx.tcx.hir_parent_id_iter(hir_id).any(|id| {
|
||||
cx.tcx.hir_attrs(id).iter().any(|attr| {
|
||||
matches!(
|
||||
attr.ident().map(|ident| ident.name),
|
||||
Some(sym::cfg_trace | sym::cfg_attr_trace)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use crate::methods::method_call;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{peel_blocks, sym};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{peel_blocks, peel_hir_expr_while, sym};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -43,53 +42,58 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(IneffectiveOpenOptions => [INEFFECTIVE_OPEN_OPTIONS]);
|
||||
|
||||
fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option<Span> {
|
||||
if let [arg] = args
|
||||
&& let ExprKind::Lit(lit) = peel_blocks(arg).kind
|
||||
&& lit.node == LitKind::Bool(true)
|
||||
{
|
||||
// The `.` is not included in the span so we cheat a little bit to include it as well.
|
||||
Some(call_span.with_lo(call_span.lo() - BytePos(1)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else {
|
||||
return;
|
||||
};
|
||||
let receiver_ty = cx.typeck_results().expr_ty(receiver);
|
||||
match receiver_ty.peel_refs().kind() {
|
||||
ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::FsOpenOptions, adt.did()) => {},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
let mut append = None;
|
||||
let mut write = None;
|
||||
|
||||
while let Some((name, recv, args, _, span)) = method_call(receiver) {
|
||||
if name == sym::append {
|
||||
append = index_if_arg_is_boolean(args, span);
|
||||
} else if name == sym::write {
|
||||
write = index_if_arg_is_boolean(args, span);
|
||||
}
|
||||
receiver = recv;
|
||||
}
|
||||
|
||||
if let Some(write_span) = write
|
||||
&& append.is_some()
|
||||
if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind
|
||||
&& name.ident.name == sym::open
|
||||
&& !expr.span.from_expansion()
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INEFFECTIVE_OPEN_OPTIONS,
|
||||
write_span,
|
||||
"unnecessary use of `.write(true)` because there is `.append(true)`",
|
||||
"remove `.write(true)`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
let mut append = false;
|
||||
let mut write = None;
|
||||
peel_hir_expr_while(recv, |e| {
|
||||
if let ExprKind::MethodCall(name, recv, args, call_span) = e.kind
|
||||
&& !e.span.from_expansion()
|
||||
{
|
||||
if let [arg] = args
|
||||
&& let ExprKind::Lit(lit) = peel_blocks(arg).kind
|
||||
&& matches!(lit.node, LitKind::Bool(true))
|
||||
&& !arg.span.from_expansion()
|
||||
&& !lit.span.from_expansion()
|
||||
{
|
||||
match name.ident.name {
|
||||
sym::append => append = true,
|
||||
sym::write
|
||||
if let Some(range) = call_span.map_range(cx, |_, text, range| {
|
||||
if text.get(..range.start)?.ends_with('.') {
|
||||
Some(range.start - 1..range.end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) =>
|
||||
{
|
||||
write = Some(call_span.with_lo(range.start));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Some(recv)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if append && let Some(write_span) = write {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INEFFECTIVE_OPEN_OPTIONS,
|
||||
write_span,
|
||||
"unnecessary use of `.write(true)` because there is `.append(true)`",
|
||||
"remove `.write(true)`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,13 +52,17 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom {
|
|||
if !cx.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id) {
|
||||
return;
|
||||
}
|
||||
for ii in cx.tcx.associated_items(item.owner_id.def_id)
|
||||
for ii in cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id.def_id)
|
||||
.filter_by_name_unhygienic_and_kind(sym::Error, AssocTag::Type)
|
||||
{
|
||||
let ii_ty = cx.tcx.type_of(ii.def_id).instantiate_identity();
|
||||
if !ii_ty.is_inhabited_from(cx.tcx, ii.def_id, cx.typing_env()) {
|
||||
let mut span = MultiSpan::from_span(cx.tcx.def_span(item.owner_id.to_def_id()));
|
||||
let ii_ty_span = cx.tcx.hir_node_by_def_id(ii.def_id.expect_local())
|
||||
let ii_ty_span = cx
|
||||
.tcx
|
||||
.hir_node_by_def_id(ii.def_id.expect_local())
|
||||
.expect_impl_item()
|
||||
.expect_type()
|
||||
.span;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use rustc_data_structures::fx::FxHashSet;
|
|||
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::MacroKind;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -502,7 +503,8 @@ impl LateLintPass<'_> for ItemNameRepetitions {
|
|||
);
|
||||
}
|
||||
|
||||
if both_are_public && item_camel.len() > mod_camel.len() {
|
||||
let is_macro_rule = matches!(item.kind, ItemKind::Macro(_, _, MacroKind::Bang));
|
||||
if both_are_public && item_camel.len() > mod_camel.len() && !is_macro_rule {
|
||||
let matching = count_match_start(mod_camel, &item_camel);
|
||||
let rmatching = count_match_end(mod_camel, &item_camel);
|
||||
let nchars = mod_camel.chars().count();
|
||||
|
|
|
|||
|
|
@ -139,11 +139,17 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
|
|||
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
|
||||
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some()
|
||||
})
|
||||
&& let Some(iter_assoc_span) = cx.tcx.associated_items(item.owner_id)
|
||||
&& let Some(iter_assoc_span) = cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id)
|
||||
.filter_by_name_unhygienic_and_kind(sym::IntoIter, ty::AssocTag::Type)
|
||||
.next()
|
||||
.map(|assoc_item| {
|
||||
cx.tcx.hir_node_by_def_id(assoc_item.def_id.expect_local()).expect_impl_item().expect_type().span
|
||||
cx.tcx
|
||||
.hir_node_by_def_id(assoc_item.def_id.expect_local())
|
||||
.expect_impl_item()
|
||||
.expect_type()
|
||||
.span
|
||||
})
|
||||
&& is_ty_exported(cx, ty)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_no_std_crate;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_copy};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -83,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
|
|||
|
||||
let mut difference = variants_size[0].size - variants_size[1].size;
|
||||
if difference > self.maximum_size_difference_allowed {
|
||||
let help_text = "consider boxing the large fields to reduce the total size of the enum";
|
||||
let help_text = "consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum";
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_ENUM_VARIANT,
|
||||
|
|
@ -117,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
|
|||
ident.span,
|
||||
"boxing a variant would require the type no longer be `Copy`",
|
||||
);
|
||||
} else {
|
||||
} else if !is_no_std_crate(cx) {
|
||||
let sugg: Vec<(Span, String)> = variants_size[0]
|
||||
.fields_size
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::{get_parent_expr, is_from_proc_macro};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use hir::def_id::DefId;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -102,39 +103,45 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
|
|||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
|
||||
let ExprKind::Path(qpath) = &expr.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
// `std::<integer>::<CONST>` check
|
||||
let (span, sugg, msg) = if let QPath::Resolved(None, path) = qpath
|
||||
let (sugg, msg) = if let ExprKind::Path(qpath) = &expr.kind
|
||||
&& let QPath::Resolved(None, path) = qpath
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
&& is_numeric_const(cx, def_id)
|
||||
&& let def_path = cx.get_def_path(def_id)
|
||||
&& let [.., mod_name, name] = &*def_path
|
||||
&& let [.., mod_name, name] = &*cx.get_def_path(def_id)
|
||||
// Skip linting if this usage looks identical to the associated constant,
|
||||
// since this would only require removing a `use` import (which is already linted).
|
||||
&& !is_numeric_const_path_canonical(path, [*mod_name, *name])
|
||||
{
|
||||
(
|
||||
expr.span,
|
||||
format!("{mod_name}::{name}"),
|
||||
vec![(expr.span, format!("{mod_name}::{name}"))],
|
||||
"usage of a legacy numeric constant",
|
||||
)
|
||||
// `<integer>::xxx_value` check
|
||||
} else if let QPath::TypeRelative(_, last_segment) = qpath
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id()
|
||||
&& let Some(par_expr) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::Call(_, []) = par_expr.kind
|
||||
} else if let ExprKind::Call(func, []) = &expr.kind
|
||||
&& let ExprKind::Path(qpath) = &func.kind
|
||||
&& let QPath::TypeRelative(ty, last_segment) = qpath
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id()
|
||||
&& is_integer_method(cx, def_id)
|
||||
{
|
||||
let name = last_segment.ident.name.as_str();
|
||||
|
||||
(
|
||||
last_segment.ident.span.with_hi(par_expr.span.hi()),
|
||||
name[..=2].to_ascii_uppercase(),
|
||||
"usage of a legacy numeric method",
|
||||
)
|
||||
let mut sugg = vec![
|
||||
// Replace the function name up to the end by the constant name
|
||||
(
|
||||
last_segment.ident.span.to(expr.span.shrink_to_hi()),
|
||||
last_segment.ident.name.as_str()[..=2].to_ascii_uppercase(),
|
||||
),
|
||||
];
|
||||
let before_span = expr.span.shrink_to_lo().until(ty.span);
|
||||
if !before_span.is_empty() {
|
||||
// Remove everything before the type name
|
||||
sugg.push((before_span, String::new()));
|
||||
}
|
||||
// Use `::` between the type name and the constant
|
||||
let between_span = ty.span.shrink_to_hi().until(last_segment.ident.span);
|
||||
if !between_span.check_source_text(cx, |s| s == "::") {
|
||||
sugg.push((between_span, String::from("::")));
|
||||
}
|
||||
(sugg, "usage of a legacy numeric method")
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -143,9 +150,8 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
|
|||
&& self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
{
|
||||
span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| {
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
span_lint_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.span, msg, |diag| {
|
||||
diag.multipart_suggestion_verbose(
|
||||
"use the associated constant instead",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::{DefId, DefIdSet};
|
||||
use rustc_hir::{
|
||||
BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind,
|
||||
ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy,
|
||||
QPath, TraitItemId, TyKind,
|
||||
BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind,
|
||||
Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemId,
|
||||
TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, FnSig, Ty};
|
||||
|
|
@ -266,11 +266,14 @@ fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span {
|
|||
}
|
||||
|
||||
fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) {
|
||||
fn is_named_self(cx: &LateContext<'_>, item: &TraitItemId, name: Symbol) -> bool {
|
||||
fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool {
|
||||
cx.tcx.item_name(item.owner_id) == name
|
||||
&& matches!(
|
||||
cx.tcx.fn_arg_idents(item.owner_id),
|
||||
[Some(Ident { name: kw::SelfLower, .. })],
|
||||
[Some(Ident {
|
||||
name: kw::SelfLower,
|
||||
..
|
||||
})],
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +287,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden
|
|||
}
|
||||
|
||||
if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
|
||||
&& trait_items.iter().any(|i| is_named_self(cx, i, sym::len))
|
||||
&& trait_items.iter().any(|&i| is_named_self(cx, i, sym::len))
|
||||
{
|
||||
let mut current_and_super_traits = DefIdSet::default();
|
||||
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ pub(super) fn check<'tcx>(
|
|||
var: canonical_id,
|
||||
indexed_mut: FxHashSet::default(),
|
||||
indexed_indirectly: FxHashMap::default(),
|
||||
unnamed_indexed_indirectly: false,
|
||||
indexed_directly: FxIndexMap::default(),
|
||||
unnamed_indexed_directly: false,
|
||||
referenced: FxHashSet::default(),
|
||||
nonindex: false,
|
||||
prefer_mutable: false,
|
||||
|
|
@ -47,7 +49,11 @@ pub(super) fn check<'tcx>(
|
|||
walk_expr(&mut visitor, body);
|
||||
|
||||
// linting condition: we only indexed one variable, and indexed it directly
|
||||
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
|
||||
if visitor.indexed_indirectly.is_empty()
|
||||
&& !visitor.unnamed_indexed_indirectly
|
||||
&& !visitor.unnamed_indexed_directly
|
||||
&& visitor.indexed_directly.len() == 1
|
||||
{
|
||||
let (indexed, (indexed_extent, indexed_ty)) = visitor
|
||||
.indexed_directly
|
||||
.into_iter()
|
||||
|
|
@ -217,6 +223,7 @@ fn is_end_eq_array_len<'tcx>(
|
|||
false
|
||||
}
|
||||
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
struct VarVisitor<'a, 'tcx> {
|
||||
/// context reference
|
||||
cx: &'a LateContext<'tcx>,
|
||||
|
|
@ -226,9 +233,13 @@ struct VarVisitor<'a, 'tcx> {
|
|||
indexed_mut: FxHashSet<Symbol>,
|
||||
/// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
|
||||
indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
|
||||
/// indirectly indexed literals, like `[1, 2, 3][(i + 4) % N]`
|
||||
unnamed_indexed_indirectly: bool,
|
||||
/// subset of `indexed` of vars that are indexed directly: `v[i]`
|
||||
/// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
|
||||
indexed_directly: FxIndexMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
|
||||
/// directly indexed literals, like `[1, 2, 3][i]`
|
||||
unnamed_indexed_directly: bool,
|
||||
/// Any names that are used outside an index operation.
|
||||
/// Used to detect things like `&mut vec` used together with `vec[i]`
|
||||
referenced: FxHashSet<Symbol>,
|
||||
|
|
@ -242,6 +253,7 @@ struct VarVisitor<'a, 'tcx> {
|
|||
|
||||
impl<'tcx> VarVisitor<'_, 'tcx> {
|
||||
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
|
||||
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
|
||||
if let ExprKind::Path(ref seqpath) = seqexpr.kind
|
||||
// the indexed container is referenced by a name
|
||||
&& let QPath::Resolved(None, seqvar) = *seqpath
|
||||
|
|
@ -251,7 +263,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> {
|
|||
if self.prefer_mutable {
|
||||
self.indexed_mut.insert(seqvar.segments[0].ident.name);
|
||||
}
|
||||
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
|
||||
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
|
||||
match res {
|
||||
Res::Local(hir_id) => {
|
||||
|
|
@ -286,6 +297,13 @@ impl<'tcx> VarVisitor<'_, 'tcx> {
|
|||
},
|
||||
_ => (),
|
||||
}
|
||||
} else if let ExprKind::Repeat(..) | ExprKind::Array(..) = seqexpr.kind {
|
||||
if index_used_directly {
|
||||
self.unnamed_indexed_directly = true;
|
||||
} else {
|
||||
self.unnamed_indexed_indirectly = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ use clippy_utils::macros::root_macro_call_first_node;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr};
|
||||
use rustc_hir::{
|
||||
Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_span::{BytePos, Span, sym};
|
||||
use std::iter::once;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
|
|
@ -20,7 +22,7 @@ pub(super) fn check<'tcx>(
|
|||
for_loop: Option<&ForLoop<'_>>,
|
||||
) {
|
||||
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
|
||||
NeverLoopResult::Diverging => {
|
||||
NeverLoopResult::Diverging { ref break_spans } => {
|
||||
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
|
||||
if let Some(ForLoop {
|
||||
arg: iterator,
|
||||
|
|
@ -38,10 +40,15 @@ pub(super) fn check<'tcx>(
|
|||
Applicability::Unspecified
|
||||
};
|
||||
|
||||
diag.span_suggestion_verbose(
|
||||
let mut suggestions = vec![(
|
||||
for_span.with_hi(iterator.span.hi()),
|
||||
"if you need the first element of the iterator, try writing",
|
||||
for_to_if_let_sugg(cx, iterator, pat),
|
||||
)];
|
||||
// Make sure to clear up the diverging sites when we remove a loopp.
|
||||
suggestions.extend(break_spans.iter().map(|span| (*span, String::new())));
|
||||
diag.multipart_suggestion_verbose(
|
||||
"if you need the first element of the iterator, try writing",
|
||||
suggestions,
|
||||
app,
|
||||
);
|
||||
}
|
||||
|
|
@ -70,22 +77,22 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
|
|||
/// The first two bits of information are in this enum, and the last part is in the
|
||||
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
|
||||
/// scope.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
enum NeverLoopResult {
|
||||
/// A continue may occur for the main loop.
|
||||
MayContinueMainLoop,
|
||||
/// We have not encountered any main loop continue,
|
||||
/// but we are diverging (subsequent control flow is not reachable)
|
||||
Diverging,
|
||||
Diverging { break_spans: Vec<Span> },
|
||||
/// We have not encountered any main loop continue,
|
||||
/// and subsequent control flow is (possibly) reachable
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
|
||||
fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult {
|
||||
match arg {
|
||||
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +101,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
|
|||
#[must_use]
|
||||
fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult {
|
||||
match first {
|
||||
NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first,
|
||||
NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop => first,
|
||||
NeverLoopResult::Normal => second(),
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +110,7 @@ fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult)
|
|||
#[must_use]
|
||||
fn combine_seq_many(iter: impl IntoIterator<Item = NeverLoopResult>) -> NeverLoopResult {
|
||||
for e in iter {
|
||||
if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e {
|
||||
if let NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop = e {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +125,19 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
|
|||
NeverLoopResult::MayContinueMainLoop
|
||||
},
|
||||
(NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal,
|
||||
(NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging,
|
||||
(
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: mut break_spans1,
|
||||
},
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: mut break_spans2,
|
||||
},
|
||||
) => {
|
||||
break_spans1.append(&mut break_spans2);
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: break_spans1,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +155,7 @@ fn never_loop_block<'tcx>(
|
|||
combine_seq_many(iter.map(|(e, els)| {
|
||||
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
// els is an else block in a let...else binding
|
||||
els.map_or(e, |els| {
|
||||
els.map_or(e.clone(), |els| {
|
||||
combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) {
|
||||
// Returning MayContinueMainLoop here means that
|
||||
// we will not evaluate the rest of the body
|
||||
|
|
@ -144,7 +163,7 @@ fn never_loop_block<'tcx>(
|
|||
// An else block always diverges, so the Normal case should not happen,
|
||||
// but the analysis is approximate so it might return Normal anyway.
|
||||
// Returning Normal here says that nothing more happens on the main path
|
||||
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal,
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
|
@ -159,6 +178,45 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
|
|||
}
|
||||
}
|
||||
|
||||
fn stmt_source_span(stmt: &Stmt<'_>) -> Span {
|
||||
let call_span = stmt.span.source_callsite();
|
||||
// if it is a macro call, the span will be missing the trailing semicolon
|
||||
if stmt.span == call_span {
|
||||
return call_span;
|
||||
}
|
||||
|
||||
// An expression without a trailing semi-colon (must have unit type).
|
||||
if let StmtKind::Expr(..) = stmt.kind {
|
||||
return call_span;
|
||||
}
|
||||
|
||||
call_span.with_hi(call_span.hi() + BytePos(1))
|
||||
}
|
||||
|
||||
/// Returns a Vec of all the individual spans after the highlighted expression in a block
|
||||
fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec<Span> {
|
||||
if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
|
||||
return block
|
||||
.stmts
|
||||
.iter()
|
||||
.skip_while(|inner| inner.hir_id != stmt.hir_id)
|
||||
.map(stmt_source_span)
|
||||
.chain(if let Some(e) = block.expr { vec![e.span] } else { vec![] })
|
||||
.collect();
|
||||
}
|
||||
|
||||
return vec![stmt.span];
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn is_label_for_block(cx: &LateContext<'_>, dest: &Destination) -> bool {
|
||||
dest.target_id
|
||||
.is_ok_and(|hir_id| matches!(cx.tcx.hir_node(hir_id), Node::Block(_)))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn never_loop_expr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
|
|
@ -197,7 +255,7 @@ fn never_loop_expr<'tcx>(
|
|||
ExprKind::Loop(b, _, _, _) => {
|
||||
// We don't attempt to track reachability after a loop,
|
||||
// just assume there may have been a break somewhere
|
||||
absorb_break(never_loop_block(cx, b, local_labels, main_loop_id))
|
||||
absorb_break(&never_loop_block(cx, b, local_labels, main_loop_id))
|
||||
},
|
||||
ExprKind::If(e, e2, e3) => {
|
||||
let e1 = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
|
|
@ -212,9 +270,10 @@ fn never_loop_expr<'tcx>(
|
|||
ExprKind::Match(e, arms, _) => {
|
||||
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
combine_seq(e, || {
|
||||
arms.iter().fold(NeverLoopResult::Diverging, |a, b| {
|
||||
combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id))
|
||||
})
|
||||
arms.iter()
|
||||
.fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| {
|
||||
combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id))
|
||||
})
|
||||
})
|
||||
},
|
||||
ExprKind::Block(b, _) => {
|
||||
|
|
@ -224,7 +283,7 @@ fn never_loop_expr<'tcx>(
|
|||
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
|
||||
let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1;
|
||||
match ret {
|
||||
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
|
||||
NeverLoopResult::Diverging { .. } if jumped_to => NeverLoopResult::Normal,
|
||||
_ => ret,
|
||||
}
|
||||
},
|
||||
|
|
@ -235,25 +294,39 @@ fn never_loop_expr<'tcx>(
|
|||
if id == main_loop_id {
|
||||
NeverLoopResult::MayContinueMainLoop
|
||||
} else {
|
||||
NeverLoopResult::Diverging
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: all_spans_after_expr(cx, expr),
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::Break(_, e) | ExprKind::Ret(e) => {
|
||||
ExprKind::Ret(e) => {
|
||||
let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
|
||||
never_loop_expr(cx, e, local_labels, main_loop_id)
|
||||
});
|
||||
combine_seq(first, || {
|
||||
// checks if break targets a block instead of a loop
|
||||
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind
|
||||
&& let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t)
|
||||
{
|
||||
*reachable = true;
|
||||
mark_block_as_reachable(expr, local_labels);
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
})
|
||||
},
|
||||
ExprKind::Break(dest, e) => {
|
||||
let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
|
||||
never_loop_expr(cx, e, local_labels, main_loop_id)
|
||||
});
|
||||
combine_seq(first, || {
|
||||
// checks if break targets a block instead of a loop
|
||||
mark_block_as_reachable(expr, local_labels);
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: if is_label_for_block(cx, &dest) {
|
||||
vec![]
|
||||
} else {
|
||||
all_spans_after_expr(cx, expr)
|
||||
},
|
||||
}
|
||||
NeverLoopResult::Diverging
|
||||
})
|
||||
},
|
||||
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
|
||||
NeverLoopResult::Diverging
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
}),
|
||||
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
|
|
@ -283,12 +356,12 @@ fn never_loop_expr<'tcx>(
|
|||
};
|
||||
let result = combine_seq(result, || {
|
||||
if cx.typeck_results().expr_ty(expr).is_never() {
|
||||
NeverLoopResult::Diverging
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
} else {
|
||||
NeverLoopResult::Normal
|
||||
}
|
||||
});
|
||||
if let NeverLoopResult::Diverging = result
|
||||
if let NeverLoopResult::Diverging { .. } = result
|
||||
&& let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& let Some(sym::todo_macro) = cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
|
|
@ -316,3 +389,11 @@ fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>)
|
|||
|
||||
format!("if let Some({pat_snippet}) = {iter_snippet}.next()")
|
||||
}
|
||||
|
||||
fn mark_block_as_reachable(expr: &Expr<'_>, local_labels: &mut [(HirId, bool)]) {
|
||||
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind
|
||||
&& let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t)
|
||||
{
|
||||
*reachable = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use hir::def::{DefKind, Res};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, AmbigArg};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{Span};
|
||||
use std::collections::BTreeMap;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use clippy_utils::msrvs::{self, Msrv};
|
|||
use clippy_utils::source::HasSession as _;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment};
|
||||
use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
|
|||
&& let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind
|
||||
&& let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) =
|
||||
(op.node, rhs, lhs)
|
||||
&& let Some(ty) = self.are_ty_eligible(cx, a, b)
|
||||
&& let Some((ty, b_n_refs)) = self.are_ty_eligible(cx, a, b)
|
||||
&& is_sub_expr(cx, if_expr.then, a, b, ty)
|
||||
&& is_sub_expr(cx, r#else, b, a, ty)
|
||||
{
|
||||
|
|
@ -86,8 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
|
|||
}
|
||||
};
|
||||
let sugg = format!(
|
||||
"{}.abs_diff({})",
|
||||
"{}.abs_diff({}{})",
|
||||
Sugg::hir(cx, a, "..").maybe_paren(),
|
||||
"*".repeat(b_n_refs),
|
||||
Sugg::hir(cx, b, "..")
|
||||
);
|
||||
diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability);
|
||||
|
|
@ -100,13 +101,15 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
|
|||
impl ManualAbsDiff {
|
||||
/// Returns a type if `a` and `b` are both of it, and this lint can be applied to that
|
||||
/// type (currently, any primitive int, or a `Duration`)
|
||||
fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<Ty<'tcx>> {
|
||||
fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<(Ty<'tcx>, usize)> {
|
||||
let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF);
|
||||
let is_duration =
|
||||
|ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF);
|
||||
|
||||
let a_ty = cx.typeck_results().expr_ty(a).peel_refs();
|
||||
(a_ty == cx.typeck_results().expr_ty(b).peel_refs() && (is_int(a_ty) || is_duration(a_ty))).then_some(a_ty)
|
||||
let (b_ty, b_n_refs) = peel_middle_ty_refs(cx.typeck_results().expr_ty(b));
|
||||
|
||||
(a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert {
|
|||
ExprKind::Unary(UnOp::Not, e) => (e, ""),
|
||||
_ => (cond, "!"),
|
||||
};
|
||||
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren();
|
||||
let cond_sugg =
|
||||
sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability).maybe_paren();
|
||||
let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" };
|
||||
let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}");
|
||||
// we show to the user the suggestion without the comments, but when applying the fix, include the
|
||||
|
|
|
|||
|
|
@ -152,21 +152,26 @@ fn report_single_pattern(
|
|||
}) if lit.node.is_str() || lit.node.is_bytestr() => pat_ref_count + 1,
|
||||
_ => pat_ref_count,
|
||||
};
|
||||
// References are only implicitly added to the pattern, so no overflow here.
|
||||
// e.g. will work: match &Some(_) { Some(_) => () }
|
||||
// will not: match Some(_) { &Some(_) => () }
|
||||
let ref_count_diff = ty_ref_count - pat_ref_count;
|
||||
|
||||
// Try to remove address of expressions first.
|
||||
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
|
||||
let ref_count_diff = ref_count_diff - removed;
|
||||
// References are implicitly removed when `deref_patterns` are used.
|
||||
// They are implicitly added when match ergonomics are used.
|
||||
let (ex, ref_or_deref_adjust) = if ty_ref_count > pat_ref_count {
|
||||
let ref_count_diff = ty_ref_count - pat_ref_count;
|
||||
|
||||
// Try to remove address of expressions first.
|
||||
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
|
||||
|
||||
(ex, String::from(if ref_count_diff == removed { "" } else { "&" }))
|
||||
} else {
|
||||
(ex, "*".repeat(pat_ref_count - ty_ref_count))
|
||||
};
|
||||
|
||||
let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
|
||||
let sugg = format!(
|
||||
"if {} == {}{} {}{els_str}",
|
||||
snippet_with_context(cx, ex.span, ctxt, "..", &mut app).0,
|
||||
// PartialEq for different reference counts may not exist.
|
||||
"&".repeat(ref_count_diff),
|
||||
ref_or_deref_adjust,
|
||||
snippet_with_applicability(cx, arm.pat.span, "..", &mut app),
|
||||
expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{contains_return, is_inside_always_const_context, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::EXPECT_FUN_CALL;
|
||||
|
||||
|
|
@ -23,10 +25,10 @@ pub(super) fn check<'tcx>(
|
|||
receiver: &'tcx hir::Expr<'tcx>,
|
||||
args: &'tcx [hir::Expr<'tcx>],
|
||||
) {
|
||||
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
|
||||
// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
|
||||
// `&str`
|
||||
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||
let mut arg_root = arg;
|
||||
let mut arg_root = peel_blocks(arg);
|
||||
loop {
|
||||
arg_root = match &arg_root.kind {
|
||||
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
|
||||
|
|
@ -47,124 +49,68 @@ pub(super) fn check<'tcx>(
|
|||
arg_root
|
||||
}
|
||||
|
||||
// Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
|
||||
// converted to string.
|
||||
fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
if is_type_lang_item(cx, arg_ty, hir::LangItem::String) {
|
||||
return false;
|
||||
fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool {
|
||||
for_each_expr(cx, arg, |expr| {
|
||||
if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. })
|
||||
&& !is_inside_always_const_context(cx.tcx, expr.hir_id)
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
if name == sym::expect
|
||||
&& let [arg] = args
|
||||
&& let arg_root = get_arg_root(cx, arg)
|
||||
&& contains_call(cx, arg_root)
|
||||
&& !contains_return(arg_root)
|
||||
{
|
||||
let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
|
||||
"||"
|
||||
} else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
|
||||
"|_|"
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let span_replace_word = method_span.with_hi(expr.span.hi());
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
// Special handling for `format!` as arg_root
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
|
||||
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
|
||||
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
|
||||
{
|
||||
let span = format_args_inputs_span(format_args);
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let ty::Ref(_, ty, ..) = arg_ty.kind()
|
||||
&& ty.is_str()
|
||||
&& can_be_static_str(cx, arg)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
||||
let arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if an expression could have type `&'static str`, knowing that it
|
||||
// has type `&str` for some lifetime.
|
||||
fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||
match arg.kind {
|
||||
hir::ExprKind::Lit(_) => true,
|
||||
hir::ExprKind::Call(fun, _) => {
|
||||
if let hir::ExprKind::Path(ref p) = fun.kind {
|
||||
match cx.qpath_res(p, fun.hir_id) {
|
||||
hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
|
||||
cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder().kind(),
|
||||
ty::Ref(re, ..) if re.is_static(),
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(..) => {
|
||||
cx.typeck_results()
|
||||
.type_dependent_def_id(arg.hir_id)
|
||||
.is_some_and(|method_id| {
|
||||
matches!(
|
||||
cx.tcx.fn_sig(method_id).instantiate_identity().output().skip_binder().kind(),
|
||||
ty::Ref(re, ..) if re.is_static()
|
||||
)
|
||||
})
|
||||
},
|
||||
hir::ExprKind::Path(ref p) => matches!(
|
||||
cx.qpath_res(p, arg.hir_id),
|
||||
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _)
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_call(node: &hir::ExprKind<'_>) -> bool {
|
||||
match node {
|
||||
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
|
||||
is_call(&expr.kind)
|
||||
},
|
||||
hir::ExprKind::Call(..)
|
||||
| hir::ExprKind::MethodCall(..)
|
||||
// These variants are debatable or require further examination
|
||||
| hir::ExprKind::If(..)
|
||||
| hir::ExprKind::Match(..)
|
||||
| hir::ExprKind::Block{ .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() != 1 || name != sym::expect || !is_call(&args[0].kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
|
||||
"||"
|
||||
} else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
|
||||
"|_|"
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_root = get_arg_root(cx, &args[0]);
|
||||
|
||||
let span_replace_word = method_span.with_hi(expr.span.hi());
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
// Special handling for `format!` as arg_root
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
|
||||
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
|
||||
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
|
||||
{
|
||||
let span = format_args_inputs_span(format_args);
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
|
||||
if requires_to_string(cx, arg_root) {
|
||||
arg_root_snippet.to_mut().push_str(".to_string()");
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::FILTER_MAP_BOOL_THEN;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{
|
||||
CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks,
|
||||
|
|
@ -45,9 +45,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
|
|||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count()
|
||||
&& let Some(param_snippet) = param.span.get_source_text(cx)
|
||||
&& let Some(filter) = recv.span.get_source_text(cx)
|
||||
&& let Some(map) = then_body.span.get_source_text(cx)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (filter, _) = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut applicability);
|
||||
let (map, _) = snippet_with_context(cx, then_body.span, expr.span.ctxt(), "..", &mut applicability);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FILTER_MAP_BOOL_THEN,
|
||||
|
|
@ -62,7 +64,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
|
|||
"filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})",
|
||||
derefs = "*".repeat(needed_derefs)
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
applicability,
|
||||
);
|
||||
} else {
|
||||
diag.help("consider using `filter` then `map` instead");
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
|
|||
match x {
|
||||
UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())),
|
||||
UseKind::Borrowed(s) => {
|
||||
#[expect(clippy::range_plus_one)]
|
||||
let range = s.map_range(cx, |_, src, range| {
|
||||
let src = src.get(range.clone())?;
|
||||
let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']);
|
||||
|
|
|
|||
|
|
@ -3859,6 +3859,7 @@ declare_clippy_lint! {
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
|
||||
/// Also checks for equality comparisons like `option.map(f) == Some(true)` and `result.map(f) == Ok(true)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
|
||||
|
|
@ -3869,6 +3870,11 @@ declare_clippy_lint! {
|
|||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.map(|a| a > 10).unwrap_or_default();
|
||||
/// result.map(|a| a > 10).unwrap_or_default();
|
||||
///
|
||||
/// option.map(|a| a > 10) == Some(true);
|
||||
/// result.map(|a| a > 10) == Ok(true);
|
||||
/// option.map(|a| a > 10) != Some(true);
|
||||
/// result.map(|a| a > 10) != Ok(true);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
|
|
@ -3876,11 +3882,16 @@ declare_clippy_lint! {
|
|||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.is_some_and(|a| a > 10);
|
||||
/// result.is_ok_and(|a| a > 10);
|
||||
///
|
||||
/// option.is_some_and(|a| a > 10);
|
||||
/// result.is_ok_and(|a| a > 10);
|
||||
/// option.is_none_or(|a| a > 10);
|
||||
/// !result.is_ok_and(|a| a > 10);
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub MANUAL_IS_VARIANT_AND,
|
||||
pedantic,
|
||||
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
|
||||
"using `.map(f).unwrap_or_default()` or `.map(f) == Some/Ok(true)`, which are more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -5275,10 +5286,6 @@ impl Methods {
|
|||
}
|
||||
map_identity::check(cx, expr, recv, m_arg, name, span);
|
||||
manual_inspect::check(cx, expr, m_arg, name, span, self.msrv);
|
||||
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
|
||||
},
|
||||
(sym::map_break | sym::map_continue, [m_arg]) => {
|
||||
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
|
||||
},
|
||||
(sym::map_or, [def, map]) => {
|
||||
option_map_or_none::check(cx, expr, recv, def, map);
|
||||
|
|
@ -5546,7 +5553,7 @@ impl Methods {
|
|||
// Handle method calls whose receiver and arguments may come from expansion
|
||||
if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind {
|
||||
match (path.ident.name, args) {
|
||||
(sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => {
|
||||
(sym::expect, [_]) => {
|
||||
unwrap_expect_used::check(
|
||||
cx,
|
||||
expr,
|
||||
|
|
|
|||
|
|
@ -242,15 +242,23 @@ pub(super) fn check<'tcx>(
|
|||
let inner_arg = peel_blocks(arg);
|
||||
for_each_expr(cx, inner_arg, |ex| {
|
||||
let is_top_most_expr = ex.hir_id == inner_arg.hir_id;
|
||||
if let hir::ExprKind::Call(fun, fun_args) = ex.kind {
|
||||
let fun_span = if fun_args.is_empty() && is_top_most_expr {
|
||||
Some(fun.span)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
match ex.kind {
|
||||
hir::ExprKind::Call(fun, fun_args) => {
|
||||
let fun_span = if fun_args.is_empty() && is_top_most_expr {
|
||||
Some(fun.span)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(..) => {
|
||||
if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, None) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ use clippy_utils::{is_path_lang_item, sym};
|
|||
use rustc_ast::LitKind;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData,
|
||||
};
|
||||
use rustc_hir::{Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{Ty, TypeckResults};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::Attribute;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{self as hir, Attribute};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::AssocItemContainer;
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
@ -97,11 +97,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
|
|||
}
|
||||
match it.kind {
|
||||
hir::ItemKind::Fn { .. } => {
|
||||
if fn_is_externally_exported(cx, it.owner_id.to_def_id()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let desc = "a function";
|
||||
let attrs = cx.tcx.hir_attrs(it.hir_id());
|
||||
check_missing_inline_attrs(cx, attrs, it.span, desc);
|
||||
},
|
||||
hir::ItemKind::Trait(ref _constness, ref _is_auto, ref _unsafe, _ident, _generics, _bounds, trait_items) => {
|
||||
hir::ItemKind::Trait(
|
||||
ref _constness,
|
||||
ref _is_auto,
|
||||
ref _unsafe,
|
||||
_ident,
|
||||
_generics,
|
||||
_bounds,
|
||||
trait_items,
|
||||
) => {
|
||||
// note: we need to check if the trait is exported so we can't use
|
||||
// `LateLintPass::check_trait_item` here.
|
||||
for &tit in trait_items {
|
||||
|
|
@ -173,3 +185,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
|
|||
check_missing_inline_attrs(cx, attrs, impl_item.span, desc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if this function is externally exported, where #[inline] wouldn't have the desired effect
|
||||
/// and a rustc warning would be triggered, see #15301
|
||||
fn fn_is_externally_exported(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
||||
let attrs = cx.tcx.codegen_fn_attrs(def_id);
|
||||
attrs.contains_extern_indicator()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
|
|||
}) = item.kind
|
||||
&& let Some(trait_id) = trait_ref.trait_def_id()
|
||||
{
|
||||
let trait_item_ids: DefIdSet = cx.tcx.associated_items(item.owner_id)
|
||||
let trait_item_ids: DefIdSet = cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id)
|
||||
.in_definition_order()
|
||||
.filter_map(|assoc_item| assoc_item.trait_item_def_id)
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -171,14 +171,11 @@ impl<'tcx> Visitor<'tcx> for DivergenceVisitor<'_, 'tcx> {
|
|||
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
|
||||
ExprKind::Call(func, _) => {
|
||||
let typ = self.cx.typeck_results().expr_ty(func);
|
||||
match typ.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => {
|
||||
let sig = typ.fn_sig(self.cx.tcx);
|
||||
if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never {
|
||||
self.report_diverging_sub_expr(e);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
if typ.is_fn() {
|
||||
let sig = typ.fn_sig(self.cx.tcx);
|
||||
if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never {
|
||||
self.report_diverging_sub_expr(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(..) => {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ fn check_arguments<'tcx>(
|
|||
name: &str,
|
||||
fn_kind: &str,
|
||||
) {
|
||||
if let ty::FnDef(..) | ty::FnPtr(..) = type_definition.kind() {
|
||||
if type_definition.is_fn() {
|
||||
let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
|
||||
for (argument, parameter) in iter::zip(arguments, parameters) {
|
||||
if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass;
|
|||
use rustc_span::Span;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::ty::has_iter_method;
|
||||
use clippy_utils::{is_trait_method, sym};
|
||||
|
||||
|
|
@ -101,18 +101,23 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach {
|
|||
|
||||
let body_param_sugg = snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability);
|
||||
let for_each_rev_sugg = snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability);
|
||||
let body_value_sugg = snippet_with_applicability(cx, body.value.span, "..", &mut applicability);
|
||||
let (body_value_sugg, is_macro_call) =
|
||||
snippet_with_context(cx, body.value.span, for_each_recv.span.ctxt(), "..", &mut applicability);
|
||||
|
||||
let sugg = format!(
|
||||
"for {} in {} {}",
|
||||
body_param_sugg,
|
||||
for_each_rev_sugg,
|
||||
match body.value.kind {
|
||||
ExprKind::Block(block, _) if is_let_desugar(block) => {
|
||||
format!("{{ {body_value_sugg} }}")
|
||||
},
|
||||
ExprKind::Block(_, _) => body_value_sugg.to_string(),
|
||||
_ => format!("{{ {body_value_sugg}; }}"),
|
||||
if is_macro_call {
|
||||
format!("{{ {body_value_sugg}; }}")
|
||||
} else {
|
||||
match body.value.kind {
|
||||
ExprKind::Block(block, _) if is_let_desugar(block) => {
|
||||
format!("{{ {body_value_sugg} }}")
|
||||
},
|
||||
ExprKind::Block(_, _) => body_value_sugg.to_string(),
|
||||
_ => format!("{{ {body_value_sugg}; }}"),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -246,8 +246,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
|
|||
for (span, suggestion) in clone_spans {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
span.get_source_text(cx)
|
||||
.map_or("change the call to".to_owned(), |src| format!("change `{src}` to")),
|
||||
span.get_source_text(cx).map_or_else(
|
||||
|| "change the call to".to_owned(),
|
||||
|src| format!("change `{src}` to"),
|
||||
),
|
||||
suggestion,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
|
|
@ -275,8 +277,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
|
|||
for (span, suggestion) in clone_spans {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
span.get_source_text(cx)
|
||||
.map_or("change the call to".to_owned(), |src| format!("change `{src}` to")),
|
||||
span.get_source_text(cx).map_or_else(
|
||||
|| "change the call to".to_owned(),
|
||||
|src| format!("change `{src}` to"),
|
||||
),
|
||||
suggestion,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -65,11 +65,16 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
|
|||
..
|
||||
}) = item.kind
|
||||
{
|
||||
for assoc_item in cx.tcx.associated_items(item.owner_id.def_id)
|
||||
for assoc_item in cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id.def_id)
|
||||
.filter_by_name_unhygienic(sym::new)
|
||||
{
|
||||
if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind {
|
||||
let impl_item = cx.tcx.hir_node_by_def_id(assoc_item.def_id.expect_local()).expect_impl_item();
|
||||
let impl_item = cx
|
||||
.tcx
|
||||
.hir_node_by_def_id(assoc_item.def_id.expect_local())
|
||||
.expect_impl_item();
|
||||
if impl_item.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ use clippy_config::Conf;
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary};
|
||||
use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use {rustc_ast as ast, rustc_hir as hir};
|
||||
|
||||
|
|
@ -89,6 +88,18 @@ impl ArithmeticSideEffects {
|
|||
self.allowed_unary.contains(ty_string_elem)
|
||||
}
|
||||
|
||||
fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
if let ty::Adt(adt, substs) = ty.kind()
|
||||
&& cx.tcx.is_diagnostic_item(sym::NonZero, adt.did())
|
||||
&& let int_type = substs.type_at(0)
|
||||
&& matches!(int_type.kind(), ty::Uint(_))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies built-in types that have specific allowed operations
|
||||
fn has_specific_allowed_type_and_operation<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
|
|
@ -97,33 +108,12 @@ impl ArithmeticSideEffects {
|
|||
rhs_ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem);
|
||||
let is_non_zero_u = |cx: &LateContext<'tcx>, ty: Ty<'tcx>| {
|
||||
let tcx = cx.tcx;
|
||||
|
||||
let ty::Adt(adt, substs) = ty.kind() else { return false };
|
||||
|
||||
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let int_type = substs.type_at(0);
|
||||
let unsigned_int_types = [
|
||||
tcx.types.u8,
|
||||
tcx.types.u16,
|
||||
tcx.types.u32,
|
||||
tcx.types.u64,
|
||||
tcx.types.u128,
|
||||
tcx.types.usize,
|
||||
];
|
||||
|
||||
unsigned_int_types.contains(&int_type)
|
||||
};
|
||||
let is_sat_or_wrap = |ty: Ty<'_>| {
|
||||
is_type_diagnostic_item(cx, ty, sym::Saturating) || is_type_diagnostic_item(cx, ty, sym::Wrapping)
|
||||
};
|
||||
|
||||
// If the RHS is `NonZero<u*>`, then division or module by zero will never occur.
|
||||
if is_non_zero_u(cx, rhs_ty) && is_div_or_rem {
|
||||
if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +209,18 @@ impl ArithmeticSideEffects {
|
|||
let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
|
||||
actual_lhs = expr_or_init(cx, actual_lhs);
|
||||
actual_rhs = expr_or_init(cx, actual_rhs);
|
||||
|
||||
// `NonZeroU*.get() - 1`, will never overflow
|
||||
if let hir::BinOpKind::Sub = op
|
||||
&& let hir::ExprKind::MethodCall(method, receiver, [], _) = actual_lhs.kind
|
||||
&& method.ident.name == sym::get
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs()
|
||||
&& Self::is_non_zero_u(cx, receiver_ty)
|
||||
&& let Some(1) = Self::literal_integer(cx, actual_rhs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs();
|
||||
let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs();
|
||||
if self.has_allowed_binary(lhs_ty, rhs_ty) {
|
||||
|
|
@ -227,6 +229,7 @@ impl ArithmeticSideEffects {
|
|||
if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
|
||||
if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op {
|
||||
// At least for integers, shifts are already handled by the CTFE
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ use clippy_utils::consts::is_zero_integer_const;
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::expr_type_is_certain;
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::MANUAL_IS_MULTIPLE_OF;
|
||||
|
||||
|
|
@ -22,9 +23,21 @@ pub(super) fn check<'tcx>(
|
|||
&& let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs)
|
||||
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
|
||||
&& operand_op.node == BinOpKind::Rem
|
||||
&& matches!(
|
||||
cx.typeck_results().expr_ty_adjusted(operand_left).peel_refs().kind(),
|
||||
ty::Uint(_)
|
||||
)
|
||||
&& matches!(
|
||||
cx.typeck_results().expr_ty_adjusted(operand_right).peel_refs().kind(),
|
||||
ty::Uint(_)
|
||||
)
|
||||
&& expr_type_is_certain(cx, operand_left)
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app);
|
||||
let divisor = deref_sugg(
|
||||
Sugg::hir_with_applicability(cx, operand_right, "_", &mut app),
|
||||
cx.typeck_results().expr_ty_adjusted(operand_right),
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_MULTIPLE_OF,
|
||||
|
|
@ -64,3 +77,11 @@ fn uint_compare_to_zero<'tcx>(
|
|||
|
||||
matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand)
|
||||
}
|
||||
|
||||
fn deref_sugg<'a>(sugg: Sugg<'a>, ty: Ty<'_>) -> Sugg<'a> {
|
||||
if let ty::Ref(_, target_ty, _) = ty.kind() {
|
||||
deref_sugg(sugg.deref(), *target_ty)
|
||||
} else {
|
||||
sugg
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
|
|||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Match(_, arms, _) = expr.kind {
|
||||
// if the match is generated by an external macro, the writer does not control
|
||||
// how the scrutinee (`match &scrutiny { ... }`) is matched
|
||||
if expr.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for arm in arms {
|
||||
let pat = &arm.pat;
|
||||
if apply_lint(cx, pat, DerefPossible::Possible) {
|
||||
|
|
|
|||
|
|
@ -584,7 +584,13 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[
|
|||
Some((Node::Stmt(_), _)) => (),
|
||||
Some((Node::LetStmt(l), _)) => {
|
||||
// Only trace simple bindings. e.g `let x = y;`
|
||||
if let PatKind::Binding(BindingMode::NONE, id, _, None) = l.pat.kind {
|
||||
if let PatKind::Binding(BindingMode::NONE, id, ident, None) = l.pat.kind
|
||||
// Let's not lint for the current parameter. The user may still intend to mutate
|
||||
// (or, if not mutate, then perhaps call a method that's not otherwise available
|
||||
// for) the referenced value behind the parameter through this local let binding
|
||||
// with the underscore being only temporary.
|
||||
&& !ident.name.as_str().starts_with('_')
|
||||
{
|
||||
self.bindings.insert(id, args_idx);
|
||||
} else {
|
||||
set_skip_flag();
|
||||
|
|
@ -650,7 +656,14 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[
|
|||
.filter_map(|(i, arg)| {
|
||||
let param = &body.params[arg.idx];
|
||||
match param.pat.kind {
|
||||
PatKind::Binding(BindingMode::NONE, id, _, None) if !is_lint_allowed(cx, PTR_ARG, param.hir_id) => {
|
||||
PatKind::Binding(BindingMode::NONE, id, ident, None)
|
||||
if !is_lint_allowed(cx, PTR_ARG, param.hir_id)
|
||||
// Let's not lint for the current parameter. The user may still intend to mutate
|
||||
// (or, if not mutate, then perhaps call a method that's not otherwise available
|
||||
// for) the referenced value behind the parameter with the underscore being only
|
||||
// temporary.
|
||||
&& !ident.name.as_str().starts_with('_') =>
|
||||
{
|
||||
Some((id, i))
|
||||
},
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,20 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{get_parent_expr, higher, is_in_const_context, is_integer_const, path_to_local};
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{
|
||||
expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item,
|
||||
path_to_local,
|
||||
};
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, LangItem, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint};
|
||||
use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::{Span, sym};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -24,6 +29,12 @@ declare_clippy_lint! {
|
|||
/// The code is more readable with an inclusive range
|
||||
/// like `x..=y`.
|
||||
///
|
||||
/// ### Limitations
|
||||
/// The lint is conservative and will trigger only when switching
|
||||
/// from an exclusive to an inclusive range is provably safe from
|
||||
/// a typing point of view. This corresponds to situations where
|
||||
/// the range is used as an iterator, or for indexing.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Will add unnecessary pair of parentheses when the
|
||||
/// expression is not wrapped in a pair but starts with an opening parenthesis
|
||||
|
|
@ -34,11 +45,6 @@ declare_clippy_lint! {
|
|||
/// exclusive ranges, because they essentially add an extra branch that
|
||||
/// LLVM may fail to hoist out of the loop.
|
||||
///
|
||||
/// This will cause a warning that cannot be fixed if the consumer of the
|
||||
/// range only accepts a specific range type, instead of the generic
|
||||
/// `RangeBounds` trait
|
||||
/// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let x = 0;
|
||||
|
|
@ -71,11 +77,11 @@ declare_clippy_lint! {
|
|||
/// The code is more readable with an exclusive range
|
||||
/// like `x..y`.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This will cause a warning that cannot be fixed if
|
||||
/// the consumer of the range only accepts a specific range type, instead of
|
||||
/// the generic `RangeBounds` trait
|
||||
/// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
|
||||
/// ### Limitations
|
||||
/// The lint is conservative and will trigger only when switching
|
||||
/// from an inclusive to an exclusive range is provably safe from
|
||||
/// a typing point of view. This corresponds to situations where
|
||||
/// the range is used as an iterator, or for indexing.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
|
|
@ -344,70 +350,188 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) ->
|
|||
None
|
||||
}
|
||||
|
||||
// exclusive range plus one: `x..(y+1)`
|
||||
fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if expr.span.can_be_used_for_suggestions()
|
||||
&& let Some(higher::Range {
|
||||
start,
|
||||
end: Some(end),
|
||||
limits: RangeLimits::HalfOpen,
|
||||
}) = higher::Range::hir(expr)
|
||||
&& let Some(y) = y_plus_one(cx, end)
|
||||
/// Check whether `expr` could switch range types without breaking the typing requirements. This is
|
||||
/// generally the case when `expr` is used as an iterator for example, or as a slice or `&str`
|
||||
/// index.
|
||||
///
|
||||
/// FIXME: Note that the current implementation may still return false positives. A proper fix would
|
||||
/// check that the obligations are still satisfied after switching the range type.
|
||||
fn can_switch_ranges<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
original: RangeLimits,
|
||||
inner_ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
let use_ctxt = expr_use_ctxt(cx, expr);
|
||||
let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)`
|
||||
if let ExprKind::Call(func, [arg]) = parent_expr.kind
|
||||
&& arg.hir_id == use_ctxt.child_id
|
||||
&& is_path_lang_item(cx, func, LangItem::IntoIterIntoIter)
|
||||
{
|
||||
let span = expr.span;
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RANGE_PLUS_ONE,
|
||||
span,
|
||||
"an inclusive range would be more readable",
|
||||
|diag| {
|
||||
let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string());
|
||||
let end = Sugg::hir(cx, y, "y").maybe_paren();
|
||||
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
|
||||
Some(true) => {
|
||||
diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect);
|
||||
},
|
||||
Some(false) => {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"use",
|
||||
format!("{start}..={end}"),
|
||||
Applicability::MachineApplicable, // snippet
|
||||
);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`,
|
||||
// or `RangeBounds` traits.
|
||||
if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind
|
||||
&& receiver.hir_id == use_ctxt.child_id
|
||||
&& let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
|
||||
&& let Some(trait_did) = cx.tcx.trait_of_item(method_did)
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(trait_did),
|
||||
Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds)
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`,
|
||||
// or `RangeBounds` trait.
|
||||
if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
|
||||
&& let Some(id) = fn_def_id(cx, parent_expr)
|
||||
&& let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id)
|
||||
{
|
||||
let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) {
|
||||
arg_idx + 1
|
||||
} else {
|
||||
arg_idx
|
||||
};
|
||||
let inputs = cx
|
||||
.tcx
|
||||
.liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity())
|
||||
.inputs();
|
||||
let expr_ty = inputs[input_idx];
|
||||
// Check that the `expr` type is present only once, otherwise modifying just one of them might be
|
||||
// risky if they are referenced using the same generic type for example.
|
||||
if inputs.iter().enumerate().all(|(n, ty)|
|
||||
n == input_idx
|
||||
|| !ty.walk().any(|arg| matches!(arg.kind(),
|
||||
GenericArgKind::Type(ty) if ty == expr_ty)))
|
||||
// Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`.
|
||||
&& cx
|
||||
.tcx
|
||||
.param_env(id)
|
||||
.caller_bounds()
|
||||
.into_iter()
|
||||
.any(|p| {
|
||||
if let ClauseKind::Trait(t) = p.kind().skip_binder()
|
||||
&& t.polarity == PredicatePolarity::Positive
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(t.trait_ref.def_id),
|
||||
Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds)
|
||||
)
|
||||
{
|
||||
t.self_ty() == expr_ty
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if `expr` is used for indexing, and if the switched range type could be used
|
||||
// as well.
|
||||
if let ExprKind::Index(outer_expr, index, _) = parent_expr.kind
|
||||
&& index.hir_id == expr.hir_id
|
||||
// Build the switched range type (for example `RangeInclusive<usize>`).
|
||||
&& let Some(switched_range_def_id) = match original {
|
||||
RangeLimits::HalfOpen => cx.tcx.lang_items().range_inclusive_struct(),
|
||||
RangeLimits::Closed => cx.tcx.lang_items().range_struct(),
|
||||
}
|
||||
&& let switched_range_ty = cx
|
||||
.tcx
|
||||
.type_of(switched_range_def_id)
|
||||
.instantiate(cx.tcx, &[inner_ty.into()])
|
||||
// Check that the switched range type can be used for indexing the original expression
|
||||
// through the `Index` or `IndexMut` trait.
|
||||
&& let ty::Ref(_, outer_ty, mutability) = cx.typeck_results().expr_ty_adjusted(outer_expr).kind()
|
||||
&& let Some(index_def_id) = match mutability {
|
||||
Mutability::Not => cx.tcx.lang_items().index_trait(),
|
||||
Mutability::Mut => cx.tcx.lang_items().index_mut_trait(),
|
||||
}
|
||||
&& implements_trait(cx, *outer_ty, index_def_id, &[switched_range_ty.into()])
|
||||
// We could also check that the associated item of the `index_def_id` trait with the switched range type
|
||||
// return the same type, but it is reasonable to expect so. We can't check that the result is identical
|
||||
// in both `Index<Range<…>>` and `Index<RangeInclusive<…>>` anyway.
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// exclusive range plus one: `x..(y+1)`
|
||||
fn check_exclusive_range_plus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
check_range_switch(
|
||||
cx,
|
||||
expr,
|
||||
RangeLimits::HalfOpen,
|
||||
y_plus_one,
|
||||
RANGE_PLUS_ONE,
|
||||
"an inclusive range would be more readable",
|
||||
"..=",
|
||||
);
|
||||
}
|
||||
|
||||
// inclusive range minus one: `x..=(y-1)`
|
||||
fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
fn check_inclusive_range_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
check_range_switch(
|
||||
cx,
|
||||
expr,
|
||||
RangeLimits::Closed,
|
||||
y_minus_one,
|
||||
RANGE_MINUS_ONE,
|
||||
"an exclusive range would be more readable",
|
||||
"..",
|
||||
);
|
||||
}
|
||||
|
||||
/// Check for a `kind` of range in `expr`, check for `predicate` on the end,
|
||||
/// and emit the `lint` with `msg` and the `operator`.
|
||||
fn check_range_switch<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
kind: RangeLimits,
|
||||
predicate: impl for<'hir> FnOnce(&LateContext<'_>, &Expr<'hir>) -> Option<&'hir Expr<'hir>>,
|
||||
lint: &'static Lint,
|
||||
msg: &'static str,
|
||||
operator: &str,
|
||||
) {
|
||||
if expr.span.can_be_used_for_suggestions()
|
||||
&& let Some(higher::Range {
|
||||
start,
|
||||
end: Some(end),
|
||||
limits: RangeLimits::Closed,
|
||||
limits,
|
||||
}) = higher::Range::hir(expr)
|
||||
&& let Some(y) = y_minus_one(cx, end)
|
||||
&& limits == kind
|
||||
&& let Some(y) = predicate(cx, end)
|
||||
&& can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y))
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RANGE_MINUS_ONE,
|
||||
expr.span,
|
||||
"an exclusive range would be more readable",
|
||||
|diag| {
|
||||
let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string());
|
||||
let end = Sugg::hir(cx, y, "y").maybe_paren();
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"use",
|
||||
format!("{start}..{end}"),
|
||||
Applicability::MachineApplicable, // snippet
|
||||
);
|
||||
},
|
||||
);
|
||||
let span = expr.span;
|
||||
span_lint_and_then(cx, lint, span, msg, |diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let start = start.map_or(String::new(), |x| {
|
||||
Sugg::hir_with_applicability(cx, x, "<x>", &mut app)
|
||||
.maybe_paren()
|
||||
.to_string()
|
||||
});
|
||||
let end = Sugg::hir_with_applicability(cx, y, "<y>", &mut app).maybe_paren();
|
||||
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
|
||||
Some(true) => {
|
||||
diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app);
|
||||
},
|
||||
Some(false) => {
|
||||
diag.span_suggestion(span, "use", format!("{start}{operator}{end}"), app);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,7 +618,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
|
||||
fn y_plus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(
|
||||
Spanned {
|
||||
|
|
@ -515,7 +639,7 @@ fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'
|
|||
}
|
||||
}
|
||||
|
||||
fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
|
||||
fn y_minus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(
|
||||
Spanned {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use rustc_data_structures::fx::FxHashMap;
|
|||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{AssocKind, AssocItem};
|
||||
use rustc_middle::ty::{AssocItem, AssocKind};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
|
@ -53,11 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
for id in cx.tcx.hir_free_items() {
|
||||
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
|
||||
&& let item = cx.tcx.hir_item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let ItemKind::Impl(Impl { of_trait, self_ty, .. }) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
|
@ -127,7 +123,9 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
},
|
||||
None => {
|
||||
for assoc_item in cx.tcx.associated_items(id.owner_id).in_definition_order() {
|
||||
let AssocKind::Fn { name, .. } = assoc_item.kind else { continue };
|
||||
let AssocKind::Fn { name, .. } = assoc_item.kind else {
|
||||
continue;
|
||||
};
|
||||
let impl_span = cx.tcx.def_span(assoc_item.def_id);
|
||||
let hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local());
|
||||
if let Some(trait_spans) = existing_name.trait_methods.get(&name) {
|
||||
|
|
@ -140,10 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
|diag| {
|
||||
// TODO should we `span_note` on every trait?
|
||||
// iterate on trait_spans?
|
||||
diag.span_note(
|
||||
trait_spans[0],
|
||||
format!("existing `{name}` defined here"),
|
||||
);
|
||||
diag.span_note(trait_spans[0], format!("existing `{name}` defined here"));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::is_def_id_trait_method;
|
||||
use clippy_utils::usage::is_todo_unimplemented_stub;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
|
||||
use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
|
||||
use rustc_hir::{
|
||||
Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, Node,
|
||||
TraitItem, YieldSource,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -81,11 +85,8 @@ impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
|
|||
|
||||
let is_async_block = matches!(
|
||||
ex.kind,
|
||||
ExprKind::Closure(rustc_hir::Closure {
|
||||
kind: rustc_hir::ClosureKind::Coroutine(rustc_hir::CoroutineKind::Desugared(
|
||||
rustc_hir::CoroutineDesugaring::Async,
|
||||
_
|
||||
)),
|
||||
ExprKind::Closure(Closure {
|
||||
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
|
||||
..
|
||||
})
|
||||
);
|
||||
|
|
@ -120,6 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
|
|||
&& fn_kind.asyncness().is_async()
|
||||
&& !is_def_id_trait_method(cx, def_id)
|
||||
&& !is_default_trait_impl(cx, def_id)
|
||||
&& !async_fn_contains_todo_unimplemented_macro(cx, body)
|
||||
{
|
||||
let mut visitor = AsyncFnVisitor {
|
||||
cx,
|
||||
|
|
@ -203,3 +205,18 @@ fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool {
|
||||
if let ExprKind::Closure(closure) = body.value.kind
|
||||
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
|
||||
&& let body = cx.tcx.hir_body(closure.body)
|
||||
&& let ExprKind::Block(block, _) = body.value.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& let ExprKind::DropTemps(inner) = expr.kind
|
||||
{
|
||||
return is_todo_unimplemented_stub(cx, inner);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::usage::is_todo_unimplemented_stub;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use rustc_hir::{Body, Impl, ImplItem, ImplItemKind, ItemKind};
|
||||
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -60,18 +58,6 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
|
|||
let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
|
||||
let parent_item = cx.tcx.hir_expect_item(parent);
|
||||
let assoc_item = cx.tcx.associated_item(impl_item.owner_id);
|
||||
let contains_todo = |cx, body: &'_ Body<'_>| -> bool {
|
||||
clippy_utils::visitors::for_each_expr_without_closures(body.value, |e| {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, e)
|
||||
&& cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id)
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
};
|
||||
if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind
|
||||
&& assoc_item.is_method()
|
||||
&& let ImplItemKind::Fn(.., body_id) = &impl_item.kind
|
||||
|
|
@ -79,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
|
|||
&& let body = cx.tcx.hir_body(*body_id)
|
||||
&& let [self_param, ..] = body.params
|
||||
&& !is_local_used(cx, body, self_param.pat.hir_id)
|
||||
&& !contains_todo(cx, body)
|
||||
&& !is_todo_unimplemented_stub(cx, body.value)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use clippy_utils::source::snippet_opt;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Item, ItemKind, UseKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext as _};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Visibility;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::kw;
|
||||
|
|
@ -59,7 +59,7 @@ impl_lint_pass!(UnusedTraitNames => [UNUSED_TRAIT_NAMES]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if !item.span.in_external_macro(cx.sess().source_map())
|
||||
if !item.span.from_expansion()
|
||||
&& let ItemKind::Use(path, UseKind::Single(ident)) = item.kind
|
||||
// Ignore imports that already use Underscore
|
||||
&& ident.name != kw::Underscore
|
||||
|
|
|
|||
|
|
@ -176,6 +176,33 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
}
|
||||
},
|
||||
|
||||
ExprKind::MethodCall(path, recv, [arg], _) => {
|
||||
if matches!(
|
||||
path.ident.name,
|
||||
sym::map | sym::map_err | sym::map_break | sym::map_continue
|
||||
) && has_eligible_receiver(cx, recv, e)
|
||||
&& (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From))
|
||||
&& let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind()
|
||||
&& let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice()
|
||||
&& same_type_and_consts(from_ty, to_ty)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
USELESS_CONVERSION,
|
||||
e.span.with_lo(recv.span.hi()),
|
||||
format!("useless conversion to the same type: `{from_ty}`"),
|
||||
|diag| {
|
||||
diag.suggest_remove_item(
|
||||
cx,
|
||||
e.span.with_lo(recv.span.hi()),
|
||||
"consider removing",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
ExprKind::MethodCall(name, recv, [], _) => {
|
||||
if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into {
|
||||
let a = cx.typeck_results().expr_ty(e);
|
||||
|
|
@ -412,32 +439,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a
|
||||
/// higher-order mapping function.
|
||||
pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if has_eligible_receiver(cx, recv, expr)
|
||||
&& (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From))
|
||||
&& let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind()
|
||||
&& let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice()
|
||||
&& same_type_and_consts(from_ty, to_ty)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
USELESS_CONVERSION,
|
||||
expr.span.with_lo(recv.span.hi()),
|
||||
format!("useless conversion to the same type: `{from_ty}`"),
|
||||
|diag| {
|
||||
diag.suggest_remove_item(
|
||||
cx,
|
||||
expr.span.with_lo(recv.span.hi()),
|
||||
"consider removing",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool {
|
||||
if is_inherent_method_call(cx, expr) {
|
||||
matches!(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint;
|
|||
use clippy_utils::paths;
|
||||
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
||||
use rustc_ast::{AttrStyle, DelimArgs};
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::{
|
||||
|
|
@ -11,7 +12,6 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_lint_defs::declare_tool_lint;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_tool_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -88,7 +88,10 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown {
|
|||
}
|
||||
|
||||
// Is it derived?
|
||||
if !find_attr!(cx.tcx.get_all_attrs(item.owner_id), AttributeKind::AutomaticallyDerived(..)) {
|
||||
if !find_attr!(
|
||||
cx.tcx.get_all_attrs(item.owner_id),
|
||||
AttributeKind::AutomaticallyDerived(..)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::internal_paths;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::{is_lint_allowed, sym};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -12,9 +12,9 @@ use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
|
|||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_tool_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -160,9 +160,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
|
|||
let body = cx.tcx.hir_body_owned_by(
|
||||
impl_item_refs
|
||||
.iter()
|
||||
.find(|iiref| iiref.ident.as_str() == "lint_vec")
|
||||
.find(|&&iiref| cx.tcx.item_name(iiref.owner_id) == sym::lint_vec)
|
||||
.expect("LintPass needs to implement lint_vec")
|
||||
.id
|
||||
.owner_id
|
||||
.def_id,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::internal_paths;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -40,7 +41,9 @@ impl LateLintPass<'_> for MsrvAttrImpl {
|
|||
.filter(|t| matches!(t.kind(), GenericArgKind::Type(_)))
|
||||
.any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty()))
|
||||
})
|
||||
&& !items.iter().any(|item| item.ident.name.as_str() == "check_attributes")
|
||||
&& !items
|
||||
.iter()
|
||||
.any(|&item| cx.tcx.item_name(item.owner_id) == sym::check_attributes)
|
||||
{
|
||||
let span = cx.sess().source_map().span_through_char(item.span, '{');
|
||||
span_lint_and_sugg(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-07-10
|
||||
nightly-2025-07-25
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
|
|||
{
|
||||
diag.help(format!(
|
||||
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}",
|
||||
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
format!("rust-{}", n.rsplit_once('.').unwrap().1)
|
||||
})
|
||||
&option_env!("RUST_RELEASE_NUM").map_or_else(
|
||||
|| "master".to_string(),
|
||||
|n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
format!("rust-{}", n.rsplit_once('.').unwrap().1)
|
||||
}
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
|
|||
|
||||
use itertools::Itertools;
|
||||
use rustc_abi::Integer;
|
||||
use rustc_ast::join_path_syms;
|
||||
use rustc_ast::ast::{self, LitKind, RangeLimits};
|
||||
use rustc_ast::join_path_syms;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::packed::Pu128;
|
||||
|
|
@ -114,7 +114,7 @@ use rustc_middle::hir::nested_filter;
|
|||
use rustc_middle::hir::place::PlaceBase;
|
||||
use rustc_middle::lint::LevelAndSource;
|
||||
use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, PointerCoercion};
|
||||
use rustc_middle::ty::layout::IntegerExt;
|
||||
use rustc_middle::ty::{
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
|
||||
|
|
@ -1897,6 +1897,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
/// * `|x| { return x }`
|
||||
/// * `|x| { return x; }`
|
||||
/// * `|(x, y)| (x, y)`
|
||||
/// * `|[x, y]| [x, y]`
|
||||
///
|
||||
/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
|
||||
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
|
||||
|
|
@ -1907,9 +1908,9 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
|
|||
.get(pat.hir_id)
|
||||
.is_some_and(|mode| matches!(mode.0, ByRef::Yes(_)))
|
||||
{
|
||||
// If a tuple `(x, y)` is of type `&(i32, i32)`, then due to match ergonomics,
|
||||
// the inner patterns become references. Don't consider this the identity function
|
||||
// as that changes types.
|
||||
// If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then
|
||||
// due to match ergonomics, the inner patterns become references. Don't consider this
|
||||
// the identity function as that changes types.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1922,6 +1923,13 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
|
|||
{
|
||||
pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr))
|
||||
},
|
||||
(PatKind::Slice(before, slice, after), ExprKind::Array(arr))
|
||||
if slice.is_none() && before.len() + after.len() == arr.len() =>
|
||||
{
|
||||
(before.iter().chain(after))
|
||||
.zip(arr)
|
||||
.all(|(pat, expr)| check_pat(cx, pat, expr))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -3269,15 +3277,13 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St
|
|||
|
||||
if go_up_by > max_super {
|
||||
// `super` chain would be too long, just use the absolute path instead
|
||||
join_path_syms(
|
||||
once(kw::Crate).chain(to.data.iter().filter_map(|el| {
|
||||
if let DefPathData::TypeNs(sym) = el.data {
|
||||
Some(sym)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
)
|
||||
join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| {
|
||||
if let DefPathData::TypeNs(sym) = el.data {
|
||||
Some(sym)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
join_path_syms(repeat_n(kw::Super, go_up_by).chain(path))
|
||||
}
|
||||
|
|
@ -3560,3 +3566,14 @@ pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>)
|
|||
// enclosing body.
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks if the expression has adjustments that require coercion, for example: dereferencing with
|
||||
/// overloaded deref, coercing pointers and `dyn` objects.
|
||||
pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
cx.typeck_results().expr_adjustments(expr).iter().any(|adj| {
|
||||
matches!(
|
||||
adj.kind,
|
||||
Adjust::Deref(Some(_)) | Adjust::Pointer(PointerCoercion::Unsize) | Adjust::NeverToAny
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,10 +308,11 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n
|
|||
None
|
||||
}
|
||||
}),
|
||||
ItemKind::Impl(..) | ItemKind::Trait(..)
|
||||
=> tcx.associated_items(local_id).filter_by_name_unhygienic(name)
|
||||
.find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
|
||||
.map(|assoc_item| assoc_item.def_id),
|
||||
ItemKind::Impl(..) | ItemKind::Trait(..) => tcx
|
||||
.associated_items(local_id)
|
||||
.filter_by_name_unhygienic(name)
|
||||
.find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
|
||||
.map(|assoc_item| assoc_item.def_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ generate! {
|
|||
ceil_char_boundary,
|
||||
chain,
|
||||
chars,
|
||||
check_attributes,
|
||||
checked_abs,
|
||||
checked_add,
|
||||
checked_isqrt,
|
||||
|
|
@ -196,6 +197,7 @@ generate! {
|
|||
kw,
|
||||
last,
|
||||
lazy_static,
|
||||
lint_vec,
|
||||
ln,
|
||||
lock,
|
||||
lock_api,
|
||||
|
|
@ -261,6 +263,7 @@ generate! {
|
|||
read_to_end,
|
||||
read_to_string,
|
||||
read_unaligned,
|
||||
redundant_imports,
|
||||
redundant_pub_crate,
|
||||
regex,
|
||||
rem_euclid,
|
||||
|
|
|
|||
|
|
@ -492,10 +492,7 @@ pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
|
|||
|
||||
/// Returns `true` if the given type is an `unsafe` function.
|
||||
pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => ty.fn_sig(cx.tcx).safety().is_unsafe(),
|
||||
_ => false,
|
||||
}
|
||||
ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe()
|
||||
}
|
||||
|
||||
/// Returns the base type for HIR references and pointers.
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@
|
|||
//! be considered a bug.
|
||||
|
||||
use crate::paths::{PathNS, lookup_path};
|
||||
use rustc_ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty};
|
||||
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind};
|
||||
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, Param, PathSegment, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
|
||||
use rustc_span::Span;
|
||||
|
|
@ -24,22 +25,24 @@ mod certainty;
|
|||
use certainty::{Certainty, Meet, join, meet};
|
||||
|
||||
pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
expr_type_certainty(cx, expr).is_certain()
|
||||
expr_type_certainty(cx, expr, false).is_certain()
|
||||
}
|
||||
|
||||
fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
||||
/// Determine the type certainty of `expr`. `in_arg` indicates that the expression happens within
|
||||
/// the evaluation of a function or method call argument.
|
||||
fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> Certainty {
|
||||
let certainty = match &expr.kind {
|
||||
ExprKind::Unary(_, expr)
|
||||
| ExprKind::Field(expr, _)
|
||||
| ExprKind::Index(expr, _, _)
|
||||
| ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr),
|
||||
| ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr, in_arg),
|
||||
|
||||
ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
|
||||
ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
|
||||
|
||||
ExprKind::Call(callee, args) => {
|
||||
let lhs = expr_type_certainty(cx, callee);
|
||||
let lhs = expr_type_certainty(cx, callee, false);
|
||||
let rhs = if type_is_inferable_from_arguments(cx, expr) {
|
||||
meet(args.iter().map(|arg| expr_type_certainty(cx, arg)))
|
||||
meet(args.iter().map(|arg| expr_type_certainty(cx, arg, true)))
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
};
|
||||
|
|
@ -47,7 +50,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
|||
},
|
||||
|
||||
ExprKind::MethodCall(method, receiver, args, _) => {
|
||||
let mut receiver_type_certainty = expr_type_certainty(cx, receiver);
|
||||
let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false);
|
||||
// Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
|
||||
// identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
|
||||
// for example. So update the `DefId` in `receiver_type_certainty` (if any).
|
||||
|
|
@ -59,7 +62,8 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
|||
let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
|
||||
let rhs = if type_is_inferable_from_arguments(cx, expr) {
|
||||
meet(
|
||||
std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))),
|
||||
std::iter::once(receiver_type_certainty)
|
||||
.chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))),
|
||||
)
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
|
|
@ -67,16 +71,39 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
|||
lhs.join(rhs)
|
||||
},
|
||||
|
||||
ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
|
||||
ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
|
||||
|
||||
ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)),
|
||||
ExprKind::Binary(_, lhs, rhs) => {
|
||||
// If one of the side of the expression is uncertain, the certainty will come from the other side,
|
||||
// with no information on the type.
|
||||
match (
|
||||
expr_type_certainty(cx, lhs, in_arg),
|
||||
expr_type_certainty(cx, rhs, in_arg),
|
||||
) {
|
||||
(Certainty::Uncertain, Certainty::Certain(_)) | (Certainty::Certain(_), Certainty::Uncertain) => {
|
||||
Certainty::Certain(None)
|
||||
},
|
||||
(l, r) => l.meet(r),
|
||||
}
|
||||
},
|
||||
|
||||
ExprKind::Lit(_) => Certainty::Certain(None),
|
||||
ExprKind::Lit(lit) => {
|
||||
if !in_arg
|
||||
&& matches!(
|
||||
lit.node,
|
||||
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
|
||||
)
|
||||
{
|
||||
Certainty::Uncertain
|
||||
} else {
|
||||
Certainty::Certain(None)
|
||||
}
|
||||
},
|
||||
|
||||
ExprKind::Cast(_, ty) => type_certainty(cx, ty),
|
||||
|
||||
ExprKind::If(_, if_expr, Some(else_expr)) => {
|
||||
expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr))
|
||||
expr_type_certainty(cx, if_expr, in_arg).join(expr_type_certainty(cx, else_expr, in_arg))
|
||||
},
|
||||
|
||||
ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false),
|
||||
|
|
@ -188,6 +215,20 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo
|
|||
certainty
|
||||
}
|
||||
|
||||
/// Tries to tell whether `param` resolves to something certain, e.g., a non-wildcard type if
|
||||
/// present. The certainty `DefId` is cleared before returning.
|
||||
fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty {
|
||||
let owner_did = cx.tcx.hir_enclosing_body_owner(param.hir_id);
|
||||
let Some(fn_decl) = cx.tcx.hir_fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(owner_did)) else {
|
||||
return Certainty::Uncertain;
|
||||
};
|
||||
let inputs = fn_decl.inputs;
|
||||
let body_params = cx.tcx.hir_body_owned_by(owner_did).params;
|
||||
std::iter::zip(body_params, inputs)
|
||||
.find(|(p, _)| p.hir_id == param.hir_id)
|
||||
.map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id())
|
||||
}
|
||||
|
||||
fn path_segment_certainty(
|
||||
cx: &LateContext<'_>,
|
||||
parent_certainty: Certainty,
|
||||
|
|
@ -240,15 +281,16 @@ fn path_segment_certainty(
|
|||
|
||||
// `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
|
||||
Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) {
|
||||
// An argument's type is always certain.
|
||||
Node::Param(..) => Certainty::Certain(None),
|
||||
// A parameter's type is not always certain, as it may come from an untyped closure definition,
|
||||
// or from a wildcard in a typed closure definition.
|
||||
Node::Param(param) => param_certainty(cx, param),
|
||||
// A local's type is certain if its type annotation is certain or it has an initializer whose
|
||||
// type is certain.
|
||||
Node::LetStmt(local) => {
|
||||
let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty));
|
||||
let rhs = local
|
||||
.init
|
||||
.map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init));
|
||||
.map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init, false));
|
||||
let certainty = lhs.join(rhs);
|
||||
if resolves_to_type {
|
||||
certainty
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::macros::root_macro_call_first_node;
|
||||
use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures};
|
||||
use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
|
||||
use core::ops::ControlFlow;
|
||||
|
|
@ -9,6 +10,7 @@ use rustc_lint::LateContext;
|
|||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
|
||||
/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
|
||||
pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
|
||||
|
|
@ -140,6 +142,46 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`.
|
||||
pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
root_macro_call_first_node(cx, expr).is_some_and(|macro_call| {
|
||||
[sym::todo_macro, sym::unimplemented_macro]
|
||||
.iter()
|
||||
.any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id))
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression,
|
||||
/// or a block whose last expression is a `todo!()` or `unimplemented!()`.
|
||||
pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Block(block, _) = expr.kind {
|
||||
if let Some(last_expr) = block.expr {
|
||||
return is_todo_unimplemented_macro(cx, last_expr);
|
||||
}
|
||||
|
||||
return block.stmts.last().is_some_and(|stmt| {
|
||||
if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind {
|
||||
return is_todo_unimplemented_macro(cx, expr);
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
is_todo_unimplemented_macro(cx, expr)
|
||||
}
|
||||
|
||||
/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`.
|
||||
pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
|
||||
for_each_expr_without_closures(expr, |e| {
|
||||
if is_todo_unimplemented_macro(cx, e) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
|
||||
for_each_expr_without_closures(expression, |e| {
|
||||
match e.kind {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[toolchain]
|
||||
# begin autogenerated nightly
|
||||
channel = "nightly-2025-07-10"
|
||||
channel = "nightly-2025-07-25"
|
||||
# end autogenerated nightly
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
profile = "minimal"
|
||||
|
|
|
|||
|
|
@ -157,7 +157,8 @@ pub fn get_commit_date() -> Option<String> {
|
|||
|
||||
#[must_use]
|
||||
pub fn get_compiler_version() -> Option<String> {
|
||||
get_output("rustc", &["-V"])
|
||||
let compiler = std::option_env!("RUSTC").unwrap_or("rustc");
|
||||
get_output(compiler, &["-V"])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -172,6 +173,8 @@ pub fn get_channel(compiler_version: Option<String>) -> String {
|
|||
return String::from("beta");
|
||||
} else if rustc_output.contains("nightly") {
|
||||
return String::from("nightly");
|
||||
} else if rustc_output.contains("dev") {
|
||||
return String::from("dev");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,6 +162,10 @@ impl TestContext {
|
|||
// however has some staging logic that is hurting us here, so to work around
|
||||
// that we set both the "real" and "staging" rustc to TEST_RUSTC, including the
|
||||
// associated library paths.
|
||||
#[expect(
|
||||
clippy::option_env_unwrap,
|
||||
reason = "TEST_RUSTC will ensure that the requested env vars are set during compile time"
|
||||
)]
|
||||
if let Some(rustc) = option_env!("TEST_RUSTC") {
|
||||
let libdir = option_env!("TEST_RUSTC_LIB").unwrap();
|
||||
let sysroot = option_env!("TEST_SYSROOT").unwrap();
|
||||
|
|
@ -169,10 +173,7 @@ impl TestContext {
|
|||
p.envs.push(("RUSTC_REAL_LIBDIR".into(), Some(libdir.into())));
|
||||
p.envs.push(("RUSTC_SNAPSHOT".into(), Some(rustc.into())));
|
||||
p.envs.push(("RUSTC_SNAPSHOT_LIBDIR".into(), Some(libdir.into())));
|
||||
p.envs.push((
|
||||
"RUSTC_SYSROOT".into(),
|
||||
Some(sysroot.into()),
|
||||
));
|
||||
p.envs.push(("RUSTC_SYSROOT".into(), Some(sysroot.into())));
|
||||
}
|
||||
p
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is
|
|||
|
|
||||
LL | sleep(Duration::new(1, 0));
|
||||
| ^^^^^
|
||||
|
|
||||
= note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ LL | | }
|
|||
|
|
||||
= note: `-D clippy::large-enum-variant` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]`
|
||||
help: consider boxing the large fields to reduce the total size of the enum
|
||||
help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum
|
||||
|
|
||||
LL - B([u8; 501]),
|
||||
LL + B(Box<[u8; 501]>),
|
||||
|
|
|
|||
|
|
@ -106,4 +106,19 @@ fn main() {
|
|||
//~^ approx_constant
|
||||
|
||||
let no_tau = 6.3;
|
||||
|
||||
// issue #15194
|
||||
#[allow(clippy::excessive_precision)]
|
||||
let x: f64 = 3.1415926535897932384626433832;
|
||||
//~^ approx_constant
|
||||
|
||||
#[allow(clippy::excessive_precision)]
|
||||
let _: f64 = 003.14159265358979311599796346854418516159057617187500;
|
||||
//~^ approx_constant
|
||||
|
||||
let almost_frac_1_sqrt_2 = 00.70711;
|
||||
//~^ approx_constant
|
||||
|
||||
let almost_frac_1_sqrt_2 = 00.707_11;
|
||||
//~^ approx_constant
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,5 +184,37 @@ LL | let almost_tau = 6.28;
|
|||
|
|
||||
= help: consider using the constant directly
|
||||
|
||||
error: aborting due to 23 previous errors
|
||||
error: approximate value of `f{32, 64}::consts::PI` found
|
||||
--> tests/ui/approx_const.rs:112:18
|
||||
|
|
||||
LL | let x: f64 = 3.1415926535897932384626433832;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider using the constant directly
|
||||
|
||||
error: approximate value of `f{32, 64}::consts::PI` found
|
||||
--> tests/ui/approx_const.rs:116:18
|
||||
|
|
||||
LL | let _: f64 = 003.14159265358979311599796346854418516159057617187500;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider using the constant directly
|
||||
|
||||
error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found
|
||||
--> tests/ui/approx_const.rs:119:32
|
||||
|
|
||||
LL | let almost_frac_1_sqrt_2 = 00.70711;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: consider using the constant directly
|
||||
|
||||
error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found
|
||||
--> tests/ui/approx_const.rs:122:32
|
||||
|
|
||||
LL | let almost_frac_1_sqrt_2 = 00.707_11;
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= help: consider using the constant directly
|
||||
|
||||
error: aborting due to 27 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ LL | let _ = Arc::new(RefCell::new(42));
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `Arc<RefCell<i32>>` is not `Send` and `Sync` as `RefCell<i32>` is not `Sync`
|
||||
= help: if the `Arc` will not used be across threads replace it with an `Rc`
|
||||
= help: if the `Arc` will not be used across threads replace it with an `Rc`
|
||||
= help: otherwise make `RefCell<i32>` `Send` and `Sync` or consider a wrapper type such as `Mutex`
|
||||
= note: `-D clippy::arc-with-non-send-sync` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::arc_with_non_send_sync)]`
|
||||
|
|
@ -17,7 +17,7 @@ LL | let _ = Arc::new(mutex.lock().unwrap());
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `Arc<MutexGuard<'_, i32>>` is not `Send` and `Sync` as `MutexGuard<'_, i32>` is not `Send`
|
||||
= help: if the `Arc` will not used be across threads replace it with an `Rc`
|
||||
= help: if the `Arc` will not be used across threads replace it with an `Rc`
|
||||
= help: otherwise make `MutexGuard<'_, i32>` `Send` and `Sync` or consider a wrapper type such as `Mutex`
|
||||
|
||||
error: usage of an `Arc` that is not `Send` and `Sync`
|
||||
|
|
@ -27,7 +27,7 @@ LL | let _ = Arc::new(&42 as *const i32);
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `Arc<*const i32>` is not `Send` and `Sync` as `*const i32` is neither `Send` nor `Sync`
|
||||
= help: if the `Arc` will not used be across threads replace it with an `Rc`
|
||||
= help: if the `Arc` will not be used across threads replace it with an `Rc`
|
||||
= help: otherwise make `*const i32` `Send` and `Sync` or consider a wrapper type such as `Mutex`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
|
|
|||
|
|
@ -664,6 +664,20 @@ pub fn issue_12318() {
|
|||
//~^ arithmetic_side_effects
|
||||
}
|
||||
|
||||
pub fn issue_15225() {
|
||||
use core::num::{NonZero, NonZeroU8};
|
||||
|
||||
let one = const { NonZeroU8::new(1).unwrap() };
|
||||
let _ = one.get() - 1;
|
||||
|
||||
let one: NonZero<u8> = const { NonZero::new(1).unwrap() };
|
||||
let _ = one.get() - 1;
|
||||
|
||||
type AliasedType = u8;
|
||||
let one: NonZero<AliasedType> = const { NonZero::new(1).unwrap() };
|
||||
let _ = one.get() - 1;
|
||||
}
|
||||
|
||||
pub fn explicit_methods() {
|
||||
use core::ops::Add;
|
||||
let one: i32 = 1;
|
||||
|
|
|
|||
|
|
@ -758,13 +758,13 @@ LL | one.sub_assign(1);
|
|||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||
--> tests/ui/arithmetic_side_effects.rs:670:5
|
||||
--> tests/ui/arithmetic_side_effects.rs:684:5
|
||||
|
|
||||
LL | one.add(&one);
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||
--> tests/ui/arithmetic_side_effects.rs:672:5
|
||||
--> tests/ui/arithmetic_side_effects.rs:686:5
|
||||
|
|
||||
LL | Box::new(one).add(one);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ mod issue14871 {
|
|||
const ONE: Self;
|
||||
}
|
||||
|
||||
#[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet)
|
||||
pub const trait NumberConstants {
|
||||
fn constant(value: usize) -> Self;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ mod issue14871 {
|
|||
const ONE: Self;
|
||||
}
|
||||
|
||||
#[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet)
|
||||
pub const trait NumberConstants {
|
||||
fn constant(value: usize) -> Self;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ impl _ExternalStruct {
|
|||
pub fn _foo(self) {}
|
||||
}
|
||||
|
||||
pub fn _exernal_foo() {}
|
||||
pub fn _external_foo() {}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ pub const fn issue_8898(i: u32) -> bool {
|
|||
#[clippy::msrv = "1.33"]
|
||||
fn msrv_1_33() {
|
||||
let value: i64 = 33;
|
||||
let _ = value <= (u32::MAX as i64) && value >= 0;
|
||||
let _ = value <= (u32::max_value() as i64) && value >= 0;
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.34"]
|
||||
|
|
|
|||
|
|
@ -95,13 +95,13 @@ pub const fn issue_8898(i: u32) -> bool {
|
|||
#[clippy::msrv = "1.33"]
|
||||
fn msrv_1_33() {
|
||||
let value: i64 = 33;
|
||||
let _ = value <= (u32::MAX as i64) && value >= 0;
|
||||
let _ = value <= (u32::max_value() as i64) && value >= 0;
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.34"]
|
||||
fn msrv_1_34() {
|
||||
let value: i64 = 34;
|
||||
let _ = value <= (u32::MAX as i64) && value >= 0;
|
||||
let _ = value <= (u32::max_value() as i64) && value >= 0;
|
||||
//~^ checked_conversions
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ LL | let _ = value <= u16::MAX as u32 && value as i32 == 5;
|
|||
error: checked cast can be simplified
|
||||
--> tests/ui/checked_conversions.rs:104:13
|
||||
|
|
||||
LL | let _ = value <= (u32::MAX as i64) && value >= 0;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
|
||||
LL | let _ = value <= (u32::max_value() as i64) && value >= 0;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()`
|
||||
|
||||
error: aborting due to 17 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,26 @@ fn expect_result() {
|
|||
//~^ expect_used
|
||||
}
|
||||
|
||||
#[allow(clippy::ok_expect)]
|
||||
#[allow(clippy::err_expect)]
|
||||
fn issue_15247() {
|
||||
let x: Result<u8, u8> = Err(0);
|
||||
x.ok().expect("Huh");
|
||||
//~^ expect_used
|
||||
|
||||
{ x.ok() }.expect("...");
|
||||
//~^ expect_used
|
||||
|
||||
let y: Result<u8, u8> = Ok(0);
|
||||
y.err().expect("Huh");
|
||||
//~^ expect_used
|
||||
|
||||
{ y.err() }.expect("...");
|
||||
//~^ expect_used
|
||||
}
|
||||
|
||||
fn main() {
|
||||
expect_option();
|
||||
expect_result();
|
||||
issue_15247();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,5 +24,37 @@ LL | let _ = res.expect_err("");
|
|||
|
|
||||
= note: if this value is an `Ok`, it will panic
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: used `expect()` on an `Option` value
|
||||
--> tests/ui/expect.rs:23:5
|
||||
|
|
||||
LL | x.ok().expect("Huh");
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: if this value is `None`, it will panic
|
||||
|
||||
error: used `expect()` on an `Option` value
|
||||
--> tests/ui/expect.rs:26:5
|
||||
|
|
||||
LL | { x.ok() }.expect("...");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: if this value is `None`, it will panic
|
||||
|
||||
error: used `expect()` on an `Option` value
|
||||
--> tests/ui/expect.rs:30:5
|
||||
|
|
||||
LL | y.err().expect("Huh");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: if this value is `None`, it will panic
|
||||
|
||||
error: used `expect()` on an `Option` value
|
||||
--> tests/ui/expect.rs:33:5
|
||||
|
|
||||
LL | { y.err() }.expect("...");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: if this value is `None`, it will panic
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -90,17 +90,30 @@ fn main() {
|
|||
"foo"
|
||||
}
|
||||
|
||||
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
|
||||
const fn const_evaluable() -> &'static str {
|
||||
"foo"
|
||||
}
|
||||
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
|
||||
//~^ expect_fun_call
|
||||
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
|
||||
//~^ expect_fun_call
|
||||
Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", get_string()));
|
||||
//~^ expect_fun_call
|
||||
|
||||
Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) });
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", get_static_str()));
|
||||
//~^ expect_fun_call
|
||||
Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) });
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", get_non_static_str(&0)));
|
||||
//~^ expect_fun_call
|
||||
|
||||
Some("foo").unwrap_or_else(|| panic!("{}", const_evaluable()));
|
||||
//~^ expect_fun_call
|
||||
|
||||
const {
|
||||
Some("foo").expect(const_evaluable());
|
||||
}
|
||||
|
||||
Some("foo").expect(const { const_evaluable() });
|
||||
}
|
||||
|
||||
//Issue #3839
|
||||
|
|
@ -122,4 +135,15 @@ fn main() {
|
|||
let format_capture_and_value: Option<i32> = None;
|
||||
format_capture_and_value.unwrap_or_else(|| panic!("{error_code}, {}", 1));
|
||||
//~^ expect_fun_call
|
||||
|
||||
// Issue #15056
|
||||
let a = false;
|
||||
Some(5).expect(if a { "a" } else { "b" });
|
||||
|
||||
let return_in_expect: Option<i32> = None;
|
||||
return_in_expect.expect(if true {
|
||||
"Error"
|
||||
} else {
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@ fn main() {
|
|||
"foo"
|
||||
}
|
||||
|
||||
const fn const_evaluable() -> &'static str {
|
||||
"foo"
|
||||
}
|
||||
|
||||
Some("foo").expect(&get_string());
|
||||
//~^ expect_fun_call
|
||||
Some("foo").expect(get_string().as_ref());
|
||||
|
|
@ -101,6 +105,15 @@ fn main() {
|
|||
//~^ expect_fun_call
|
||||
Some("foo").expect(get_non_static_str(&0));
|
||||
//~^ expect_fun_call
|
||||
|
||||
Some("foo").expect(const_evaluable());
|
||||
//~^ expect_fun_call
|
||||
|
||||
const {
|
||||
Some("foo").expect(const_evaluable());
|
||||
}
|
||||
|
||||
Some("foo").expect(const { const_evaluable() });
|
||||
}
|
||||
|
||||
//Issue #3839
|
||||
|
|
@ -122,4 +135,15 @@ fn main() {
|
|||
let format_capture_and_value: Option<i32> = None;
|
||||
format_capture_and_value.expect(&format!("{error_code}, {}", 1));
|
||||
//~^ expect_fun_call
|
||||
|
||||
// Issue #15056
|
||||
let a = false;
|
||||
Some(5).expect(if a { "a" } else { "b" });
|
||||
|
||||
let return_in_expect: Option<i32> = None;
|
||||
return_in_expect.expect(if true {
|
||||
"Error"
|
||||
} else {
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,59 +37,65 @@ error: function call inside of `expect`
|
|||
LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:93:21
|
||||
|
|
||||
LL | Some("foo").expect(&get_string());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:95:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_ref());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:97:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
LL | Some("foo").expect(&get_string());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:100:21
|
||||
--> tests/ui/expect_fun_call.rs:99:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_ref());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:101:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:104:21
|
||||
|
|
||||
LL | Some("foo").expect(get_static_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_static_str()))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:102:21
|
||||
--> tests/ui/expect_fun_call.rs:106:21
|
||||
|
|
||||
LL | Some("foo").expect(get_non_static_str(&0));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_non_static_str(&0)))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:107:16
|
||||
--> tests/ui/expect_fun_call.rs:109:21
|
||||
|
|
||||
LL | Some("foo").expect(const_evaluable());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", const_evaluable()))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:120:16
|
||||
|
|
||||
LL | Some(true).expect(&format!("key {}, {}", 1, 2));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:114:17
|
||||
--> tests/ui/expect_fun_call.rs:127:17
|
||||
|
|
||||
LL | opt_ref.expect(&format!("{:?}", opt_ref));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{:?}", opt_ref))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:119:20
|
||||
--> tests/ui/expect_fun_call.rs:132:20
|
||||
|
|
||||
LL | format_capture.expect(&format!("{error_code}"));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}"))`
|
||||
|
||||
error: function call inside of `expect`
|
||||
--> tests/ui/expect_fun_call.rs:123:30
|
||||
--> tests/ui/expect_fun_call.rs:136:30
|
||||
|
|
||||
LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))`
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
error: aborting due to 16 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -89,3 +89,24 @@ fn issue11503() {
|
|||
let _: Vec<usize> = bools.iter().enumerate().filter(|&(i, b)| ****b).map(|(i, b)| i).collect();
|
||||
//~^ filter_map_bool_then
|
||||
}
|
||||
|
||||
fn issue15047() {
|
||||
#[derive(Clone, Copy)]
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
macro_rules! foo {
|
||||
($e:expr) => {
|
||||
$e + 1
|
||||
};
|
||||
}
|
||||
|
||||
let x = 1;
|
||||
let _ = [(MyEnum::A, "foo", 1i32)]
|
||||
.iter()
|
||||
.filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x));
|
||||
//~^ filter_map_bool_then
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,3 +89,24 @@ fn issue11503() {
|
|||
let _: Vec<usize> = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect();
|
||||
//~^ filter_map_bool_then
|
||||
}
|
||||
|
||||
fn issue15047() {
|
||||
#[derive(Clone, Copy)]
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
macro_rules! foo {
|
||||
($e:expr) => {
|
||||
$e + 1
|
||||
};
|
||||
}
|
||||
|
||||
let x = 1;
|
||||
let _ = [(MyEnum::A, "foo", 1i32)]
|
||||
.iter()
|
||||
.filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x)));
|
||||
//~^ filter_map_bool_then
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,5 +61,11 @@ error: usage of `bool::then` in `filter_map`
|
|||
LL | let _: Vec<usize> = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| ****b).map(|(i, b)| i)`
|
||||
|
||||
error: aborting due to 10 previous errors
|
||||
error: usage of `bool::then` in `filter_map`
|
||||
--> tests/ui/filter_map_bool_then.rs:110:10
|
||||
|
|
||||
LL | .filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x)));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x))`
|
||||
|
||||
error: aborting due to 11 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -16,3 +16,16 @@ fn main() {
|
|||
let _ = iterator.flatten();
|
||||
//~^ flat_map_identity
|
||||
}
|
||||
|
||||
fn issue15198() {
|
||||
let x = [[1, 2], [3, 4]];
|
||||
// don't lint: this is an `Iterator<Item = &[i32, i32]>`
|
||||
// match ergonomics makes the binding patterns into references
|
||||
// so that its type changes to `Iterator<Item = [&i32, &i32]>`
|
||||
let _ = x.iter().flat_map(|[x, y]| [x, y]);
|
||||
let _ = x.iter().flat_map(|x| [x[0]]);
|
||||
|
||||
// no match ergonomics for `[i32, i32]`
|
||||
let _ = x.iter().copied().flatten();
|
||||
//~^ flat_map_identity
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,3 +16,16 @@ fn main() {
|
|||
let _ = iterator.flat_map(|x| return x);
|
||||
//~^ flat_map_identity
|
||||
}
|
||||
|
||||
fn issue15198() {
|
||||
let x = [[1, 2], [3, 4]];
|
||||
// don't lint: this is an `Iterator<Item = &[i32, i32]>`
|
||||
// match ergonomics makes the binding patterns into references
|
||||
// so that its type changes to `Iterator<Item = [&i32, &i32]>`
|
||||
let _ = x.iter().flat_map(|[x, y]| [x, y]);
|
||||
let _ = x.iter().flat_map(|x| [x[0]]);
|
||||
|
||||
// no match ergonomics for `[i32, i32]`
|
||||
let _ = x.iter().copied().flat_map(|[x, y]| [x, y]);
|
||||
//~^ flat_map_identity
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,5 +19,11 @@ error: use of `flat_map` with an identity function
|
|||
LL | let _ = iterator.flat_map(|x| return x);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: use of `flat_map` with an identity function
|
||||
--> tests/ui/flat_map_identity.rs:29:31
|
||||
|
|
||||
LL | let _ = x.iter().copied().flat_map(|[x, y]| [x, y]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()`
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
|
|
|||
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