Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions book/src/development/adding_lints.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,10 @@ if !self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) {
}
```

An important consideration is that `Msrv::meets` is relatively expensive to
call, so you should typically match the MSRV at the end of an if let chain,
after other short-circuiting checks.
Comment on lines +488 to +490

@Jarcho Jarcho Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only partially correct. Msrv::meets only slow if the crate has a clippy::msrv attribute which is becoming less common ever since Cargo.toml got the msrv field. This slowness could even be mostly fixed if it were an unavoidable issue.

The biggest issue with it is that it's just not a good filter. The MSRV being set to whatever the current version is very common and makes this filter out nothing. Even for (actively developed) crates which set an MSRV, it's not usually more than a couple years old. This not only makes most checks not filter anything out, but the total number of ineffective filters grows over time.

View changes since the review


Early lint passes should instead use `MsrvStack` coupled with
`extract_msrv_attr!()`

Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/attrs/deprecated_cfg_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) {
{
// check for `rustfmt`
if feature_item.has_name(sym::rustfmt)
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
// check for `rustfmt_skip` and `rustfmt::skip`
&& let Some(skip_item) = &items[1].meta_item()
&& (skip_item.has_name(sym::rustfmt_skip)
Expand All @@ -30,6 +29,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) {
// Only lint outer attributes, because custom inner attributes are unstable
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
&& attr.style == AttrStyle::Outer
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
{
span_lint_and_sugg(
cx,
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/casts/ptr_as_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ pub(super) fn check<'tcx>(
// The `U` in `pointer::cast` have to be `Sized`
// as explained here: https://github.com/rust-lang/rust/issues/60602.
&& to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
&& !is_from_proc_macro(cx, expr)
&& msrv.meets(cx, msrvs::POINTER_CAST)
&& !is_from_proc_macro(cx, expr)
{
let mut app = Applicability::MachineApplicable;
let turbofish = match &cast_to_hir.kind {
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/inefficient_to_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_
&& let self_ty = args.type_at(0)
&& let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty)
&& deref_count >= 1
&& specializes_tostring(cx, deref_self_ty)
// Since Rust 1.82, the specialized `ToString` is properly called
&& !msrv.meets(cx, msrvs::SPECIALIZED_TO_STRING_FOR_REFS)
Comment on lines +22 to 24

@Jarcho Jarcho Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't touch this one. The msrv check is doing the opposite of what we normally do which undermines a large part of the argument for pushing the check later.

View changes since the review

&& specializes_tostring(cx, deref_self_ty)
{
span_lint_and_then(
cx,
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/manual_is_variant_and.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ pub(super) fn check_is_some_is_none<'tcx>(
.expr_ty_adjusted(recv)
.peel_refs()
.is_diag_item(cx, sym::Option)
&& (is_some || msrv.meets(cx, msrvs::IS_NONE_OR))
&& let Ok(map_func) = MapFunc::try_from(arg)
&& (is_some || msrv.meets(cx, msrvs::IS_NONE_OR))
{
let method = if is_some { "is_some_and" } else { "is_none_or" };
let lint_span = recv.span.to(call_span);
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/manual_try_fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pub(super) fn check<'tcx>(
&& let ExprKind::Path(qpath) = path.kind
&& let Res::Def(DefKind::Ctor(_, _), _) = cx.qpath_res(&qpath, path.hir_id)
&& let ExprKind::Closure(closure) = acc.kind
&& let Some(args_snip) = closure.fn_arg_span.and_then(|fn_arg_span| fn_arg_span.get_text(cx))
&& msrv.meets(cx, msrvs::ITERATOR_TRY_FOLD)
&& !is_from_proc_macro(cx, expr)
Comment on lines +32 to 34

@Jarcho Jarcho Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't move this one. Taking a snippet for a suggestions should happen after the everything else.

View changes since the review

&& let Some(args_snip) = closure.fn_arg_span.and_then(|fn_arg_span| fn_arg_span.get_text(cx))
{
let init_snip = rest
.is_empty()
Expand Down
9 changes: 4 additions & 5 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5728,9 +5728,9 @@ impl Methods {
no_effect_replace::check(cx, expr, arg1, arg2);

// Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint
if self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY)
&& name == sym::replace
if name == sym::replace
&& let Some((sym::replace, ..)) = method_call(recv)
&& self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY)
{
collapsible_str_replace::check(cx, expr, arg1, arg2);
}
Expand Down Expand Up @@ -5801,10 +5801,9 @@ impl Methods {
},
(sym::take, []) => needless_option_take::check(cx, expr, recv),
(sym::then, [arg]) => {
if !self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
return;
if self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some", true);
}
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some", true);
},
(sym::try_into, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::TryInto) => {
unnecessary_fallible_conversions::check_method(cx, expr);
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/methods/string_lit_chars_any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ pub(super) fn check<'tcx>(
(false, true) => lhs,
_ => return,
}
&& let Some(scrutinee_snip) = scrutinee.span.get_text(cx)
&& msrv.meets(cx, msrvs::MATCHES_MACRO)
&& !is_from_proc_macro(cx, expr)
Comment on lines +36 to 38

@Jarcho Jarcho Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't move this one as it's a snippet.

View changes since the review

&& let Some(scrutinee_snip) = scrutinee.span.get_text(cx)
{
// Normalize the char using `map` so `join` doesn't use `Display`, if we don't then
// something like `r"\"` will become `'\'`, which is of course invalid
Expand Down
4 changes: 2 additions & 2 deletions clippy_lints/src/operators/manual_is_multiple_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ pub(super) fn check<'tcx>(
rhs: &'tcx Expr<'tcx>,
msrv: Msrv,
) {
if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF)
&& let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs)
if let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs)
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
&& operand_op.node == BinOpKind::Rem
&& matches!(
Expand All @@ -32,6 +31,7 @@ pub(super) fn check<'tcx>(
ty::Uint(_)
)
&& expr_type_is_certain(cx, operand_left)
&& msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF)
{
let mut app = Applicability::MachineApplicable;
let divisor = deref_sugg(
Expand Down
4 changes: 2 additions & 2 deletions clippy_lints/src/unnested_or_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ impl EarlyLintPass for UnnestedOrPatterns {
}

fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if self.msrv.meets(msrvs::OR_PATTERNS)
&& let ast::ExprKind::Let(pat, _, _, _) = &e.kind
if let ast::ExprKind::Let(pat, _, _, _) = &e.kind
&& self.msrv.meets(msrvs::OR_PATTERNS)
{
lint_unnested_or_patterns(cx, pat);
}
Expand Down