Auto merge of #144449 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`
This commit is contained in:
bors 2025-07-25 19:19:45 +00:00
commit 430d6eddfc
167 changed files with 3825 additions and 1159 deletions

View file

@ -19,6 +19,7 @@ const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
"too_many_arguments",
"needless_lifetimes", // people want to keep the lifetimes
"wrong_self_convention",
"approx_constant", // libcore is what defines those
];
fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {

View file

@ -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"

View file

@ -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

View file

@ -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;

View file

@ -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
}
}

View file

@ -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,
}
}

View file

@ -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`"
));

View file

@ -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;

View file

@ -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()

View file

@ -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,
);
}
}
}

View file

@ -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);

View file

@ -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,
);
}
}
}

View file

@ -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()
}

View file

@ -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)

View file

@ -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,

View file

@ -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(());
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

@ -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 {

View file

@ -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))

View file

@ -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);

View file

@ -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
};

View file

@ -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};

View file

@ -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,
);
});
}

View file

@ -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()

View file

@ -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)
)
})
})
}

View file

@ -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,
);
}
}
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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)
{

View file

@ -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()

View file

@ -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,

View file

@ -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);

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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))
}
}

View file

@ -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

View file

@ -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),
);

View file

@ -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,
);
}

View file

@ -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");

View file

@ -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', '(']);

View file

@ -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,

View file

@ -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(())
});

View file

@ -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;

View file

@ -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()
}

View file

@ -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();

View file

@ -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(..) => {

View file

@ -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()

View file

@ -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}; }}"),
}
}
);

View file

@ -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,
);

View file

@ -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;
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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))
},
_ => {

View file

@ -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 {

View file

@ -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"));
},
);
}

View file

@ -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
}

View file

@ -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,

View file

@ -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

View file

@ -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!(

View file

@ -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;
}

View file

@ -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,
);

View file

@ -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(

View file

@ -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 -->

View file

@ -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)
}
)
));
}
}

View file

@ -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
)
})
}

View file

@ -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,
}
}

View file

@ -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,

View file

@ -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.

View file

@ -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

View file

@ -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 {

View file

@ -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"

View file

@ -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");
}
}

View file

@ -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
},

View file

@ -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

View file

@ -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]>),

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);
| ^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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;
}

View file

@ -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;
}

View file

@ -4,4 +4,4 @@ impl _ExternalStruct {
pub fn _foo(self) {}
}
pub fn _exernal_foo() {}
pub fn _external_foo() {}

View file

@ -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"]

View file

@ -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
}

View file

@ -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

View file

@ -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();
}

View file

@ -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

View file

@ -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;
});
}

View file

@ -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;
});
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

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