Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
759c2bf
Assume that any external function might return a type alias
samueltardieu Jan 16, 2026
d0c6a64
Fix `redundant_iter_cloned` false positive with move closures and cor…
lapla-cogito Feb 1, 2026
91de07d
Extend `iter_kv_map` to cover `flat_map` and `filter_map`
profetia Feb 5, 2026
0df2894
Apply `iter_kv_map` to Clippy itself
profetia Feb 5, 2026
559f1c3
Replace a stale clippy `CURRENT_RUSTC_VERSION`
cuviper Feb 6, 2026
99e4722
Stabilize assert_matches
Voultapher Jan 23, 2026
ae2d679
Fix clippy ast utils
mu001999 Jan 28, 2026
c8fd55b
Merge commit 'a62c6af53676bb15a40488ce2d632de558f001de' into clippy-s…
flip1995 Feb 12, 2026
0f4f81d
Make `iter_kv_map` to cover `flat_map` and `filter_map` (#16519)
dswij Feb 13, 2026
43eb3b9
fix: `RustcCallbacks::config()` in `clippy-driver`
devjgm Feb 13, 2026
2737b26
Fix the compile-test tests when setting Cargo's `build.build-dir` set…
jakubadamw Feb 14, 2026
6c04e46
Port #[rustc_test_marker] to the attribute parser
Ozzy1423 Feb 13, 2026
ad7979b
Assume that any external function might return a type alias (#16415)
dswij Feb 14, 2026
18a4108
Fix the compile-test tests when setting Cargo's `build.build-dir` set…
samueltardieu Feb 14, 2026
ec1e165
Provide all lint group names to Clippy
Alexendoo Feb 14, 2026
845936f
Rollup merge of #152188 - cuviper:placeholder-stdarch, r=Mark-Simulacrum
jhpratt Feb 15, 2026
9d850a0
Rollup merge of #152625 - Alexendoo:lint-group-names, r=Kivooeo
JonathanBrouwer Feb 15, 2026
87c404e
fix: `RustcCallbacks::config()` in `clippy-driver` (#16562)
Alexendoo Feb 16, 2026
32d32b6
adjust clippy to fix some of the issues
RalfJung Nov 9, 2025
d5643a0
Rollup merge of #151783 - mu001999-contrib:impl/final-method, r=fee1-…
Zalathar Feb 17, 2026
1750703
Remove ShallowInitBox.
cjgillot Oct 15, 2025
bd21958
Remove `env` macro use from ui tests.
Jarcho Feb 17, 2026
24cccf9
Fix `redundant_iter_cloned` false positive with move closures and cor…
Jarcho Feb 17, 2026
e299e18
Remove `env` macro use from ui tests. (#16580)
samueltardieu Feb 17, 2026
b8e86e6
Unify wording of resolve error
estebank Aug 12, 2025
1994230
fix(str_to_string): false positive non-str types
nyurik Feb 15, 2026
899fa02
Rollup merge of #152758 - cjgillot:noinit-box, r=RalfJung
Zalathar Feb 18, 2026
b0c4cba
Fix stale metadata output comment in compile-test
szokeasaurusrex Feb 18, 2026
52a32d6
Fix stale metadata output comment in compile-test (#16583)
llogiq Feb 18, 2026
a25baf6
fix(str_to_string): false positive non-str types (#16571)
samueltardieu Feb 19, 2026
e96a83b
Merge remote-tracking branch 'upstream/master' into rustup
flip1995 Feb 19, 2026
650809e
Bump nightly version -> 2026-02-19
flip1995 Feb 19, 2026
e63814d
Rustup (#16588)
flip1995 Feb 19, 2026
9d06e5d
Merge commit 'e63814d6d197a14bc9f31eea6f9e94a746bdd50a' into clippy-s…
flip1995 Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 14 additions & 27 deletions src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::res::MaybeResPath;
use clippy_utils::res::MaybeResPath as _;
use clippy_utils::source::{SpanRangeExt, snippet_opt};
use clippy_utils::visitors::{Visitable, for_each_expr_without_closures};
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias};
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_hir::{Expr, ExprKind, FnRetTy, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
Expand Down Expand Up @@ -97,7 +97,7 @@ pub(super) fn check<'tcx>(

// skip cast of fn call that returns type alias
if let ExprKind::Cast(inner, ..) = expr.kind
&& is_cast_from_ty_alias(cx, inner, cast_from)
&& is_cast_from_ty_alias(cx, inner)
{
return false;
}
Expand Down Expand Up @@ -270,42 +270,33 @@ fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {

/// Finds whether an `Expr` returns a type alias.
///
/// TODO: Maybe we should move this to `clippy_utils` so others won't need to go down this dark,
/// dark path reimplementing this (or something similar).
fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx>, cast_from: Ty<'tcx>) -> bool {
/// When in doubt, for example because it calls a non-local function that we don't have the
/// declaration for, assume if might be a type alias.
fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx>) -> bool {
for_each_expr_without_closures(expr, |expr| {
// Calls are a `Path`, and usage of locals are a `Path`. So, this checks
// - call() as i32
// - local as i32
if let ExprKind::Path(qpath) = expr.kind {
let res = cx.qpath_res(&qpath, expr.hir_id);
// Function call
if let Res::Def(DefKind::Fn, def_id) = res {
let Some(snippet) = cx.tcx.def_span(def_id).get_source_text(cx) else {
return ControlFlow::Continue(());
let Some(def_id) = def_id.as_local() else {
// External function, we can't know, better be safe
return ControlFlow::Break(());
};
// This is the worst part of this entire function. This is the only way I know of to
// check whether a function returns a type alias. Sure, you can get the return type
// from a function in the current crate as an hir ty, but how do you get it for
// external functions?? Simple: It's impossible. So, we check whether a part of the
// function's declaration snippet is exactly equal to the `Ty`. That way, we can
// see whether it's a type alias.
//
// FIXME: This won't work if the type is given an alias through `use`, should we
// consider this a type alias as well?
if !snippet
.split("->")
.skip(1)
.any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from)))
if let Some(FnRetTy::Return(ty)) = cx.tcx.hir_get_fn_output(def_id)
&& let TyKind::Path(qpath) = ty.kind
&& is_ty_alias(&qpath)
{
// Function call to a local function returning a type alias
return ControlFlow::Break(());
}
// Local usage
} else if let Res::Local(hir_id) = res
&& let Node::LetStmt(l) = cx.tcx.parent_hir_node(hir_id)
{
if let Some(e) = l.init
&& is_cast_from_ty_alias(cx, e, cast_from)
&& is_cast_from_ty_alias(cx, e)
{
return ControlFlow::Break::<()>(());
}
Expand All @@ -323,7 +314,3 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx
})
.is_some()
}

fn snippet_eq_ty(snippet: &str, ty: Ty<'_>) -> bool {
snippet.trim() == ty.to_string() || snippet.trim().contains(&format!("::{ty}"))
}
4 changes: 2 additions & 2 deletions src/tools/clippy/clippy_lints/src/macro_metavars_in_unsafe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
// We want to lint unsafe blocks #0 and #1
let bad_unsafe_blocks = self
.metavar_expns
.iter()
.filter_map(|(_, state)| match state {
.values()
.filter_map(|state| match state {
MetavarState::ReferencedInUnsafe { unsafe_blocks } => Some(unsafe_blocks.as_slice()),
MetavarState::ReferencedInSafe => None,
})
Expand Down
3 changes: 2 additions & 1 deletion src/tools/clippy/clippy_lints/src/methods/iter_kv_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub(super) fn check<'tcx>(
recv: &'tcx Expr<'tcx>, // hashmap
m_arg: &'tcx Expr<'tcx>, // |(_, v)| v
msrv: Msrv,
method_name: Symbol,
) {
if map_type == sym::into_iter && !msrv.meets(cx, msrvs::INTO_KEYS) {
return;
Expand Down Expand Up @@ -67,7 +68,7 @@ pub(super) fn check<'tcx>(
format!("iterating on a map's {replacement_kind}s"),
"try",
format!(
"{recv_snippet}.{into_prefix}{replacement_kind}s().map(|{}{bound_ident}| {body_snippet})",
"{recv_snippet}.{into_prefix}{replacement_kind}s().{method_name}(|{}{bound_ident}| {body_snippet})",
annotation.prefix_str(),
),
applicability,
Expand Down
39 changes: 37 additions & 2 deletions src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::visitors::for_each_expr_without_closures;
use core::ops::ControlFlow;
use rustc_ast::BindingMode;
use rustc_errors::Applicability;
use rustc_hir::{Body, Expr, ExprKind, HirId, HirIdSet, PatKind};
use rustc_hir::{Body, CaptureBy, Closure, Expr, ExprKind, HirId, HirIdSet, Param, PatKind};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_lint::LateContext;
use rustc_middle::mir::{FakeReadCause, Mutability};
use rustc_middle::ty::{self, BorrowKind};
use rustc_middle::ty::{self, BorrowKind, UpvarCapture};
use rustc_span::{Symbol, sym};

use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED};
Expand Down Expand Up @@ -64,6 +66,11 @@ pub(super) fn check<'tcx>(
let body @ Body { params: [p], .. } = cx.tcx.hir_body(closure.body) else {
return;
};

if param_captured_by_move_block(cx, body.value, p) {
return;
}

let mut delegate = MoveDelegate {
used_move: HirIdSet::default(),
};
Expand Down Expand Up @@ -140,6 +147,34 @@ struct MoveDelegate {
used_move: HirIdSet,
}

/// Checks if the expression contains a closure or coroutine with `move` capture semantics that
/// captures the given parameter.
fn param_captured_by_move_block(cx: &LateContext<'_>, expr: &Expr<'_>, param: &Param<'_>) -> bool {
let mut param_hir_ids = HirIdSet::default();
param.pat.walk(|pat| {
param_hir_ids.insert(pat.hir_id);
true
});

for_each_expr_without_closures(expr, |e| {
if let ExprKind::Closure(Closure {
capture_clause: CaptureBy::Value { .. },
def_id,
..
}) = e.kind
&& cx.tcx.closure_captures(*def_id).iter().any(|capture| {
matches!(capture.info.capture_kind, UpvarCapture::ByValue)
&& matches!(capture.place.base, PlaceBase::Upvar(upvar) if param_hir_ids.contains(&upvar.var_path.hir_id))
})
{
return ControlFlow::Break(());
}

ControlFlow::Continue(())
})
.is_some()
}

impl<'tcx> Delegate<'tcx> for MoveDelegate {
fn consume(&mut self, place_with_id: &PlaceWithHirId<'tcx>, _: HirId) {
if let PlaceBase::Local(l) = place_with_id.place.base {
Expand Down
8 changes: 7 additions & 1 deletion src/tools/clippy/clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5275,6 +5275,9 @@ impl Methods {
call_span,
self.msrv,
);
if let Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) = method_call(recv) {
iter_kv_map::check(cx, map_name, expr, recv2, arg, self.msrv, sym::filter_map);
}
},
(sym::find_map, [arg]) => {
unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap);
Expand All @@ -5285,6 +5288,9 @@ impl Methods {
lines_filter_map_ok::check_filter_or_flat_map(
cx, expr, recv, "flat_map", arg, call_span, self.msrv,
);
if let Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) = method_call(recv) {
iter_kv_map::check(cx, map_name, expr, recv2, arg, self.msrv, sym::flat_map);
}
},
(sym::flatten, []) => {
match method_call(recv) {
Expand Down Expand Up @@ -5383,7 +5389,7 @@ impl Methods {
manual_is_variant_and::check_map(cx, expr);
match method_call(recv) {
Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) => {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv);
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv, sym::map);
},
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
Expand Down
4 changes: 3 additions & 1 deletion src/tools/clippy/clippy_lints/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ impl<'tcx> LateLintPass<'tcx> for StrToString {
&& args.iter().any(|a| a.hir_id == expr.hir_id)
&& let Res::Def(DefKind::AssocFn, def_id) = expr.res(cx)
&& cx.tcx.is_diagnostic_item(sym::to_string_method, def_id)
&& let Some(args) = cx.typeck_results().node_args_opt(expr.hir_id)
&& args.type_at(0).is_str()
{
// Detected `ToString::to_string` passed as an argument (generic: any call or method call)
span_lint_and_sugg(
Expand All @@ -425,7 +427,7 @@ impl<'tcx> LateLintPass<'tcx> for StrToString {
expr.span,
"`ToString::to_string` used as `&str` to `String` converter",
"try",
"ToOwned::to_owned".to_string(),
"str::to_owned".to_string(),
Applicability::MachineApplicable,
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:

<!-- begin autogenerated nightly -->
```
nightly-2026-02-11
nightly-2026-02-19
```
<!-- end autogenerated nightly -->

Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/clippy_utils/src/ast_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,8 +820,8 @@ pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
matches!(
(l, r),
(Defaultness::Implicit, Defaultness::Implicit)
| (Defaultness::Default(_), Defaultness::Default(_))
| (Defaultness::Final(_), Defaultness::Final(_))
| (Defaultness::Default(_), Defaultness::Default(_))
| (Defaultness::Final(_), Defaultness::Final(_))
)
}

Expand Down
9 changes: 4 additions & 5 deletions src/tools/clippy/clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2340,12 +2340,11 @@ fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalModDefId, f: impl FnOnce(&
&& let item = tcx.hir_item(id)
&& let ItemKind::Const(ident, _generics, ty, _body) = item.kind
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
// We could also check for the type name `test::TestDescAndFn`
&& let Res::Def(DefKind::Struct, _) = path.res
// We could also check for the type name `test::TestDescAndFn`
&& let Res::Def(DefKind::Struct, _) = path.res
&& find_attr!(tcx.hir_attrs(item.hir_id()), AttributeKind::RustcTestMarker(..))
{
if find_attr!(tcx.hir_attrs(item.hir_id()), AttributeKind::RustcTestMarker(..)) {
names.push(ident.name);
}
names.push(ident.name);
}
}
names.sort_unstable();
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2026-02-11"
channel = "nightly-2026-02-19"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"
1 change: 1 addition & 0 deletions src/tools/clippy/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl rustc_driver::Callbacks for RustcCallbacks {
config.psess_created = Some(Box::new(move |psess| {
track_clippy_args(psess, clippy_args_var.as_deref());
}));
config.extra_symbols = sym::EXTRA_SYMBOLS.into();
}
}

Expand Down
7 changes: 2 additions & 5 deletions src/tools/clippy/tests/compile-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,6 @@ impl TestContext {
defaults.set_custom("diagnostic-collector", collector);
}
config.with_args(&self.args);
let current_exe_path = env::current_exe().unwrap();
let deps_path = current_exe_path.parent().unwrap();
let profile_path = deps_path.parent().unwrap();

config.program.args.extend(
[
"--emit=metadata",
Expand All @@ -224,6 +220,7 @@ impl TestContext {
config.program.args.push(format!("--sysroot={sysroot}").into());
}

let profile_path = target_dir.join(env!("PROFILE"));
config.program.program = profile_path.join(if cfg!(windows) {
"clippy-driver.exe"
} else {
Expand Down Expand Up @@ -469,7 +466,7 @@ enum DiagnosticOrMessage {
}

/// Collects applicabilities from the diagnostics produced for each UI test, producing the
/// `util/gh-pages/lints.json` file used by <https://rust-lang.github.io/rust-clippy/>
/// `util/gh-pages/index.html` file used by <https://rust-lang.github.io/rust-clippy/>
#[derive(Debug, Clone)]
struct DiagnosticCollector {
sender: Sender<Vec<u8>>,
Expand Down
10 changes: 8 additions & 2 deletions src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ fn issue16322(item: String) {
}

fn issue16458() {
macro_rules! m {
() => {
""
};
}

macro_rules! partly_comes_from_macro {
($i:ident: $ty:ty, $def:expr) => {
let _ = {
Expand All @@ -125,7 +131,7 @@ fn issue16458() {
}

partly_comes_from_macro! {
required_version: String, env!("HOME").to_string()
required_version: String, m!().to_string()
}

macro_rules! all_comes_from_macro {
Expand All @@ -141,6 +147,6 @@ fn issue16458() {
};
}
all_comes_from_macro! {
required_version: String, env!("HOME").to_string();
required_version: String, m!().to_string();
}
}
10 changes: 8 additions & 2 deletions src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ fn issue16322(item: String) {
}

fn issue16458() {
macro_rules! m {
() => {
""
};
}

macro_rules! partly_comes_from_macro {
($i:ident: $ty:ty, $def:expr) => {
let _ = {
Expand All @@ -125,7 +131,7 @@ fn issue16458() {
}

partly_comes_from_macro! {
required_version: String, env!("HOME").to_string()
required_version: String, m!().to_string()
}

macro_rules! all_comes_from_macro {
Expand All @@ -141,6 +147,6 @@ fn issue16458() {
};
}
all_comes_from_macro! {
required_version: String, env!("HOME").to_string();
required_version: String, m!().to_string();
}
}
4 changes: 2 additions & 2 deletions src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ LL | if item == t!(frohes_neu_Jahr).to_string() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t!(frohes_neu_Jahr)`

error: this creates an owned instance just for comparison
--> tests/ui/cmp_owned/with_suggestion.rs:135:51
--> tests/ui/cmp_owned/with_suggestion.rs:141:51
|
LL | let res = <$ty>::default() == "$def".to_string();
| ^^^^^^^^^^^^^^^^^^ help: try: `"$def"`
...
LL | / all_comes_from_macro! {
LL | | required_version: String, env!("HOME").to_string();
LL | | required_version: String, m!().to_string();
LL | | }
| |_____- in this macro invocation
|
Expand Down
Loading
Loading