Merge commit '4f142aa1058f14f153f8bfd2d82f04ddb9982388' into clippyup

This commit is contained in:
flip1995 2022-10-23 15:18:45 +02:00
parent 2ed404937f
commit cd0bb7de01
284 changed files with 8555 additions and 4250 deletions

View file

@ -25,6 +25,7 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
jobs:
base:

View file

@ -11,6 +11,7 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
defaults:
run:

View file

@ -15,6 +15,8 @@ on:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_UNSTABLE_SPARSE_REGISTRY: true
jobs:
clippy_dev:

View file

@ -3735,6 +3735,7 @@ Released 2018-09-13
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
[`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut
[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
[`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states
@ -3772,6 +3773,7 @@ Released 2018-09-13
[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
[`cast_nan_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_nan_to_int
[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
@ -3988,6 +3990,7 @@ Released 2018-09-13
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
@ -4046,6 +4049,7 @@ Released 2018-09-13
[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
[`missing_trait_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_trait_methods
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
[`mixed_read_write_in_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_read_write_in_expression
@ -4131,6 +4135,7 @@ Released 2018-09-13
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
[`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
[`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
@ -4312,6 +4317,7 @@ Released 2018-09-13
[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable

View file

@ -29,7 +29,7 @@ All contributors are expected to follow the [Rust Code of Conduct].
## The Clippy book
If you're new to Clippy and don't know where to start the [Clippy book] includes
If you're new to Clippy and don't know where to start, the [Clippy book] includes
a [developer guide] and is a good place to start your journey.
[Clippy book]: https://doc.rust-lang.org/nightly/clippy/index.html

View file

@ -478,8 +478,27 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
```
Once the `msrv` is added to the lint, a relevant test case should be added to
`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted
if the project's MSRV is lower.
the lint's test file, `tests/ui/manual_strip.rs` in this example. It should
have a case for the version below the MSRV and one with the same contents but
for the MSRV version itself.
```rust
#![feature(custom_inner_attributes)]
...
fn msrv_1_44() {
#![clippy::msrv = "1.44"]
/* something that would trigger the lint */
}
fn msrv_1_45() {
#![clippy::msrv = "1.45"]
/* something that would trigger the lint */
}
```
As a last step, the lint should be added to the lint documentation. This is done
in `clippy_lints/src/utils/conf.rs`:

View file

@ -69,7 +69,7 @@ the reference file with:
cargo dev bless
```
For example, this is necessary, if you fix a typo in an error message of a lint
For example, this is necessary if you fix a typo in an error message of a lint,
or if you modify a test file to add a test case.
> _Note:_ This command may update more files than you intended. In that case
@ -101,8 +101,9 @@ cargo dev setup intellij
cargo dev dogfood
```
More about intellij command usage and reasons
[here](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust)
More about [intellij] command usage and reasons.
[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust
## lintcheck

View file

@ -49,7 +49,7 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
.into_iter()
.flatten()
.flatten()
.map(|entry| mtime(&entry.path()))
.map(|entry| mtime(entry.path()))
.max()
.unwrap_or(SystemTime::UNIX_EPOCH)
} else {

View file

@ -36,9 +36,8 @@ impl ClippyProjectInfo {
}
pub fn setup_rustc_src(rustc_path: &str) {
let rustc_source_dir = match check_and_get_rustc_dir(rustc_path) {
Ok(path) => path,
Err(_) => return,
let Ok(rustc_source_dir) = check_and_get_rustc_dir(rustc_path) else {
return
};
for project in CLIPPY_PROJECTS {
@ -172,14 +171,10 @@ pub fn remove_rustc_src() {
}
fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool {
let mut cargo_content = if let Ok(content) = read_project_file(project.cargo_file) {
content
} else {
let Ok(mut cargo_content) = read_project_file(project.cargo_file) else {
return false;
};
let section_start = if let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) {
section_start
} else {
let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) else {
println!(
"info: dependencies could not be found in `{}` for {}, skipping file",
project.cargo_file, project.name
@ -187,9 +182,7 @@ fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool {
return true;
};
let end_point = if let Some(end_point) = cargo_content.find(DEPENDENCIES_SECTION) {
end_point
} else {
let Some(end_point) = cargo_content.find(DEPENDENCIES_SECTION) else {
eprintln!(
"error: the end of the rustc dependencies section could not be found in `{}`",
project.cargo_file

View file

@ -128,7 +128,7 @@ fn generate_lint_files(
for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
let content = gen_lint_group_list(&lint_group, lints.iter());
process_file(
&format!("clippy_lints/src/lib.register_{lint_group}.rs"),
format!("clippy_lints/src/lib.register_{lint_group}.rs"),
update_mode,
&content,
);
@ -869,13 +869,11 @@ fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
macro_rules! match_tokens {
($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
{
$($(let $capture =)? if let Some(LintDeclSearchResult {
$(#[allow(clippy::redundant_pattern)] let Some(LintDeclSearchResult {
token_kind: TokenKind::$token $({$($fields)*})?,
content: _x,
content: $($capture @)? _,
..
}) = $iter.next() {
_x
} else {
}) = $iter.next() else {
continue;
};)*
#[allow(clippy::unused_unit)]

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -483,7 +483,9 @@ impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]))
cx.tcx
.get_diagnostic_item(sym::Ord)
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
}
struct NotSimplificationVisitor<'a, 'tcx> {

View file

@ -1,5 +1,12 @@
use clippy_utils::{diagnostics::span_lint_and_help, is_default_equivalent, path_def_id};
use rustc_hir::{Expr, ExprKind, QPath};
use clippy_utils::{
diagnostics::span_lint_and_sugg, get_parent_node, is_default_equivalent, macros::macro_backtrace, match_path,
path_def_id, paths, ty::expr_sig,
};
use rustc_errors::Applicability;
use rustc_hir::{
intravisit::{walk_ty, Visitor},
Block, Expr, ExprKind, Local, Node, QPath, TyKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -15,12 +22,6 @@ declare_clippy_lint! {
/// Second, `Box::default()` can be faster
/// [in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box).
///
/// ### Known problems
/// The lint may miss some cases (e.g. Box::new(String::from(""))).
/// On the other hand, it will trigger on cases where the `default`
/// code comes from a macro that does something different based on
/// e.g. target operating system.
///
/// ### Example
/// ```rust
/// let x: Box<String> = Box::new(Default::default());
@ -41,21 +42,88 @@ impl LateLintPass<'_> for BoxDefault {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Call(box_new, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind
&& let ExprKind::Call(..) = arg.kind
&& let ExprKind::Call(arg_path, ..) = arg.kind
&& !in_external_macro(cx.sess(), expr.span)
&& expr.span.eq_ctxt(arg.span)
&& (expr.span.eq_ctxt(arg.span) || is_vec_expn(cx, arg))
&& seg.ident.name == sym::new
&& path_def_id(cx, ty) == cx.tcx.lang_items().owned_box()
&& path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box())
&& is_default_equivalent(cx, arg)
{
span_lint_and_help(
let arg_ty = cx.typeck_results().expr_ty(arg);
span_lint_and_sugg(
cx,
BOX_DEFAULT,
expr.span,
"`Box::new(_)` of default value",
None,
"use `Box::default()` instead",
"try",
if is_plain_default(arg_path) || given_type(cx, expr) {
"Box::default()".into()
} else {
format!("Box::<{arg_ty}>::default()")
},
Applicability::MachineApplicable
);
}
}
}
fn is_plain_default(arg_path: &Expr<'_>) -> bool {
// we need to match the actual path so we don't match e.g. "u8::default"
if let ExprKind::Path(QPath::Resolved(None, path)) = &arg_path.kind {
// avoid generic parameters
match_path(path, &paths::DEFAULT_TRAIT_METHOD) && path.segments.iter().all(|seg| seg.args.is_none())
} else {
false
}
}
fn is_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
macro_backtrace(expr.span)
.next()
.map_or(false, |call| cx.tcx.is_diagnostic_item(sym::vec_macro, call.def_id))
}
#[derive(Default)]
struct InferVisitor(bool);
impl<'tcx> Visitor<'tcx> for InferVisitor {
fn visit_ty(&mut self, t: &rustc_hir::Ty<'_>) {
self.0 |= matches!(t.kind, TyKind::Infer | TyKind::OpaqueDef(..) | TyKind::TraitObject(..));
if !self.0 {
walk_ty(self, t);
}
}
}
fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match get_parent_node(cx.tcx, expr.hir_id) {
Some(Node::Local(Local { ty: Some(ty), .. })) => {
let mut v = InferVisitor::default();
v.visit_ty(ty);
!v.0
},
Some(
Node::Expr(Expr {
kind: ExprKind::Call(path, args),
..
}) | Node::Block(Block {
expr:
Some(Expr {
kind: ExprKind::Call(path, args),
..
}),
..
}),
) => {
if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) &&
let Some(sig) = expr_sig(cx, path) &&
let Some(input) = sig.input(index)
{
input.no_bound_vars().is_some()
} else {
false
}
},
_ => false,
}
}

View file

@ -0,0 +1,38 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::{
mir::Mutability,
ty::{self, Ty, TypeAndMut},
};
use super::AS_PTR_CAST_MUT;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>) {
if let ty::RawPtr(ptrty @ TypeAndMut { mutbl: Mutability::Mut, .. }) = cast_to.kind()
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Not, .. }) =
cx.typeck_results().node_type(cast_expr.hir_id).kind()
&& let ExprKind::MethodCall(method_name, receiver, [], _) = cast_expr.peel_blocks().kind
&& method_name.ident.name == rustc_span::sym::as_ptr
&& let Some(as_ptr_did) = cx.typeck_results().type_dependent_def_id(cast_expr.peel_blocks().hir_id)
&& let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did)
&& let Some(first_param_ty) = as_ptr_sig.skip_binder().inputs().iter().next()
&& let ty::Ref(_, _, Mutability::Not) = first_param_ty.kind()
&& let Some(recv) = snippet_opt(cx, receiver.span)
{
// `as_mut_ptr` might not exist
let applicability = Applicability::MaybeIncorrect;
span_lint_and_sugg(
cx,
AS_PTR_CAST_MUT,
expr.span,
&format!("casting the result of `as_ptr` to *{ptrty}"),
"replace with",
format!("{recv}.as_mut_ptr()"),
applicability
);
}
}

View file

@ -0,0 +1,28 @@
use super::CAST_NAN_TO_INT;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_note;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, from_ty: Ty<'_>, to_ty: Ty<'_>) {
if from_ty.is_floating_point() && to_ty.is_integral() && is_known_nan(cx, cast_expr) {
span_lint_and_note(
cx,
CAST_NAN_TO_INT,
expr.span,
&format!("casting a known NaN to {to_ty}"),
None,
"this always evaluates to 0",
);
}
}
fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), e) {
Some((Constant::F64(n), _)) => n.is_nan(),
Some((Constant::F32(n), _)) => n.is_nan(),
_ => false,
}
}

View file

@ -1,8 +1,10 @@
mod as_ptr_cast_mut;
mod as_underscore;
mod borrow_as_ptr;
mod cast_abs_to_unsigned;
mod cast_enum_constructor;
mod cast_lossless;
mod cast_nan_to_int;
mod cast_possible_truncation;
mod cast_possible_wrap;
mod cast_precision_loss;
@ -569,6 +571,7 @@ declare_clippy_lint! {
pedantic,
"borrowing just to cast to a raw pointer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for a raw slice being cast to a slice pointer
@ -596,6 +599,54 @@ declare_clippy_lint! {
"casting a slice created from a pointer and length to a slice pointer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer
///
/// ### Why is this bad?
/// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior
/// mutability is used, making it unlikely that having it as a mutable pointer is correct.
///
/// ### Example
/// ```rust
/// let string = String::with_capacity(1);
/// let ptr = string.as_ptr() as *mut u8;
/// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR
/// ```
/// Use instead:
/// ```rust
/// let mut string = String::with_capacity(1);
/// let ptr = string.as_mut_ptr();
/// unsafe { ptr.write(4) };
/// ```
#[clippy::version = "1.66.0"]
pub AS_PTR_CAST_MUT,
nursery,
"casting the result of the `&self`-taking `as_ptr` to a mutabe pointer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for a known NaN float being cast to an integer
///
/// ### Why is this bad?
/// NaNs are cast into zero, so one could simply use this and make the
/// code more readable. The lint could also hint at a programmer error.
///
/// ### Example
/// ```rust,ignore
/// let _: (0.0_f32 / 0.0) as u64;
/// ```
/// Use instead:
/// ```rust,ignore
/// let _: = 0_u64;
/// ```
#[clippy::version = "1.64.0"]
pub CAST_NAN_TO_INT,
suspicious,
"casting a known floating-point NaN into an integer"
}
pub struct Casts {
msrv: Option<RustcVersion>,
}
@ -627,7 +678,9 @@ impl_lint_pass!(Casts => [
CAST_ABS_TO_UNSIGNED,
AS_UNDERSCORE,
BORROW_AS_PTR,
CAST_SLICE_FROM_RAW_PARTS
CAST_SLICE_FROM_RAW_PARTS,
AS_PTR_CAST_MUT,
CAST_NAN_TO_INT,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -653,6 +706,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
return;
}
cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
@ -664,6 +718,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_precision_loss::check(cx, expr, cast_from, cast_to);
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
cast_nan_to_int::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);

View file

@ -59,9 +59,6 @@ pub(super) fn check<'tcx>(
lint_unnecessary_cast(cx, expr, literal_str, cast_from, cast_to);
return false;
},
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {
return false;
},
LitKind::Int(_, LitIntType::Signed(_) | LitIntType::Unsigned(_))
| LitKind::Float(_, LitFloatType::Suffixed(_))
if cast_from.kind() == cast_to.kind() =>

View file

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, if_sequence, in_constant, is_else_clause, paths, SpanlessEq};
use clippy_utils::{if_sequence, in_constant, is_else_clause, SpanlessEq};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -106,7 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
// Check that the type being compared implements `core::cmp::Ord`
let ty = cx.typeck_results().expr_ty(lhs1);
let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]));
let is_ord = cx
.tcx
.get_diagnostic_item(sym::Ord)
.map_or(false, |id| implements_trait(cx, ty, id, &[]));
if !is_ord {
return;

View file

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::numeric_literal;
use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_node, numeric_literal};
use if_chain::if_chain;
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{
intravisit::{walk_expr, walk_stmt, Visitor},
Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
Body, Expr, ExprKind, HirId, ItemKind, Lit, Node, Stmt, StmtKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::{
@ -55,22 +55,31 @@ declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
let mut visitor = NumericFallbackVisitor::new(cx);
let is_parent_const = if let Some(Node::Item(item)) = get_parent_node(cx.tcx, body.id().hir_id) {
matches!(item.kind, ItemKind::Const(..))
} else {
false
};
let mut visitor = NumericFallbackVisitor::new(cx, is_parent_const);
visitor.visit_body(body);
}
}
struct NumericFallbackVisitor<'a, 'tcx> {
/// Stack manages type bound of exprs. The top element holds current expr type.
ty_bounds: Vec<TyBound<'tcx>>,
ty_bounds: Vec<ExplicitTyBound>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
fn new(cx: &'a LateContext<'tcx>, is_parent_const: bool) -> Self {
Self {
ty_bounds: vec![TyBound::Nothing],
ty_bounds: vec![if is_parent_const {
ExplicitTyBound(true)
} else {
ExplicitTyBound(false)
}],
cx,
}
}
@ -79,10 +88,9 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) {
if_chain! {
if !in_external_macro(self.cx.sess(), lit.span);
if let Some(ty_bound) = self.ty_bounds.last();
if matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)));
if matches!(lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
if !ty_bound.is_numeric();
then {
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
@ -123,7 +131,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) {
// Push found arg type, then visit arg.
self.ty_bounds.push(TyBound::Ty(*bound));
self.ty_bounds.push((*bound).into());
self.visit_expr(expr);
self.ty_bounds.pop();
}
@ -135,7 +143,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
for (expr, bound) in iter::zip(std::iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) {
self.ty_bounds.push(TyBound::Ty(*bound));
self.ty_bounds.push((*bound).into());
self.visit_expr(expr);
self.ty_bounds.pop();
}
@ -169,7 +177,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(TyBound::Nothing);
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();
}
@ -192,15 +200,10 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
match stmt.kind {
StmtKind::Local(local) => {
if local.ty.is_some() {
self.ty_bounds.push(TyBound::Any);
} else {
self.ty_bounds.push(TyBound::Nothing);
}
},
// we cannot check the exact type since it's a hir::Ty which does not implement `is_numeric`
StmtKind::Local(local) => self.ty_bounds.push(ExplicitTyBound(local.ty.is_some())),
_ => self.ty_bounds.push(TyBound::Nothing),
_ => self.ty_bounds.push(ExplicitTyBound(false)),
}
walk_stmt(self, stmt);
@ -218,28 +221,18 @@ fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'
}
}
/// Wrapper around a `bool` to make the meaning of the value clearer
#[derive(Debug, Clone, Copy)]
enum TyBound<'tcx> {
Any,
Ty(Ty<'tcx>),
Nothing,
}
struct ExplicitTyBound(pub bool);
impl<'tcx> TyBound<'tcx> {
fn is_numeric(self) -> bool {
match self {
TyBound::Any => true,
TyBound::Ty(t) => t.is_numeric(),
TyBound::Nothing => false,
}
impl<'tcx> From<Ty<'tcx>> for ExplicitTyBound {
fn from(v: Ty<'tcx>) -> Self {
Self(v.is_numeric())
}
}
impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> {
impl<'tcx> From<Option<Ty<'tcx>>> for ExplicitTyBound {
fn from(v: Option<Ty<'tcx>>) -> Self {
match v {
Some(t) => TyBound::Ty(t),
None => TyBound::Nothing,
}
Self(v.map_or(false, Ty::is_numeric))
}
}

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
@ -11,13 +12,16 @@ use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{
self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy,
GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp,
self as hir,
def_id::{DefId, LocalDefId},
BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem,
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
TraitItemKind, TyKind, UnOp,
};
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::{Rvalue, StatementKind};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{
self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
@ -141,7 +145,7 @@ declare_clippy_lint! {
"dereferencing when the compiler would automatically dereference"
}
impl_lint_pass!(Dereferencing => [
impl_lint_pass!(Dereferencing<'_> => [
EXPLICIT_DEREF_METHODS,
NEEDLESS_BORROW,
REF_BINDING_TO_REFERENCE,
@ -149,7 +153,7 @@ impl_lint_pass!(Dereferencing => [
]);
#[derive(Default)]
pub struct Dereferencing {
pub struct Dereferencing<'tcx> {
state: Option<(State, StateData)>,
// While parsing a `deref` method call in ufcs form, the path to the function is itself an
@ -170,11 +174,16 @@ pub struct Dereferencing {
/// e.g. `m!(x) | Foo::Bar(ref x)`
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
/// be moved.
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
// `IntoIterator` for arrays requires Rust 1.53.
msrv: Option<RustcVersion>,
}
impl Dereferencing {
impl<'tcx> Dereferencing<'tcx> {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self {
@ -244,7 +253,7 @@ struct RefPat {
hir_id: HirId,
}
impl<'tcx> LateLintPass<'tcx> for Dereferencing {
impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
#[expect(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Skip path expressions from deref calls. e.g. `Deref::deref(e)`
@ -278,7 +287,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
match (self.state.take(), kind) {
(None, kind) => {
let expr_ty = typeck.expr_ty(expr);
let (position, adjustments) = walk_parents(cx, expr, self.msrv);
let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv);
match kind {
RefOp::Deref => {
if let Position::FieldAccess {
@ -550,6 +559,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
}
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
}) {
self.possible_borrowers.pop();
}
if Some(body.id()) == self.current_body {
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
let replacements = pat.replacements;
@ -682,6 +697,7 @@ impl Position {
#[expect(clippy::too_many_lines)]
fn walk_parents<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
e: &'tcx Expr<'_>,
msrv: Option<RustcVersion>,
) -> (Position, &'tcx [Adjustment<'tcx>]) {
@ -796,7 +812,16 @@ fn walk_parents<'tcx>(
Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
None => {
if let ty::Param(param_ty) = ty.skip_binder().kind() {
needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv)
needless_borrow_impl_arg_position(
cx,
possible_borrowers,
parent,
i,
*param_ty,
e,
precedence,
msrv,
)
} else {
ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
.position_for_arg()
@ -843,7 +868,16 @@ fn walk_parents<'tcx>(
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
if let ty::Param(param_ty) = ty.kind() {
needless_borrow_impl_arg_position(cx, parent, i + 1, *param_ty, e, precedence, msrv)
needless_borrow_impl_arg_position(
cx,
possible_borrowers,
parent,
i + 1,
*param_ty,
e,
precedence,
msrv,
)
} else {
ty_auto_deref_stability(
cx,
@ -1017,8 +1051,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
// be moved, but it cannot be.
#[expect(clippy::too_many_arguments)]
fn needless_borrow_impl_arg_position<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
parent: &Expr<'tcx>,
arg_index: usize,
param_ty: ParamTy,
@ -1081,10 +1117,13 @@ fn needless_borrow_impl_arg_position<'tcx>(
// elements are modified each time `check_referent` is called.
let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
let mut check_referent = |referent| {
let mut check_reference_and_referent = |reference, referent| {
let referent_ty = cx.typeck_results().expr_ty(referent);
if !is_copy(cx, referent_ty) {
if !is_copy(cx, referent_ty)
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
{
return false;
}
@ -1125,7 +1164,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
let mut needless_borrow = false;
while let ExprKind::AddrOf(_, _, referent) = expr.kind {
if !check_referent(referent) {
if !check_reference_and_referent(expr, referent) {
break;
}
expr = referent;
@ -1153,6 +1192,36 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
})
}
fn referent_used_exactly_once<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
reference: &Expr<'tcx>,
) -> bool {
let mir = enclosing_mir(cx.tcx, reference.hir_id);
if let Some(local) = expr_local(cx.tcx, reference)
&& let [location] = *local_assignments(mir, local).as_slice()
&& let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index)
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
&& !place.has_deref()
{
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
if possible_borrowers
.last()
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
{
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
}
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
// itself. See the comment in that method for an explanation as to why.
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
&& used_exactly_once(mir, place.local).unwrap_or(false)
} else {
false
}
}
// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
// projected type that is a type parameter. Returns `false` if replacing the types would have an
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
@ -1437,8 +1506,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
}
}
impl Dereferencing {
fn check_local_usage<'tcx>(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
impl<'tcx> Dereferencing<'tcx> {
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
if let Some(pat) = outer_pat {
// Check for auto-deref

View file

@ -339,10 +339,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
_ => return,
};
let copy_id = match cx.tcx.lang_items().copy_trait() {
Some(id) => id,
None => return,
};
let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { return };
let (ty_adt, ty_subs) = match *ty.kind() {
// Unions can't derive clone.
ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),

View file

@ -94,9 +94,8 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
} else {
path_def_id(cx, expr)
};
let def_id = match uncalled_path.or_else(|| fn_def_id(cx, expr)) {
Some(def_id) => def_id,
None => return,
let Some(def_id) = uncalled_path.or_else(|| fn_def_id(cx, expr)) else {
return
};
let conf = match self.disallowed.get(&def_id) {
Some(&index) => &self.conf_disallowed[index],

View file

@ -65,28 +65,24 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
impl<'tcx> LateLintPass<'tcx> for HashMapPass {
#[expect(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (cond_expr, then_expr, else_expr) = match higher::If::hir(expr) {
Some(higher::If { cond, then, r#else }) => (cond, then, r#else),
_ => return,
let Some(higher::If { cond: cond_expr, then: then_expr, r#else: else_expr }) = higher::If::hir(expr) else {
return
};
let (map_ty, contains_expr) = match try_parse_contains(cx, cond_expr) {
Some(x) => x,
None => return,
let Some((map_ty, contains_expr)) = try_parse_contains(cx, cond_expr) else {
return
};
let then_search = match find_insert_calls(cx, &contains_expr, then_expr) {
Some(x) => x,
None => return,
let Some(then_search) = find_insert_calls(cx, &contains_expr, then_expr) else {
return
};
let mut app = Applicability::MachineApplicable;
let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
let sugg = if let Some(else_expr) = else_expr {
let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
Some(search) => search,
None => return,
let Some(else_search) = find_insert_calls(cx, &contains_expr, else_expr) else {
return;
};
if then_search.edits.is_empty() && else_search.edits.is_empty() {

View file

@ -213,9 +213,8 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tc
if !closure_ty.has_late_bound_regions() {
return true;
}
let substs = match closure_ty.kind() {
ty::Closure(_, substs) => substs,
_ => return false,
let ty::Closure(_, substs) = closure_ty.kind() else {
return false;
};
let closure_sig = cx.tcx.signature_unclosure(substs.as_closure().sig(), Unsafety::Normal);
cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)

View file

@ -1,8 +1,10 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::FormatParamKind::{Implicit, Named, Numbered, Starred};
use clippy_utils::macros::{is_format_macro, FormatArgsExpn, FormatParam, FormatParamUsage};
use clippy_utils::macros::{
is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam, FormatParamUsage,
};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs};
use if_chain::if_chain;
use itertools::Itertools;
@ -13,6 +15,8 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
declare_clippy_lint! {
@ -111,11 +115,47 @@ declare_clippy_lint! {
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
#[clippy::version = "1.65.0"]
pub UNINLINED_FORMAT_ARGS,
pedantic,
style,
"using non-inlined variables in `format!` calls"
}
impl_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, UNINLINED_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
declare_clippy_lint! {
/// ### What it does
/// Detects [formatting parameters] that have no effect on the output of
/// `format!()`, `println!()` or similar macros.
///
/// ### Why is this bad?
/// Shorter format specifiers are easier to read, it may also indicate that
/// an expected formatting operation such as adding padding isn't happening.
///
/// ### Example
/// ```rust
/// println!("{:.}", 1.0);
///
/// println!("not padded: {:5}", format_args!("..."));
/// ```
/// Use instead:
/// ```rust
/// println!("{}", 1.0);
///
/// println!("not padded: {}", format_args!("..."));
/// // OR
/// println!("padded: {:5}", format!("..."));
/// ```
///
/// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters
#[clippy::version = "1.66.0"]
pub UNUSED_FORMAT_SPECS,
complexity,
"use of a format specifier that has no effect"
}
impl_lint_pass!(FormatArgs => [
FORMAT_IN_FORMAT_ARGS,
TO_STRING_IN_FORMAT_ARGS,
UNINLINED_FORMAT_ARGS,
UNUSED_FORMAT_SPECS,
]);
pub struct FormatArgs {
msrv: Option<RustcVersion>,
@ -130,27 +170,26 @@ impl FormatArgs {
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let Some(format_args) = FormatArgsExpn::parse(cx, expr);
let expr_expn_data = expr.span.ctxt().outer_expn_data();
let outermost_expn_data = outermost_expn_data(expr_expn_data);
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
if is_format_macro(cx, macro_def_id);
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
then {
for arg in &format_args.args {
if !arg.format.is_default() {
continue;
}
if is_aliased(&format_args, arg.param.value.hir_id) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
check_to_string_in_format_args(cx, name, arg.param.value);
if let Some(format_args) = FormatArgsExpn::parse(cx, expr)
&& let expr_expn_data = expr.span.ctxt().outer_expn_data()
&& let outermost_expn_data = outermost_expn_data(expr_expn_data)
&& let Some(macro_def_id) = outermost_expn_data.macro_def_id
&& is_format_macro(cx, macro_def_id)
&& let ExpnKind::Macro(_, name) = outermost_expn_data.kind
{
for arg in &format_args.args {
check_unused_format_specifier(cx, arg);
if !arg.format.is_default() {
continue;
}
if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site);
if is_aliased(&format_args, arg.param.value.hir_id) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
check_to_string_in_format_args(cx, name, arg.param.value);
}
if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id);
}
}
}
@ -158,10 +197,84 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
extract_msrv_attr!(LateContext);
}
fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span) {
fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs();
if let Count::Implied(Some(mut span)) = arg.format.precision
&& !span.is_empty()
{
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
span,
"empty precision specifier has no effect",
|diag| {
if param_ty.is_floating_point() {
diag.note("a precision specifier is not required to format floats");
}
if arg.format.is_default() {
// If there's no other specifiers remove the `:` too
span = arg.format_span();
}
diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable);
},
);
}
if is_type_diagnostic_item(cx, param_ty, sym::Arguments) && !arg.format.is_default_for_trait() {
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
arg.span,
"format specifiers have no effect on `format_args!()`",
|diag| {
let mut suggest_format = |spec, span| {
let message = format!("for the {spec} to apply consider using `format!()`");
if let Some(mac_call) = root_macro_call(arg.param.value.span)
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
&& arg.span.eq_ctxt(mac_call.span)
{
diag.span_suggestion(
cx.sess().source_map().span_until_char(mac_call.span, '!'),
message,
"format",
Applicability::MaybeIncorrect,
);
} else if let Some(span) = span {
diag.span_help(span, message);
}
};
if !arg.format.width.is_implied() {
suggest_format("width", arg.format.width.span());
}
if !arg.format.precision.is_implied() {
suggest_format("precision", arg.format.precision.span());
}
diag.span_suggestion_verbose(
arg.format_span(),
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
},
);
}
}
fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span, def_id: DefId) {
if args.format_string.span.from_expansion() {
return;
}
if call_site.edition() < Edition2021 && is_panic(cx, def_id) {
// panic! before 2021 edition considers a single string argument as non-format
return;
}
let mut fixes = Vec::new();
// If any of the arguments are referenced by an index number,
@ -248,7 +361,7 @@ fn check_format_in_format_args(
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
if_chain! {
if !value.span.from_expansion();
if let ExprKind::MethodCall(_, receiver, [], _) = value.kind;
if let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
if is_diag_trait_item(cx, method_def_id, sym::ToString);
let receiver_ty = cx.typeck_results().expr_ty(receiver);
@ -264,7 +377,7 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span.with_lo(receiver.span.hi()),
to_string_span.with_lo(receiver.span.hi()),
&format!(
"`to_string` applied to a type that implements `Display` in `{name}!` args"
),

View file

@ -1,11 +1,19 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_hir as hir;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::span_is_local;
use clippy_utils::source::snippet_opt;
use clippy_utils::{meets_msrv, msrvs, path_def_id};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_path, Visitor};
use rustc_hir::{
GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemRef, Item, ItemKind, PatKind, Path, PathSegment, Ty,
TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
use rustc_span::symbol::{kw, sym};
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -54,28 +62,152 @@ impl FromOverInto {
impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]);
impl<'tcx> LateLintPass<'tcx> for FromOverInto {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !meets_msrv(self.msrv, msrvs::RE_REBALANCING_COHERENCE) {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !meets_msrv(self.msrv, msrvs::RE_REBALANCING_COHERENCE) || !span_is_local(item.span) {
return;
}
if_chain! {
if let hir::ItemKind::Impl{ .. } = &item.kind;
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
if cx.tcx.is_diagnostic_item(sym::Into, impl_trait_ref.def_id);
if let ItemKind::Impl(Impl {
of_trait: Some(hir_trait_ref),
self_ty,
items: [impl_item_ref],
..
}) = item.kind
&& let Some(into_trait_seg) = hir_trait_ref.path.segments.last()
// `impl Into<target_ty> for self_ty`
&& let Some(GenericArgs { args: [GenericArg::Type(target_ty)], .. }) = into_trait_seg.args
&& let Some(middle_trait_ref) = cx.tcx.impl_trait_ref(item.def_id)
&& cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id)
{
span_lint_and_then(
cx,
FROM_OVER_INTO,
cx.tcx.sess.source_map().guess_head_span(item.span),
"an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true",
|diag| {
// If the target type is likely foreign mention the orphan rules as it's a common source of confusion
if path_def_id(cx, target_ty.peel_refs()).map_or(true, |id| !id.is_local()) {
diag.help(
"`impl From<Local> for Foreign` is allowed by the orphan rules, for more information see\n\
https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence"
);
}
then {
span_lint_and_help(
cx,
FROM_OVER_INTO,
cx.tcx.sess.source_map().guess_head_span(item.span),
"an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true",
None,
&format!("consider to implement `From<{}>` instead", impl_trait_ref.self_ty()),
);
}
let message = format!("replace the `Into` implentation with `From<{}>`", middle_trait_ref.self_ty());
if let Some(suggestions) = convert_to_from(cx, into_trait_seg, target_ty, self_ty, impl_item_ref) {
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
} else {
diag.help(message);
}
},
);
}
}
extract_msrv_attr!(LateContext);
}
/// Finds the occurences of `Self` and `self`
struct SelfFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
/// Occurences of `Self`
upper: Vec<Span>,
/// Occurences of `self`
lower: Vec<Span>,
/// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding
/// already named `val`
invalid: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for SelfFinder<'a, 'tcx> {
type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) {
for segment in path.segments {
match segment.ident.name {
kw::SelfLower => self.lower.push(segment.ident.span),
kw::SelfUpper => self.upper.push(segment.ident.span),
_ => continue,
}
}
self.invalid |= path.span.from_expansion();
if !self.invalid {
walk_path(self, path);
}
}
fn visit_name(&mut self, name: Symbol) {
if name == sym::val {
self.invalid = true;
}
}
}
fn convert_to_from(
cx: &LateContext<'_>,
into_trait_seg: &PathSegment<'_>,
target_ty: &Ty<'_>,
self_ty: &Ty<'_>,
impl_item_ref: &ImplItemRef,
) -> Option<Vec<(Span, String)>> {
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
let ImplItemKind::Fn(ref sig, body_id) = impl_item.kind else { return None };
let body = cx.tcx.hir().body(body_id);
let [input] = body.params else { return None };
let PatKind::Binding(.., self_ident, None) = input.pat.kind else { return None };
let from = snippet_opt(cx, self_ty.span)?;
let into = snippet_opt(cx, target_ty.span)?;
let mut suggestions = vec![
// impl Into<T> for U -> impl From<T> for U
// ~~~~ ~~~~
(into_trait_seg.ident.span, String::from("From")),
// impl Into<T> for U -> impl Into<U> for U
// ~ ~
(target_ty.span, from.clone()),
// impl Into<T> for U -> impl Into<T> for T
// ~ ~
(self_ty.span, into),
// fn into(self) -> T -> fn from(self) -> T
// ~~~~ ~~~~
(impl_item.ident.span, String::from("from")),
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
// fn into(self) -> T -> fn into(self) -> Self
// ~ ~~~~
(sig.decl.output.span(), String::from("Self")),
];
let mut finder = SelfFinder {
cx,
upper: Vec::new(),
lower: Vec::new(),
invalid: false,
};
finder.visit_expr(body.value);
if finder.invalid {
return None;
}
// don't try to replace e.g. `Self::default()` with `&[T]::default()`
if !finder.upper.is_empty() && !matches!(self_ty.kind, TyKind::Path(_)) {
return None;
}
for span in finder.upper {
suggestions.push((span, from.clone()));
}
for span in finder.lower {
suggestions.push((span, String::from("val")));
}
Some(suggestions)
}

View file

@ -7,14 +7,14 @@ use rustc_middle::{
lint::in_external_macro,
ty::{self, Ty},
};
use rustc_span::{sym, Span};
use rustc_span::{sym, Span, Symbol};
use clippy_utils::attrs::is_proc_macro;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{match_def_path, return_ty, trait_ref_of_method};
use clippy_utils::{return_ty, trait_ref_of_method};
use core::ops::ControlFlow;
@ -181,7 +181,7 @@ fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet)
}
}
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
static KNOWN_WRAPPER_TYS: &[Symbol] = &[sym::Rc, sym::Arc];
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool {
match *ty.kind() {
@ -189,7 +189,9 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
ty::Adt(adt, substs) => {
tys.insert(adt.did()) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did(), path))
|| KNOWN_WRAPPER_TYS
.iter()
.any(|&sym| cx.tcx.is_diagnostic_item(sym, adt.did()))
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
},
ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),

View file

@ -22,9 +22,8 @@ pub(super) fn check_fn(
return;
}
let code_snippet = match snippet_opt(cx, body.value.span) {
Some(s) => s,
_ => return,
let Some(code_snippet) = snippet_opt(cx, body.value.span) else {
return
};
let mut line_count: u64 = 0;
let mut in_comment = false;

View file

@ -35,7 +35,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.44.0"]
pub IMPLICIT_SATURATING_SUB,
pedantic,
style,
"Perform saturating subtraction instead of implicitly checking lower bound of data type"
}

View file

@ -145,9 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs);
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized {
val
} else {
let Some((rel, normalized_lhs, normalized_rhs)) = normalized else {
return;
};

View file

@ -124,9 +124,8 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
}
if let ItemKind::Enum(ref def, _) = item.kind {
let ty = cx.tcx.type_of(item.def_id);
let (adt, subst) = match ty.kind() {
Adt(adt, subst) => (adt, subst),
_ => panic!("already checked whether this is an enum"),
let Adt(adt, subst) = ty.kind() else {
panic!("already checked whether this is an enum")
};
if adt.variants().len() <= 1 {
return;

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::{is_must_use_ty, match_type};
use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item, match_type};
use clippy_utils::{is_must_use_func_call, paths};
use if_chain::if_chain;
use rustc_hir::{Local, PatKind};
@ -7,6 +7,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -99,10 +100,9 @@ declare_clippy_lint! {
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]);
const SYNC_GUARD_PATHS: [&[&str]; 6] = [
&paths::MUTEX_GUARD,
&paths::RWLOCK_READ_GUARD,
&paths::RWLOCK_WRITE_GUARD,
const SYNC_GUARD_SYMS: [Symbol; 3] = [sym::MutexGuard, sym::RwLockReadGuard, sym::RwLockWriteGuard];
const SYNC_GUARD_PATHS: [&[&str]; 3] = [
&paths::PARKING_LOT_MUTEX_GUARD,
&paths::PARKING_LOT_RWLOCK_READ_GUARD,
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
@ -121,7 +121,10 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
let init_ty = cx.typeck_results().expr_ty(init);
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => {
SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path))
SYNC_GUARD_SYMS
.iter()
.any(|&sym| is_type_diagnostic_item(cx, inner_ty, sym))
|| SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path))
},
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
@ -134,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
"non-binding let on a synchronization lock",
None,
"consider using an underscore-prefixed named \
binding or dropping explicitly with `std::mem::drop`"
binding or dropping explicitly with `std::mem::drop`",
);
} else if init_ty.needs_drop(cx.tcx, cx.param_env) {
span_lint_and_help(
@ -144,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
"non-binding `let` on a type that implements `Drop`",
None,
"consider using an underscore-prefixed named \
binding or dropping explicitly with `std::mem::drop`"
binding or dropping explicitly with `std::mem::drop`",
);
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
span_lint_and_help(
@ -153,7 +156,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
local.span,
"non-binding let on an expression with `#[must_use]` type",
None,
"consider explicitly using expression value"
"consider explicitly using expression value",
);
} else if is_must_use_func_call(cx, init) {
span_lint_and_help(
@ -162,7 +165,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
local.span,
"non-binding let on a result of a `#[must_use]` function",
None,
"consider explicitly using function result"
"consider explicitly using function result",
);
}
}

View file

@ -25,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(casts::CAST_NAN_TO_INT),
LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS),
@ -71,6 +72,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_args::UNINLINED_FORMAT_ARGS),
LintId::of(format_args::UNUSED_FORMAT_SPECS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
@ -87,6 +90,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(if_let_mutex::IF_LET_MUTEX),
LintId::of(implicit_saturating_add::IMPLICIT_SATURATING_ADD),
LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
LintId::of(infinite_iter::INFINITE_ITER),
LintId::of(inherent_to_string::INHERENT_TO_STRING),
@ -136,6 +140,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(match_result_ok::MATCH_RESULT_OK),
LintId::of(matches::COLLAPSIBLE_MATCH),
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
LintId::of(matches::MANUAL_FILTER),
LintId::of(matches::MANUAL_MAP),
LintId::of(matches::MANUAL_UNWRAP_OR),
LintId::of(matches::MATCH_AS_REF),

View file

@ -13,6 +13,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::UNUSED_FORMAT_SPECS),
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(int_plus_one::INT_PLUS_ONE),
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
@ -27,6 +28,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(matches::MANUAL_FILTER),
LintId::of(matches::MANUAL_UNWRAP_OR),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_SINGLE_BINDING),

View file

@ -3,20 +3,20 @@
// Manual edits will be overwritten.
store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON),
LintId::of(utils::internal_lints::DEFAULT_LINT),
LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE),
LintId::of(utils::internal_lints::INVALID_PATHS),
LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
LintId::of(utils::internal_lints::PRODUCE_ICE),
LintId::of(utils::internal_lints::UNNECESSARY_DEF_PATH),
LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
LintId::of(utils::internal_lints::clippy_lints_internal::CLIPPY_LINTS_INTERNAL),
LintId::of(utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS),
LintId::of(utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS),
LintId::of(utils::internal_lints::if_chain_style::IF_CHAIN_STYLE),
LintId::of(utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL),
LintId::of(utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR),
LintId::of(utils::internal_lints::invalid_paths::INVALID_PATHS),
LintId::of(utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON),
LintId::of(utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT),
LintId::of(utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE),
LintId::of(utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS),
LintId::of(utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE),
LintId::of(utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL),
LintId::of(utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA),
LintId::of(utils::internal_lints::produce_ice::PRODUCE_ICE),
LintId::of(utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH),
])

View file

@ -4,37 +4,37 @@
store.register_lints(&[
#[cfg(feature = "internal")]
utils::internal_lints::CLIPPY_LINTS_INTERNAL,
utils::internal_lints::clippy_lints_internal::CLIPPY_LINTS_INTERNAL,
#[cfg(feature = "internal")]
utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
#[cfg(feature = "internal")]
utils::internal_lints::COMPILER_LINT_FUNCTIONS,
utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS,
#[cfg(feature = "internal")]
utils::internal_lints::DEFAULT_DEPRECATION_REASON,
utils::internal_lints::if_chain_style::IF_CHAIN_STYLE,
#[cfg(feature = "internal")]
utils::internal_lints::DEFAULT_LINT,
utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL,
#[cfg(feature = "internal")]
utils::internal_lints::IF_CHAIN_STYLE,
utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR,
#[cfg(feature = "internal")]
utils::internal_lints::INTERNING_DEFINED_SYMBOL,
utils::internal_lints::invalid_paths::INVALID_PATHS,
#[cfg(feature = "internal")]
utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON,
#[cfg(feature = "internal")]
utils::internal_lints::INVALID_PATHS,
utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT,
#[cfg(feature = "internal")]
utils::internal_lints::LINT_WITHOUT_LINT_PASS,
utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
#[cfg(feature = "internal")]
utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS,
#[cfg(feature = "internal")]
utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE,
#[cfg(feature = "internal")]
utils::internal_lints::OUTER_EXPN_EXPN_DATA,
utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL,
#[cfg(feature = "internal")]
utils::internal_lints::PRODUCE_ICE,
utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA,
#[cfg(feature = "internal")]
utils::internal_lints::UNNECESSARY_DEF_PATH,
utils::internal_lints::produce_ice::PRODUCE_ICE,
#[cfg(feature = "internal")]
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH,
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
approx_const::APPROX_CONSTANT,
as_conversions::AS_CONVERSIONS,
@ -66,12 +66,14 @@ store.register_lints(&[
cargo::NEGATIVE_FEATURE_NAMES,
cargo::REDUNDANT_FEATURE_NAMES,
cargo::WILDCARD_DEPENDENCIES,
casts::AS_PTR_CAST_MUT,
casts::AS_UNDERSCORE,
casts::BORROW_AS_PTR,
casts::CAST_ABS_TO_UNSIGNED,
casts::CAST_ENUM_CONSTRUCTOR,
casts::CAST_ENUM_TRUNCATION,
casts::CAST_LOSSLESS,
casts::CAST_NAN_TO_INT,
casts::CAST_POSSIBLE_TRUNCATION,
casts::CAST_POSSIBLE_WRAP,
casts::CAST_PRECISION_LOSS,
@ -162,6 +164,7 @@ store.register_lints(&[
format_args::FORMAT_IN_FORMAT_ARGS,
format_args::TO_STRING_IN_FORMAT_ARGS,
format_args::UNINLINED_FORMAT_ARGS,
format_args::UNUSED_FORMAT_SPECS,
format_impl::PRINT_IN_FORMAT_IMPL,
format_impl::RECURSIVE_FORMAT_IMPL,
format_push_string::FORMAT_PUSH_STRING,
@ -258,6 +261,7 @@ store.register_lints(&[
match_result_ok::MATCH_RESULT_OK,
matches::COLLAPSIBLE_MATCH,
matches::INFALLIBLE_DESTRUCTURING_MATCH,
matches::MANUAL_FILTER,
matches::MANUAL_MAP,
matches::MANUAL_UNWRAP_OR,
matches::MATCH_AS_REF,
@ -403,6 +407,7 @@ store.register_lints(&[
missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
missing_trait_methods::MISSING_TRAIT_METHODS,
mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION,
mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
module_style::MOD_MODULE_FILES,
@ -475,6 +480,7 @@ store.register_lints(&[
panic_unimplemented::TODO,
panic_unimplemented::UNIMPLEMENTED,
panic_unimplemented::UNREACHABLE,
partial_pub_fields::PARTIAL_PUB_FIELDS,
partialeq_ne_impl::PARTIALEQ_NE_IMPL,
partialeq_to_none::PARTIALEQ_TO_NONE,
pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,

View file

@ -4,6 +4,7 @@
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
LintId::of(casts::AS_PTR_CAST_MUT),
LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),

View file

@ -29,12 +29,10 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS),
LintId::of(format_args::UNINLINED_FORMAT_ARGS),
LintId::of(functions::MUST_USE_CANDIDATE),
LintId::of(functions::TOO_MANY_LINES),
LintId::of(if_not_else::IF_NOT_ELSE),
LintId::of(implicit_hasher::IMPLICIT_HASHER),
LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
LintId::of(infinite_iter::MAYBE_INFINITE_ITER),
LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS),

View file

@ -47,6 +47,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES),
LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
LintId::of(missing_trait_methods::MISSING_TRAIT_METHODS),
LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION),
LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
@ -61,6 +62,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(panic_unimplemented::TODO),
LintId::of(panic_unimplemented::UNIMPLEMENTED),
LintId::of(panic_unimplemented::UNREACHABLE),
LintId::of(partial_pub_fields::PARTIAL_PUB_FIELDS),
LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH),
LintId::of(pub_use::PUB_USE),
LintId::of(redundant_slicing::DEREF_BY_SLICING),

View file

@ -25,12 +25,14 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(enum_variants::MODULE_INCEPTION),
LintId::of(eta_reduction::REDUNDANT_CLOSURE),
LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(format_args::UNINLINED_FORMAT_ARGS),
LintId::of(from_over_into::FROM_OVER_INTO),
LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
LintId::of(functions::DOUBLE_MUST_USE),
LintId::of(functions::MUST_USE_UNIT),
LintId::of(functions::RESULT_UNIT_ERR),
LintId::of(implicit_saturating_add::IMPLICIT_SATURATING_ADD),
LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
LintId::of(inherent_to_string::INHERENT_TO_STRING),
LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
LintId::of(len_zero::COMPARISON_TO_EMPTY),

View file

@ -11,6 +11,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(casts::CAST_NAN_TO_INT),
LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS),
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
LintId::of(drop_forget_ref::DROP_NON_DROP),

View file

@ -39,7 +39,6 @@ extern crate rustc_infer;
extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_mir_dataflow;
extern crate rustc_parse;
extern crate rustc_session;
extern crate rustc_span;
@ -291,6 +290,7 @@ mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
mod missing_inline;
mod missing_trait_methods;
mod mixed_read_write_in_expression;
mod module_style;
mod multi_assignments;
@ -326,6 +326,7 @@ mod option_if_let_else;
mod overflow_check_conditional;
mod panic_in_result_fn;
mod panic_unimplemented;
mod partial_pub_fields;
mod partialeq_ne_impl;
mod partialeq_to_none;
mod pass_by_ref_or_value;
@ -419,7 +420,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(&format!(
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
@ -435,7 +436,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
.and_then(|v| parse_msrv(&v, None, None));
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(&format!(
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
@ -446,7 +447,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
if let Some(clippy_msrv) = clippy_msrv {
// if both files have an msrv, let's compare them and emit a warning if they differ
if clippy_msrv != cargo_msrv {
sess.warn(&format!(
sess.warn(format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
));
}
@ -475,7 +476,7 @@ pub fn read_conf(sess: &Session) -> Conf {
let TryConf { conf, errors, warnings } = utils::conf::read(&file_name);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.err(&format!(
sess.err(format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
format_error(error)
@ -483,7 +484,7 @@ pub fn read_conf(sess: &Session) -> Conf {
}
for warning in warnings {
sess.struct_warn(&format!(
sess.struct_warn(format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
format_error(warning)
@ -530,17 +531,23 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
// all the internal lints
#[cfg(feature = "internal")]
{
store.register_early_pass(|| Box::new(utils::internal_lints::ClippyLintsInternal));
store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce));
store.register_late_pass(|_| Box::new(utils::internal_lints::CollapsibleCalls));
store.register_late_pass(|_| Box::new(utils::internal_lints::CompilerLintFunctions::new()));
store.register_late_pass(|_| Box::new(utils::internal_lints::IfChainStyle));
store.register_late_pass(|_| Box::new(utils::internal_lints::InvalidPaths));
store.register_late_pass(|_| Box::<utils::internal_lints::InterningDefinedSymbol>::default());
store.register_late_pass(|_| Box::<utils::internal_lints::LintWithoutLintPass>::default());
store.register_late_pass(|_| Box::new(utils::internal_lints::UnnecessaryDefPath));
store.register_late_pass(|_| Box::new(utils::internal_lints::OuterExpnDataPass));
store.register_late_pass(|_| Box::new(utils::internal_lints::MsrvAttrImpl));
store.register_early_pass(|| Box::new(utils::internal_lints::clippy_lints_internal::ClippyLintsInternal));
store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| {
Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new())
});
store.register_late_pass(|_| Box::new(utils::internal_lints::if_chain_style::IfChainStyle));
store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths));
store.register_late_pass(|_| {
Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default()
});
store.register_late_pass(|_| {
Box::<utils::internal_lints::lint_without_lint_pass::LintWithoutLintPass>::default()
});
store.register_late_pass(|_| Box::<utils::internal_lints::unnecessary_def_path::UnnecessaryDefPath>::default());
store.register_late_pass(|_| Box::new(utils::internal_lints::outer_expn_data_pass::OuterExpnDataPass));
store.register_late_pass(|_| Box::new(utils::internal_lints::msrv_attr_impl::MsrvAttrImpl));
}
let arithmetic_side_effects_allowed = conf.arithmetic_side_effects_allowed.clone();
@ -910,6 +917,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf));
store.register_late_pass(|_| Box::new(box_default::BoxDefault));
store.register_late_pass(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd));
store.register_early_pass(|| Box::new(partial_pub_fields::PartialPubFields));
store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -263,7 +263,8 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
match res {
Res::Local(hir_id) => {
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let extent = self.cx
let extent = self
.cx
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
@ -274,11 +275,12 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
self.indexed_indirectly
.insert(seqvar.segments[0].ident.name, Some(extent));
}
return false; // no need to walk further *on the variable*
}
Res::Def(DefKind::Static (_)| DefKind::Const, ..) => {
return false; // no need to walk further *on the variable*
},
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
@ -287,8 +289,8 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
}
return false; // no need to walk further *on the variable*
},
_ => (),
}
}
@ -302,17 +304,26 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
if_chain! {
// a range index op
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
|| (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
if let Some(trait_id) = self
.cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id));
if (meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
|| (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id));
if !self.check(args_1, args_0, expr);
then { return }
then {
return;
}
}
if_chain! {
// an index op
if let ExprKind::Index(seqexpr, idx) = expr.kind;
if !self.check(idx, seqexpr, expr);
then { return }
then {
return;
}
}
if_chain! {

View file

@ -331,9 +331,8 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
}
if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
let local_id = match iter_expr.path {
Res::Local(id) => id,
_ => return true,
let Res::Local(local_id) = iter_expr.path else {
return true
};
let mut v = NestedLoopVisitor {
cx,

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::match_function_call;
use clippy_utils::paths::FUTURE_FROM_GENERATOR;
use clippy_utils::match_function_call_with_def_id;
use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -140,9 +139,9 @@ fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'t
if args.bindings.len() == 1;
let binding = &args.bindings[0];
if binding.ident.name == sym::Output;
if let TypeBindingKind::Equality{term: Term::Ty(output)} = binding.kind;
if let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind;
then {
return Some(output)
return Some(output);
}
}
@ -175,9 +174,16 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName])
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
if_chain! {
if let Some(block_expr) = block.expr;
if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR);
if let Some(args) = cx
.tcx
.lang_items()
.from_generator_fn()
.and_then(|def_id| match_function_call_with_def_id(cx, block_expr, def_id));
if args.len() == 1;
if let Expr{kind: ExprKind::Closure(&Closure { body, .. }), ..} = args[0];
if let Expr {
kind: ExprKind::Closure(&Closure { body, .. }),
..
} = args[0];
let closure_body = cx.tcx.hir().body(body);
if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block));
then {

View file

@ -12,9 +12,9 @@ use std::ops::Deref;
use clippy_utils::{
diagnostics::{span_lint_and_then, span_lint_hir_and_then},
eq_expr_value, get_trait_def_id,
eq_expr_value,
higher::If,
is_diag_trait_item, is_trait_method, meets_msrv, msrvs, path_res, path_to_local_id, paths, peel_blocks,
is_diag_trait_item, is_trait_method, meets_msrv, msrvs, path_res, path_to_local_id, peel_blocks,
peel_blocks_with_stmt,
sugg::Sugg,
ty::implements_trait,
@ -190,7 +190,11 @@ impl TypeClampability {
fn is_clampable<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<TypeClampability> {
if ty.is_floating_point() {
Some(TypeClampability::Float)
} else if get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[])) {
} else if cx
.tcx
.get_diagnostic_item(sym::Ord)
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
Some(TypeClampability::Ord)
} else {
None

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{
is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq,
@ -63,7 +64,8 @@ fn check_arm<'tcx>(
if !pat_contains_or(inner_then_pat);
// the binding must come from the pattern of the containing match arm
// ..<local>.. => match <local> { .. }
if let Some(binding_span) = find_pat_binding(outer_pat, binding_id);
if let (Some(binding_span), is_innermost_parent_pat_struct)
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id);
// the "else" branches must be equal
if match (outer_else_body, inner_else_body) {
(None, None) => true,
@ -88,6 +90,13 @@ fn check_arm<'tcx>(
if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
if outer_is_match { "match" } else { "if let" },
);
// collapsing patterns need an explicit field name in struct pattern matching
// ex: Struct {x: Some(1)}
let replace_msg = if is_innermost_parent_pat_struct {
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
} else {
String::new()
};
span_lint_and_then(
cx,
COLLAPSIBLE_MATCH,
@ -96,7 +105,7 @@ fn check_arm<'tcx>(
|diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
help_span.push_span_label(binding_span, "replace this binding");
help_span.push_span_label(inner_then_pat.span, "with this pattern");
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
@ -117,8 +126,9 @@ fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
}
}
fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: HirId) -> (Option<Span>, bool) {
let mut span = None;
let mut is_innermost_parent_pat_struct = false;
pat.walk_short(|p| match &p.kind {
// ignore OR patterns
PatKind::Or(_) => false,
@ -129,9 +139,12 @@ fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
}
!found
},
_ => true,
_ => {
is_innermost_parent_pat_struct = matches!(p.kind, PatKind::Struct(..));
true
},
});
span
(span, is_innermost_parent_pat_struct)
}
fn pat_contains_or(pat: &Pat<'_>) -> bool {

View file

@ -0,0 +1,153 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::contains_unsafe_block;
use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id};
use rustc_hir::LangItem::OptionSome;
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_span::{sym, SyntaxContext};
use super::manual_utils::{check_with, SomeExpr};
use super::MANUAL_FILTER;
// Function called on the <expr> of `[&+]Some((ref | ref mut) x) => <expr>`
// Need to check if it's of the form `<expr>=if <cond> {<then_expr>} else {<else_expr>}`
// AND that only one `then/else_expr` resolves to `Some(x)` while the other resolves to `None`
// return the `cond` expression if so.
fn get_cond_expr<'tcx>(
cx: &LateContext<'tcx>,
pat: &Pat<'_>,
expr: &'tcx Expr<'_>,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
if_chain! {
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
if let PatKind::Binding(_,target, ..) = pat.kind;
if let (then_visitor, else_visitor)
= (is_some_expr(cx, target, ctxt, then_expr),
is_some_expr(cx, target, ctxt, else_expr));
if then_visitor != else_visitor; // check that one expr resolves to `Some(x)`, the other to `None`
then {
return Some(SomeExpr {
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
needs_unsafe_block: contains_unsafe_block(cx, expr),
needs_negated: !then_visitor // if the `then_expr` resolves to `None`, need to negate the cond
})
}
};
None
}
fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
// we don't want to use `peel_blocks` here because we don't care if the block is unsafe, it's
// checked by `contains_unsafe_block`
if let ExprKind::Block(block, None) = expr.kind {
if block.stmts.is_empty() {
return block.expr;
}
};
None
}
fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr)
}
// function called for each <expr> expression:
// Some(x) => if <cond> {
// <expr>
// } else {
// <expr>
// }
// Returns true if <expr> resolves to `Some(x)`, `false` otherwise
fn is_some_expr<'tcx>(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &'tcx Expr<'_>) -> bool {
if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) {
// there can be not statements in the block as they would be removed when switching to `.filter`
if let ExprKind::Call(callee, [arg]) = inner_expr.kind {
return ctxt == expr.span.ctxt()
&& is_res_lang_ctor(cx, path_res(cx, callee), OptionSome)
&& path_to_local_id(arg, target);
}
};
false
}
// given the closure: `|<pattern>| <expr>`
// returns `|&<pattern>| <expr>`
fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String {
if has_copy_trait {
let mut with_ampersand = body_str;
with_ampersand.insert(1, '&');
with_ampersand
} else {
body_str
}
}
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
scrutinee: &'tcx Expr<'_>,
arms: &'tcx [Arm<'_>],
expr: &'tcx Expr<'_>,
) {
let ty = cx.typeck_results().expr_ty(expr);
if is_type_diagnostic_item(cx, ty, sym::Option)
&& let [first_arm, second_arm] = arms
&& first_arm.guard.is_none()
&& second_arm.guard.is_none()
{
check(cx, expr, scrutinee, first_arm.pat, first_arm.body, Some(second_arm.pat), second_arm.body);
}
}
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>,
then_expr: &'tcx Expr<'_>,
else_expr: &'tcx Expr<'_>,
) {
check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
}
fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
scrutinee: &'tcx Expr<'_>,
then_pat: &'tcx Pat<'_>,
then_body: &'tcx Expr<'_>,
else_pat: Option<&'tcx Pat<'_>>,
else_body: &'tcx Expr<'_>,
) {
if let Some(sugg_info) = check_with(
cx,
expr,
scrutinee,
then_pat,
then_body,
else_pat,
else_body,
get_cond_expr,
) {
let body_str = add_ampersand_if_copy(sugg_info.body_str, sugg_info.scrutinee_impl_copy);
span_lint_and_sugg(
cx,
MANUAL_FILTER,
expr.span,
"manual implementation of `Option::filter`",
"try this",
if sugg_info.needs_brackets {
format!(
"{{ {}{}.filter({body_str}) }}",
sugg_info.scrutinee_str, sugg_info.as_ref_str
)
} else {
format!("{}{}.filter({body_str})", sugg_info.scrutinee_str, sugg_info.as_ref_str)
},
sugg_info.app,
);
}
}

View file

@ -1,22 +1,13 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::{
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
QPath, UnsafeSource,
};
use rustc_lint::LateContext;
use rustc_span::{sym, SyntaxContext};
use super::manual_utils::{check_with, SomeExpr};
use super::MANUAL_MAP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_res_lang_ctor, path_res};
use rustc_hir::LangItem::OptionSome;
use rustc_hir::{Arm, Block, BlockCheckMode, Expr, ExprKind, Pat, UnsafeSource};
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
@ -43,7 +34,6 @@ pub(super) fn check_if_let<'tcx>(
check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
}
#[expect(clippy::too_many_lines)]
fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
@ -53,254 +43,74 @@ fn check<'tcx>(
else_pat: Option<&'tcx Pat<'_>>,
else_body: &'tcx Expr<'_>,
) {
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
{
return;
}
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
Some(expr) => expr,
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
},
None => return,
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({scrutinee_str})")
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.ctxt() == some_expr.expr.span.ctxt();
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::MUT) {
"mut "
} else {
""
};
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{annotation}{some_binding}| unsafe {{ {expr_snip} }}")
} else {
format!("|{annotation}{some_binding}| {expr_snip}")
}
}
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{pat_snip}| unsafe {{ {expr_snip} }}")
} else {
format!("|{pat_snip}| {expr_snip}")
}
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
if let Some(sugg_info) = check_with(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
format!("{{ {scrutinee_str}{as_ref_str}.map({body_str}) }}")
} else {
format!("{scrutinee_str}{as_ref_str}.map({body_str})")
},
app,
);
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id(arg, binding)
&& cx.typeck_results().expr_adjustments(arg).is_empty()
&& !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
{
Some(func)
},
_ => None,
}
}
enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
struct SomeExpr<'tcx> {
expr: &'tcx Expr<'tcx>,
needs_unsafe_block: bool,
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
fn f<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone) => {
Some(OptionPat::None)
expr,
scrutinee,
then_pat,
then_body,
else_pat,
else_body,
get_some_expr,
) {
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if sugg_info.needs_brackets {
format!(
"{{ {}{}.map({}) }}",
sugg_info.scrutinee_str, sugg_info.as_ref_str, sugg_info.body_str
)
} else {
format!(
"{}{}.map({})",
sugg_info.scrutinee_str, sugg_info.as_ref_str, sugg_info.body_str
)
},
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
sugg_info.app,
);
}
f(cx, pat, 0, ctxt)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr<'tcx>(
cx: &LateContext<'tcx>,
_: &'tcx Pat<'_>,
expr: &'tcx Expr<'_>,
needs_unsafe_block: bool,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(callee, [arg])
if ctxt == expr.span.ctxt() && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) =>
{
Some(SomeExpr {
expr: arg,
needs_unsafe_block,
})
},
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
rules,
..
fn get_some_expr_internal<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
needs_unsafe_block: bool,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(callee, [arg])
if ctxt == expr.span.ctxt() && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) =>
{
Some(SomeExpr::new_no_negated(arg, needs_unsafe_block))
},
_,
) => get_some_expr(
cx,
expr,
needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
ctxt,
),
_ => None,
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
rules,
..
},
_,
) => get_some_expr_internal(
cx,
expr,
needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
ctxt,
),
_ => None,
}
}
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
get_some_expr_internal(cx, expr, false, ctxt)
}

View file

@ -0,0 +1,277 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::{
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, sugg::Sugg, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{def::Res, BindingAnnotation, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_span::{sym, SyntaxContext};
#[expect(clippy::too_many_arguments)]
#[expect(clippy::too_many_lines)]
pub(super) fn check_with<'tcx, F>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
scrutinee: &'tcx Expr<'_>,
then_pat: &'tcx Pat<'_>,
then_body: &'tcx Expr<'_>,
else_pat: Option<&'tcx Pat<'_>>,
else_body: &'tcx Expr<'_>,
get_some_expr_fn: F,
) -> Option<SuggInfo<'tcx>>
where
F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option<SomeExpr<'tcx>>,
{
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
{
return None;
}
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return None,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return None;
}
let Some(some_expr) = get_some_expr_fn(cx, some_pat, some_expr, expr_ctxt) else {
return None;
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return None;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
return None;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return None;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
},
None => return None,
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({scrutinee_str})")
} else {
scrutinee_str.into()
};
let closure_expr_snip = some_expr.to_snippet_with_context(cx, expr_ctxt, &mut app);
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.ctxt() == some_expr.expr.span.ctxt();
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return None;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::MUT) {
"mut "
} else {
""
};
if some_expr.needs_unsafe_block {
format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
} else {
format!("|{annotation}{some_binding}| {closure_expr_snip}")
}
}
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{pat_snip}| unsafe {{ {closure_expr_snip} }}")
} else {
format!("|{pat_snip}| {closure_expr_snip}")
}
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return None;
};
// relies on the fact that Option<T>: Copy where T: copy
let scrutinee_impl_copy = is_copy(cx, scrutinee_ty);
Some(SuggInfo {
needs_brackets: else_pat.is_none() && is_else_clause(cx.tcx, expr),
scrutinee_impl_copy,
scrutinee_str,
as_ref_str,
body_str,
app,
})
}
pub struct SuggInfo<'a> {
pub needs_brackets: bool,
pub scrutinee_impl_copy: bool,
pub scrutinee_str: String,
pub as_ref_str: &'a str,
pub body_str: String,
pub app: Applicability,
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id(arg, binding)
&& cx.typeck_results().expr_adjustments(arg).is_empty()
&& !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
{
Some(func)
},
_ => None,
}
}
#[derive(Debug)]
pub(super) enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
pub(super) struct SomeExpr<'tcx> {
pub expr: &'tcx Expr<'tcx>,
pub needs_unsafe_block: bool,
pub needs_negated: bool, // for `manual_filter` lint
}
impl<'tcx> SomeExpr<'tcx> {
pub fn new_no_negated(expr: &'tcx Expr<'tcx>, needs_unsafe_block: bool) -> Self {
Self {
expr,
needs_unsafe_block,
needs_negated: false,
}
}
pub fn to_snippet_with_context(
&self,
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
app: &mut Applicability,
) -> Sugg<'tcx> {
let sugg = Sugg::hir_with_context(cx, self.expr, ctxt, "..", app);
if self.needs_negated { !sugg } else { sugg }
}
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
pub(super) fn try_parse_pattern<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
fn f<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone) => {
Some(OptionPat::None)
},
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat, 0, ctxt)
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
}

View file

@ -221,7 +221,6 @@ fn iter_matching_struct_fields<'a>(
#[expect(clippy::similar_names)]
impl<'a> NormalizedPat<'a> {
#[expect(clippy::too_many_lines)]
fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
match pat.kind {
PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
@ -235,9 +234,8 @@ impl<'a> NormalizedPat<'a> {
Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields)
},
PatKind::TupleStruct(ref path, pats, wild_idx) => {
let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() {
Some(x) => x,
None => return Self::Wild,
let Some(adt) = cx.typeck_results().pat_ty(pat).ty_adt_def() else {
return Self::Wild
};
let (var_id, variant) = if adt.is_enum() {
match cx.qpath_res(path, pat.hir_id).opt_def_id() {

View file

@ -58,6 +58,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
&snippet_body,
&mut applicability,
Some(span),
true,
);
span_lint_and_sugg(
@ -90,6 +91,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
&snippet_body,
&mut applicability,
None,
true,
);
(expr.span, sugg)
},
@ -107,10 +109,14 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
},
PatKind::Wild => {
if ex.can_have_side_effects() {
let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
let sugg = format!(
"{};\n{indent}{snippet_body}",
snippet_with_applicability(cx, ex.span, "..", &mut applicability)
let sugg = sugg_with_curlies(
cx,
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
&mut applicability,
None,
false,
);
span_lint_and_sugg(
@ -169,6 +175,7 @@ fn sugg_with_curlies<'a>(
snippet_body: &str,
applicability: &mut Applicability,
assignment: Option<Span>,
needs_var_binding: bool,
) -> String {
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
@ -200,9 +207,15 @@ fn sugg_with_curlies<'a>(
s
});
format!(
"{cbrace_start}let {} = {};\n{indent}{assignment_str}{snippet_body}{cbrace_end}",
snippet_with_applicability(cx, bind_names, "..", applicability),
snippet_with_applicability(cx, matched_vars, "..", applicability)
)
let scrutinee = if needs_var_binding {
format!(
"let {} = {}",
snippet_with_applicability(cx, bind_names, "..", applicability),
snippet_with_applicability(cx, matched_vars, "..", applicability)
)
} else {
snippet_with_applicability(cx, matched_vars, "..", applicability).to_string()
};
format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")
}

View file

@ -1,7 +1,9 @@
mod collapsible_match;
mod infallible_destructuring_match;
mod manual_filter;
mod manual_map;
mod manual_unwrap_or;
mod manual_utils;
mod match_as_ref;
mod match_bool;
mod match_like_matches;
@ -898,6 +900,34 @@ declare_clippy_lint! {
"reimplementation of `map`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `match` which could be implemented using `filter`
///
/// ### Why is this bad?
/// Using the `filter` method is clearer and more concise.
///
/// ### Example
/// ```rust
/// match Some(0) {
/// Some(x) => if x % 2 == 0 {
/// Some(x)
/// } else {
/// None
/// },
/// None => None,
/// };
/// ```
/// Use instead:
/// ```rust
/// Some(0).filter(|&x| x % 2 == 0);
/// ```
#[clippy::version = "1.66.0"]
pub MANUAL_FILTER,
complexity,
"reimplentation of `filter`"
}
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
@ -939,6 +969,7 @@ impl_lint_pass!(Matches => [
SIGNIFICANT_DROP_IN_SCRUTINEE,
TRY_ERR,
MANUAL_MAP,
MANUAL_FILTER,
]);
impl<'tcx> LateLintPass<'tcx> for Matches {
@ -988,6 +1019,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if !in_constant(cx, expr.hir_id) {
manual_unwrap_or::check(cx, expr, ex, arms);
manual_map::check_match(cx, expr, ex, arms);
manual_filter::check_match(cx, ex, arms, expr);
}
if self.infallible_destructuring_match_linted {
@ -1014,6 +1046,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
if !in_constant(cx, expr.hir_id) {
manual_map::check_if_let(cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr);
manual_filter::check_if_let(
cx,
expr,
if_let.let_pat,
if_let.let_expr,
if_let.if_then,
else_expr,
);
}
}
redundant_pattern_match::check_if_let(

View file

@ -1,14 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{expr_block, snippet};
use clippy_utils::ty::{implements_trait, match_type, peel_mid_ty_refs};
use clippy_utils::{
is_lint_allowed, is_unit_expr, is_wild, paths, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs,
};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs};
use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs};
use core::cmp::max;
use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::sym;
use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
@ -156,10 +155,10 @@ fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) ->
/// Returns `true` if the given type is an enum we know won't be expanded in the future
fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
// list of candidate `Enum`s we know will never get any more members
let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT];
let candidates = [sym::Cow, sym::Option, sym::Result];
for candidate_ty in candidates {
if match_type(cx, ty, candidate_ty) {
if is_type_diagnostic_item(cx, ty, candidate_ty) {
return true;
}
}

View file

@ -42,9 +42,8 @@ pub(super) fn check(
fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> {
has_iter_method(cx, self_ref_ty).map(|ty_name| {
let mutbl = match self_ref_ty.kind() {
ty::Ref(_, _, mutbl) => mutbl,
_ => unreachable!(),
let ty::Ref(_, _, mutbl) = self_ref_ty.kind() else {
unreachable!()
};
let method_name = match mutbl {
hir::Mutability::Not => "iter",

View file

@ -21,11 +21,7 @@ pub fn check(
return;
}
let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) {
mm
} else {
return;
};
let Some(mm) = is_min_or_max(cx, unwrap_arg) else { return };
if ty.is_signed() {
use self::{
@ -33,9 +29,7 @@ pub fn check(
Sign::{Neg, Pos},
};
let sign = if let Some(sign) = lit_sign(arith_rhs) {
sign
} else {
let Some(sign) = lit_sign(arith_rhs) else {
return;
};

View file

@ -102,9 +102,7 @@ use bind_instead_of_map::BindInsteadOfMap;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{
contains_return, get_trait_def_id, is_trait_method, iter_input_pats, meets_msrv, msrvs, paths, return_ty,
};
use clippy_utils::{contains_return, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::def::Res;
@ -3372,7 +3370,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
then {
let first_arg_span = first_arg_ty.span;
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id())
.self_ty()
.skip_binder();
wrong_self_convention::check(
cx,
item.ident.name.as_str(),
@ -3380,7 +3380,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
first_arg_ty,
first_arg_span,
false,
true
true,
);
}
}
@ -3389,7 +3389,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
if item.ident.name == sym::new;
if let TraitItemKind::Fn(_, _) = item.kind;
let ret_ty = return_ty(cx, item.hir_id());
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id())
.self_ty()
.skip_binder();
if !ret_ty.contains(self_ty);
then {
@ -3846,14 +3848,13 @@ impl SelfKind {
return m == mutability && t == parent_ty;
}
let trait_path = match mutability {
hir::Mutability::Not => &paths::ASREF_TRAIT,
hir::Mutability::Mut => &paths::ASMUT_TRAIT,
let trait_sym = match mutability {
hir::Mutability::Not => sym::AsRef,
hir::Mutability::Mut => sym::AsMut,
};
let trait_def_id = match get_trait_def_id(cx, trait_path) {
Some(did) => did,
None => return false,
let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else {
return false
};
implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
}

View file

@ -32,8 +32,7 @@ pub(super) fn check<'tcx>(
return;
}
let deref_aliases: [&[&str]; 9] = [
&paths::DEREF_TRAIT_METHOD,
let deref_aliases: [&[&str]; 8] = [
&paths::DEREF_MUT_TRAIT_METHOD,
&paths::CSTRING_AS_C_STR,
&paths::OS_STRING_AS_OS_STR,
@ -45,12 +44,14 @@ pub(super) fn check<'tcx>(
];
let is_deref = match map_arg.kind {
hir::ExprKind::Path(ref expr_qpath) => cx
.qpath_res(expr_qpath, map_arg.hir_id)
.opt_def_id()
.map_or(false, |fun_def_id| {
deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
}),
hir::ExprKind::Path(ref expr_qpath) => {
cx.qpath_res(expr_qpath, map_arg.hir_id)
.opt_def_id()
.map_or(false, |fun_def_id| {
cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id)
|| deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path))
})
},
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
let closure_body = cx.tcx.hir().body(body);
let closure_expr = peel_blocks(closure_body.value);
@ -68,7 +69,8 @@ pub(super) fn check<'tcx>(
if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
then {
let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap();
deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
cx.tcx.is_diagnostic_item(sym::deref_method, method_did)
|| deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
} else {
false
}

View file

@ -1,14 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
use clippy_utils::ty::{implements_trait, match_type};
use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_trait_item, last_path_segment};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym};
use rustc_span::symbol::{kw, sym, Symbol};
use std::borrow::Cow;
use super::OR_FUN_CALL;
@ -88,11 +88,11 @@ pub(super) fn check<'tcx>(
fun_span: Option<Span>,
) {
// (path, fn_has_argument, methods, suffix)
const KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
(&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
(&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
(&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
(&paths::RESULT, true, &["or", "unwrap_or"], "else"),
const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
(sym::BTreeEntry, false, &["or_insert"], "with"),
(sym::HashMapEntry, false, &["or_insert"], "with"),
(sym::Option, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
(sym::Result, true, &["or", "unwrap_or"], "else"),
];
if_chain! {
@ -104,7 +104,7 @@ pub(super) fn check<'tcx>(
let self_ty = cx.typeck_results().expr_ty(self_expr);
if let Some(&(_, fn_has_arguments, poss, suffix)) =
KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0));
KNOW_TYPES.iter().find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0));
if poss.contains(&name);
@ -121,10 +121,9 @@ pub(super) fn check<'tcx>(
macro_expanded_snipped = snippet(cx, snippet_span, "..");
match macro_expanded_snipped.strip_prefix("$crate::vec::") {
Some(stripped) => Cow::from(stripped),
None => macro_expanded_snipped
None => macro_expanded_snipped,
}
}
else {
} else {
not_macro_argument_snippet
}
};

View file

@ -289,9 +289,7 @@ fn parse_iter_usage<'tcx>(
) -> Option<IterUsage> {
let (kind, span) = match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
let (name, args) = if let ExprKind::MethodCall(name, _, [args @ ..], _) = e.kind {
(name, args)
} else {
let ExprKind::MethodCall(name, _, [args @ ..], _) = e.kind else {
return None;
};
let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;

View file

@ -363,7 +363,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
&& let output_ty = return_ty(cx, item.hir_id())
&& let local_def_id = cx.tcx.hir().local_def_id(item.hir_id())
&& Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.hir_id());
let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.hir_id());
fn_ctxt.can_coerce(ty, output_ty)
}) {
if has_lifetime(output_ty) && has_lifetime(ty) {

View file

@ -6,9 +6,7 @@ use rustc_lint::EarlyContext;
use super::{SEPARATED_LITERAL_SUFFIX, UNSEPARATED_LITERAL_SUFFIX};
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) {
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
return; // It's useless so shouldn't lint.
};
// Do not lint when literal is unsuffixed.

View file

@ -5,9 +5,7 @@ use rustc_lint::EarlyContext;
use super::MIXED_CASE_HEX_LITERALS;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &str) {
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
return; // It's useless so shouldn't lint.
};
if maybe_last_sep_idx <= 2 {

View file

@ -6,6 +6,7 @@ use rustc_lint::EarlyContext;
use super::ZERO_PREFIXED_LITERAL;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
let trimmed_lit_snip = lit_snip.trim_start_matches(|c| c == '_' || c == '0');
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
@ -15,15 +16,18 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
diag.span_suggestion(
lit.span,
"if you mean to use a decimal constant, remove the `0` to avoid confusion",
lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
lit.span,
"if you mean to use an octal constant, use `0o`",
format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
trimmed_lit_snip.to_string(),
Applicability::MaybeIncorrect,
);
// do not advise to use octal form if the literal cannot be expressed in base 8.
if !lit_snip.contains(|c| c == '8' || c == '9') {
diag.span_suggestion(
lit.span,
"if you mean to use an octal constant, use `0o`",
format!("0o{trimmed_lit_snip}"),
Applicability::MaybeIncorrect,
);
}
},
);
}

View file

@ -70,9 +70,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
// find the type that the Impl is for
// only lint on struct/enum/union for now
let defid = match path.res {
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) => defid,
_ => return,
let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) = path.res else {
return
};
// get the names of the generic parameters in the type

View file

@ -0,0 +1,98 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_lint_allowed;
use clippy_utils::macros::span_is_local;
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::AssocItem;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks if a provided method is used implicitly by a trait
/// implementation. A usage example would be a wrapper where every method
/// should perform some operation before delegating to the inner type's
/// implemenation.
///
/// This lint should typically be enabled on a specific trait `impl` item
/// rather than globally.
///
/// ### Why is this bad?
/// Indicates that a method is missing.
///
/// ### Example
/// ```rust
/// trait Trait {
/// fn required();
///
/// fn provided() {}
/// }
///
/// # struct Type;
/// #[warn(clippy::missing_trait_methods)]
/// impl Trait for Type {
/// fn required() { /* ... */ }
/// }
/// ```
/// Use instead:
/// ```rust
/// trait Trait {
/// fn required();
///
/// fn provided() {}
/// }
///
/// # struct Type;
/// #[warn(clippy::missing_trait_methods)]
/// impl Trait for Type {
/// fn required() { /* ... */ }
///
/// fn provided() { /* ... */ }
/// }
/// ```
#[clippy::version = "1.66.0"]
pub MISSING_TRAIT_METHODS,
restriction,
"trait implementation uses default provided method"
}
declare_lint_pass!(MissingTraitMethods => [MISSING_TRAIT_METHODS]);
impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if !is_lint_allowed(cx, MISSING_TRAIT_METHODS, item.hir_id())
&& span_is_local(item.span)
&& let ItemKind::Impl(Impl {
items,
of_trait: Some(trait_ref),
..
}) = item.kind
&& let Some(trait_id) = trait_ref.trait_def_id()
{
let mut provided: DefIdMap<&AssocItem> = cx
.tcx
.provided_trait_methods(trait_id)
.map(|assoc| (assoc.def_id, assoc))
.collect();
for impl_item in *items {
if let Some(def_id) = impl_item.trait_item_def_id {
provided.remove(&def_id);
}
}
for assoc in provided.values() {
let source_map = cx.tcx.sess.source_map();
let definition_span = source_map.guess_head_span(cx.tcx.def_span(assoc.def_id));
span_lint_and_help(
cx,
MISSING_TRAIT_METHODS,
source_map.guess_head_span(item.span),
&format!("missing trait method provided by default: `{}`", assoc.name),
Some(definition_span),
"implement the method",
);
}
}
}
}

View file

@ -190,10 +190,7 @@ fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) {
if parent_id == cur_id {
break;
}
let parent_node = match map.find(parent_id) {
Some(parent) => parent,
None => break,
};
let Some(parent_node) = map.find(parent_id) else { break };
let stop_early = match parent_node {
Node::Expr(expr) => check_expr(vis, expr),

View file

@ -49,9 +49,8 @@ declare_lint_pass!(NeedlessForEach => [NEEDLESS_FOR_EACH]);
impl<'tcx> LateLintPass<'tcx> for NeedlessForEach {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
let expr = match stmt.kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr,
_ => return,
let (StmtKind::Expr(expr) | StmtKind::Semi(expr)) = stmt.kind else {
return
};
if_chain! {

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::implements_trait;
use clippy_utils::{self, get_trait_def_id, paths};
use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -47,18 +47,16 @@ declare_lint_pass!(NoNegCompOpForPartialOrd => [NEG_CMP_OP_ON_PARTIAL_ORD]);
impl<'tcx> LateLintPass<'tcx> for NoNegCompOpForPartialOrd {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Unary(UnOp::Not, inner) = expr.kind;
if let ExprKind::Binary(ref op, left, _) = inner.kind;
if let BinOpKind::Le | BinOpKind::Ge | BinOpKind::Lt | BinOpKind::Gt = op.node;
then {
let ty = cx.typeck_results().expr_ty(left);
let implements_ord = {
if let Some(id) = get_trait_def_id(cx, &paths::ORD) {
if let Some(id) = cx.tcx.get_diagnostic_item(sym::Ord) {
implements_trait(cx, ty, id, &[])
} else {
return;
@ -81,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for NoNegCompOpForPartialOrd {
"the use of negated comparison operators on partially ordered \
types produces code that is hard to read and refactor, please \
consider using the `partial_cmp` method instead, to make it \
clear that the two values could be incomparable"
clear that the two values could be incomparable",
);
}
}

View file

@ -357,9 +357,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
}
// Make sure it is a const item.
let item_def_id = match cx.qpath_res(qpath, expr.hir_id) {
Res::Def(DefKind::Const | DefKind::AssocConst, did) => did,
_ => return,
let Res::Def(DefKind::Const | DefKind::AssocConst, item_def_id) = cx.qpath_res(qpath, expr.hir_id) else {
return
};
// Climb up to resolve any field access and explicit referencing.

View file

@ -55,9 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
if let ExprKind::Lit(_) = param.kind;
then {
let snip = match snippet_opt(cx, param.span) {
Some(s) => s,
_ => return,
let Some(snip) = snippet_opt(cx, param.span) else {
return
};
if !snip.starts_with("0o") {
@ -72,16 +71,10 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE);
if let ExprKind::Lit(_) = param.kind;
if let Some(snip) = snippet_opt(cx, param.span);
if !snip.starts_with("0o");
then {
let snip = match snippet_opt(cx, param.span) {
Some(s) => s,
_ => return,
};
if !snip.starts_with("0o") {
show_error(cx, param);
}
show_error(cx, param);
}
}
},

View file

@ -266,7 +266,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
.iter()
.find(|b| b.0 == brace)
.map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
.ok_or_else(|| de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
.ok_or_else(|| de::Error::custom(format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
})
}
}

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{match_any_def_paths, path_def_id, paths};
use clippy_utils::{match_def_path, path_def_id, paths};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
@ -49,13 +49,15 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool)
(arg, arg.span)
},
ExprKind::Call(path, [arg])
if path_def_id(cx, path)
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
.map_or(false, |idx| match idx {
0 => true,
1 => !is_copy(cx, typeck.expr_ty(expr)),
_ => false,
}) =>
if path_def_id(cx, path).map_or(false, |id| {
if match_def_path(cx, id, &paths::FROM_STR_METHOD) {
true
} else if cx.tcx.lang_items().from_fn() == Some(id) {
!is_copy(cx, typeck.expr_ty(expr))
} else {
false
}
}) =>
{
(arg, arg.span)
},

View file

@ -0,0 +1,81 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Item, ItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks whether partial fields of a struct are public.
///
/// Either make all fields of a type public, or make none of them public
///
/// ### Why is this bad?
/// Most types should either be:
/// * Abstract data types: complex objects with opaque implementation which guard
/// interior invariants and expose intentionally limited API to the outside world.
/// * Data:relatively simple objects which group a bunch of related attributes together.
///
/// ### Example
/// ```rust
/// pub struct Color {
/// pub r: u8,
/// pub g: u8,
/// b: u8,
/// }
/// ```
/// Use instead:
/// ```rust
/// pub struct Color {
/// pub r: u8,
/// pub g: u8,
/// pub b: u8,
/// }
/// ```
#[clippy::version = "1.66.0"]
pub PARTIAL_PUB_FIELDS,
restriction,
"partial fields of a struct are public"
}
declare_lint_pass!(PartialPubFields => [PARTIAL_PUB_FIELDS]);
impl EarlyLintPass for PartialPubFields {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
let ItemKind::Struct(ref st, _) = item.kind else {
return;
};
let mut fields = st.fields().iter();
let Some(first_field) = fields.next() else {
// Empty struct.
return;
};
let all_pub = first_field.vis.kind.is_pub();
let all_priv = !all_pub;
let msg = "mixed usage of pub and non-pub fields";
for field in fields {
if all_priv && field.vis.kind.is_pub() {
span_lint_and_help(
cx,
PARTIAL_PUB_FIELDS,
field.vis.span,
msg,
None,
"consider using private field here",
);
return;
} else if all_pub && !field.vis.kind.is_pub() {
span_lint_and_help(
cx,
PARTIAL_PUB_FIELDS,
field.vis.span,
msg,
None,
"consider using public field here",
);
return;
}
}
}
}

View file

@ -15,13 +15,17 @@ use rustc_hir::{
ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
TraitItem, TraitItemKind, TyKind, Unsafety,
};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::{self, Binder, ExistentialPredicate, List, PredicateKind, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::sym;
use rustc_span::symbol::Symbol;
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use std::fmt;
use std::iter;
@ -384,6 +388,17 @@ enum DerefTy<'tcx> {
Slice(Option<Span>, Ty<'tcx>),
}
impl<'tcx> DerefTy<'tcx> {
fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> {
match *self {
Self::Str => cx.tcx.types.str_,
Self::Path => cx.tcx.mk_adt(
cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()),
List::empty(),
),
Self::Slice(_, ty) => cx.tcx.mk_slice(ty),
}
}
fn argless_str(&self) -> &'static str {
match *self {
Self::Str => "str",
@ -552,9 +567,8 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
}
// Check if this is local we care about
let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
Some(&i) => i,
None => return walk_expr(self, e),
let Some(&args_idx) = path_to_local(e).and_then(|id| self.bindings.get(&id)) else {
return walk_expr(self, e);
};
let args = &self.args[args_idx];
let result = &mut self.results[args_idx];
@ -582,6 +596,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
match *ty.skip_binder().peel_refs().kind() {
ty::Dynamic(preds, _, _) => !matches_preds(self.cx, args.deref_ty.ty(self.cx), preds),
ty::Param(_) => true,
ty::Adt(def, _) => def.did() == args.ty_did,
_ => false,
@ -609,14 +624,15 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
}
}
let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
x
} else {
let Some(id) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) else {
set_skip_flag();
return;
};
match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
ty::Dynamic(preds, _, _) if !matches_preds(self.cx, args.deref_ty.ty(self.cx), preds) => {
set_skip_flag();
},
ty::Param(_) => {
set_skip_flag();
},
@ -668,6 +684,30 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
v.results
}
fn matches_preds<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
preds: &'tcx [Binder<'tcx, ExistentialPredicate<'tcx>>],
) -> bool {
let infcx = cx.tcx.infer_ctxt().build();
preds.iter().all(|&p| match cx.tcx.erase_late_bound_regions(p) {
ExistentialPredicate::Trait(p) => infcx
.type_implements_trait(p.def_id, ty, p.substs, cx.param_env)
.must_apply_modulo_regions(),
ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new(
ObligationCause::dummy(),
cx.param_env,
cx.tcx.mk_predicate(Binder::bind_with_vars(
PredicateKind::Projection(p.with_self_ty(cx.tcx, ty)),
List::empty(),
)),
)),
ExistentialPredicate::AutoTrait(p) => infcx
.type_implements_trait(p, ty, List::empty(), cx.param_env)
.must_apply_modulo_regions(),
})
}
fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
if let TyKind::Rptr(lt, ref m) = ty.kind {
Some((lt, m.mutbl, ty.span))

View file

@ -49,15 +49,13 @@ declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]);
impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Check if the expressions is a ptr.offset or ptr.wrapping_offset method call
let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) {
Some(call_arg) => call_arg,
None => return,
let Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else {
return
};
// Check if the argument to the method call is a cast from usize
let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) {
Some(cast_lhs_expr) => cast_lhs_expr,
None => return,
let Some(cast_lhs_expr) = expr_as_cast_from_usize(cx, arg_expr) else {
return
};
let msg = format!("use of `{method}` with a `usize` casted to an `isize`");

View file

@ -1,25 +1,18 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::mir::{visit_local_usage, LocalUsage, PossibleBorrowerMap};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{def_id, Body, FnDecl, HirId};
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::{
self, traversal,
visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
Mutability,
};
use rustc_middle::ty::{self, visit::TypeVisitor, Ty};
use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis, ResultsCursor};
use rustc_middle::mir;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::{BytePos, Span};
use rustc_span::sym;
use std::ops::ControlFlow;
macro_rules! unwrap_or_continue {
($x:expr) => {
@ -89,21 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
let mir = cx.tcx.optimized_mir(def_id.to_def_id());
let possible_origin = {
let mut vis = PossibleOriginVisitor::new(mir);
vis.visit_body(mir);
vis.into_map(cx)
};
let maybe_storage_live_result = MaybeStorageLive
.into_engine(cx.tcx, mir)
.pass_name("redundant_clone")
.iterate_to_fixpoint()
.into_results_cursor(mir);
let mut possible_borrower = {
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
vis.visit_body(mir);
vis.into_map(cx, maybe_storage_live_result)
};
let mut possible_borrower = PossibleBorrowerMap::new(cx, mir);
for (bb, bbdata) in mir.basic_blocks.iter_enumerated() {
let terminator = bbdata.terminator();
@ -374,403 +353,40 @@ struct CloneUsage {
/// Whether the clone value is mutated.
clone_consumed_or_mutated: bool,
}
fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
struct V {
cloned: mir::Local,
clone: mir::Local,
result: CloneUsage,
}
impl<'tcx> mir::visit::Visitor<'tcx> for V {
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
let statements = &data.statements;
for (statement_index, statement) in statements.iter().enumerate() {
self.visit_statement(statement, mir::Location { block, statement_index });
}
self.visit_terminator(
data.terminator(),
mir::Location {
block,
statement_index: statements.len(),
},
);
}
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
let local = place.local;
if local == self.cloned
&& !matches!(
ctx,
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
)
{
self.result.cloned_used = true;
self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
matches!(
ctx,
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
)
.then(|| loc)
});
} else if local == self.clone {
match ctx {
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
self.result.clone_consumed_or_mutated = true;
},
_ => {},
}
}
}
}
let init = CloneUsage {
cloned_used: false,
cloned_consume_or_mutate_loc: None,
// Consider non-temporary clones consumed.
// TODO: Actually check for mutation of non-temporaries.
clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp,
};
traversal::ReversePostorder::new(mir, bb)
.skip(1)
.fold(init, |usage, (tbb, tdata)| {
// Short-circuit
if (usage.cloned_used && usage.clone_consumed_or_mutated) ||
// Give up on loops
tdata.terminator().successors().any(|s| s == bb)
{
return CloneUsage {
cloned_used: true,
clone_consumed_or_mutated: true,
..usage
};
}
let mut v = V {
cloned,
clone,
result: usage,
};
v.visit_basic_block_data(tbb, tdata);
v.result
})
}
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
#[derive(Copy, Clone)]
struct MaybeStorageLive;
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
type Domain = BitSet<mir::Local>;
const NAME: &'static str = "maybe_storage_live";
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = dead
BitSet::new_empty(body.local_decls.len())
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
for arg in body.args_iter() {
state.insert(arg);
}
}
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
type Idx = mir::Local;
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
match stmt.kind {
mir::StatementKind::StorageLive(l) => trans.gen(l),
mir::StatementKind::StorageDead(l) => trans.kill(l),
_ => (),
}
}
fn terminator_effect(
&self,
_trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
_loc: mir::Location,
) {
}
fn call_return_effect(
&self,
_trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
// Nothing to do when a call returns successfully
}
}
/// Collects the possible borrowers of each local.
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
/// possible borrowers of `a`.
struct PossibleBorrowerVisitor<'a, 'tcx> {
possible_borrower: TransitiveRelation,
body: &'a mir::Body<'tcx>,
cx: &'a LateContext<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
}
impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
fn new(
cx: &'a LateContext<'tcx>,
body: &'a mir::Body<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
) -> Self {
Self {
possible_borrower: TransitiveRelation::default(),
cx,
body,
possible_origin,
}
}
fn into_map(
self,
cx: &LateContext<'tcx>,
maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>,
) -> PossibleBorrowerMap<'a, 'tcx> {
let mut map = FxHashMap::default();
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
if is_copy(cx, self.body.local_decls[row].ty) {
continue;
}
let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
borrowers.remove(mir::Local::from_usize(0));
if !borrowers.is_empty() {
map.insert(row, borrowers);
}
}
let bs = BitSet::new_empty(self.body.local_decls.len());
PossibleBorrowerMap {
map,
maybe_live,
bitset: (bs.clone(), bs),
}
}
}
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
let lhs = place.local;
match rvalue {
mir::Rvalue::Ref(_, _, borrowed) => {
self.possible_borrower.add(borrowed.local, lhs);
},
other => {
if ContainsRegion
.visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
.is_continue()
{
return;
}
rvalue_locals(other, |rhs| {
if lhs != rhs {
self.possible_borrower.add(rhs, lhs);
}
});
},
}
}
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
if let mir::TerminatorKind::Call {
args,
destination: mir::Place { local: dest, .. },
..
} = &terminator.kind
{
// TODO add doc
// If the call returns something with lifetimes,
// let's conservatively assume the returned value contains lifetime of all the arguments.
// For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
let mut immutable_borrowers = vec![];
let mut mutable_borrowers = vec![];
for op in args {
match op {
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
mutable_borrowers.push(p.local);
} else {
immutable_borrowers.push(p.local);
}
},
mir::Operand::Constant(..) => (),
}
}
let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
.iter()
.filter_map(|r| self.possible_origin.get(r))
.flat_map(HybridBitSet::iter)
.collect();
if ContainsRegion.visit_ty(self.body.local_decls[*dest].ty).is_break() {
mutable_variables.push(*dest);
}
for y in mutable_variables {
for x in &immutable_borrowers {
self.possible_borrower.add(*x, y);
}
for x in &mutable_borrowers {
self.possible_borrower.add(*x, y);
}
}
}
}
}
/// Collect possible borrowed for every `&mut` local.
/// For example, `_1 = &mut _2` generate _1: {_2,...}
/// Known Problems: not sure all borrowed are tracked
struct PossibleOriginVisitor<'a, 'tcx> {
possible_origin: TransitiveRelation,
body: &'a mir::Body<'tcx>,
}
impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
fn new(body: &'a mir::Body<'tcx>) -> Self {
Self {
possible_origin: TransitiveRelation::default(),
body,
}
}
fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
let mut map = FxHashMap::default();
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
if is_copy(cx, self.body.local_decls[row].ty) {
continue;
}
let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
borrowers.remove(mir::Local::from_usize(0));
if !borrowers.is_empty() {
map.insert(row, borrowers);
}
}
map
}
}
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
let lhs = place.local;
match rvalue {
// Only consider `&mut`, which can modify origin place
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
// _2: &mut _;
// _3 = move _2
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
// _3 = move _2 as &mut _;
mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
=> {
self.possible_origin.add(lhs, borrowed.local);
},
_ => {},
}
}
}
struct ContainsRegion;
impl TypeVisitor<'_> for ContainsRegion {
type BreakTy = ();
fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> {
ControlFlow::BREAK
}
}
fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
let mut visit_op = |op: &mir::Operand<'_>| match op {
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
mir::Operand::Constant(..) => (),
};
match rvalue {
Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
Aggregate(_, ops) => ops.iter().for_each(visit_op),
BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
visit_op(lhs);
visit_op(rhs);
if let Some((
LocalUsage {
local_use_locs: cloned_use_locs,
local_consume_or_mutate_locs: cloned_consume_or_mutate_locs,
},
_ => (),
}
}
/// Result of `PossibleBorrowerVisitor`.
struct PossibleBorrowerMap<'a, 'tcx> {
/// Mapping `Local -> its possible borrowers`
map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>,
// Caches to avoid allocation of `BitSet` on every query
bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
}
impl PossibleBorrowerMap<'_, '_> {
/// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
self.maybe_live.seek_after_primary_effect(at);
self.bitset.0.clear();
let maybe_live = &mut self.maybe_live;
if let Some(bitset) = self.map.get(&borrowed) {
for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
self.bitset.0.insert(b);
}
} else {
return false;
LocalUsage {
local_use_locs: _,
local_consume_or_mutate_locs: clone_consume_or_mutate_locs,
},
)) = visit_local_usage(
&[cloned, clone],
mir,
mir::Location {
block: bb,
statement_index: mir.basic_blocks[bb].statements.len(),
},
)
.map(|mut vec| (vec.remove(0), vec.remove(0)))
{
CloneUsage {
cloned_used: !cloned_use_locs.is_empty(),
cloned_consume_or_mutate_loc: cloned_consume_or_mutate_locs.first().copied(),
// Consider non-temporary clones consumed.
// TODO: Actually check for mutation of non-temporaries.
clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp
|| !clone_consume_or_mutate_locs.is_empty(),
}
self.bitset.1.clear();
for b in borrowers {
self.bitset.1.insert(*b);
} else {
CloneUsage {
cloned_used: true,
cloned_consume_or_mutate_loc: None,
clone_consumed_or_mutated: true,
}
self.bitset.0 == self.bitset.1
}
fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
self.maybe_live.seek_after_primary_effect(at);
self.maybe_live.contains(local)
}
}
#[derive(Default)]
struct TransitiveRelation {
relations: FxHashMap<mir::Local, Vec<mir::Local>>,
}
impl TransitiveRelation {
fn add(&mut self, a: mir::Local, b: mir::Local) {
self.relations.entry(a).or_default().push(b);
}
fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
let mut seen = HybridBitSet::new_empty(domain_size);
let mut stack = vec![a];
while let Some(u) = stack.pop() {
if let Some(edges) = self.relations.get(&u) {
for &v in edges {
if seen.insert(v) {
stack.push(v);
}
}
}
}
seen
}
}

View file

@ -52,7 +52,8 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef {
GenericArg::Type(inner_ty) => Some(inner_ty),
_ => None,
});
if let TyKind::Rptr(_, _) = inner_ty.kind;
if let TyKind::Rptr(_, ref inner_mut_ty) = inner_ty.kind;
if inner_mut_ty.mutbl == Mutability::Not;
then {
span_lint_and_sugg(

View file

@ -106,10 +106,7 @@ impl_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]);
impl<'tcx> LateLintPass<'tcx> for Shadow {
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
let (id, ident) = match pat.kind {
PatKind::Binding(_, hir_id, ident, _) => (hir_id, ident),
_ => return,
};
let PatKind::Binding(_, id, ident, _) = pat.kind else { return };
if pat.span.desugaring_kind().is_some() {
return;

View file

@ -6,11 +6,7 @@ use rustc_span::DUMMY_SP;
// check if the component types of the transmuted collection and the result have different ABI,
// size or alignment
pub(super) fn is_layout_incompatible<'tcx>(
cx: &LateContext<'tcx>,
from: Ty<'tcx>,
to: Ty<'tcx>,
) -> bool {
pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {
if let Ok(from) = cx.tcx.try_normalize_erasing_regions(cx.param_env, from)
&& let Ok(to) = cx.tcx.try_normalize_erasing_regions(cx.param_env, to)
&& let Ok(from_layout) = cx.tcx.layout_of(cx.param_env.and(from))
@ -33,9 +29,7 @@ pub(super) fn can_be_expressed_as_pointer_cast<'tcx>(
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
) -> bool {
use CastKind::{
AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast,
};
use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
matches!(
check_cast(cx, e, from_ty, to_ty),
Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast)
@ -46,20 +40,18 @@ pub(super) fn can_be_expressed_as_pointer_cast<'tcx>(
/// the cast. In certain cases, including some invalid casts from array references
/// to pointers, this may cause additional errors to be emitted and/or ICE error
/// messages. This function will panic if that occurs.
fn check_cast<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
) -> Option<CastKind> {
fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> {
let hir_id = e.hir_id;
let local_def_id = hir_id.owner.def_id;
Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, hir_id);
let fn_ctxt = FnCtxt::new(inherited, cx.param_env, hir_id);
// If we already have errors, we can't be sure we can pointer cast.
assert!(!fn_ctxt.errors_reported_since_creation(), "Newly created FnCtxt contained errors");
assert!(
!fn_ctxt.errors_reported_since_creation(),
"Newly created FnCtxt contained errors"
);
if let Ok(check) = cast::CastCheck::new(
&fn_ctxt, e, from_ty, to_ty,

View file

@ -9,6 +9,7 @@ use rustc_span::symbol::sym;
use super::RC_BUFFER;
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
let app = Applicability::Unspecified;
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
if let Some(alternate) = match_buffer_type(cx, qpath) {
span_lint_and_sugg(
@ -18,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
"usage of `Rc<T>` when T is a buffer type",
"try",
format!("Rc<{alternate}>"),
Applicability::MachineApplicable,
app,
);
} else {
let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
@ -26,15 +27,12 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
return false;
}
let qpath = match &ty.kind {
TyKind::Path(qpath) => qpath,
_ => return false,
};
let TyKind::Path(qpath) = &ty.kind else { return false };
let inner_span = match qpath_generic_tys(qpath).next() {
Some(ty) => ty.span,
None => return false,
};
let mut applicability = Applicability::MachineApplicable;
let mut applicability = app;
span_lint_and_sugg(
cx,
RC_BUFFER,
@ -45,7 +43,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
"Rc<[{}]>",
snippet_with_applicability(cx, inner_span, "..", &mut applicability)
),
Applicability::MachineApplicable,
app,
);
return true;
}
@ -58,22 +56,19 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
"usage of `Arc<T>` when T is a buffer type",
"try",
format!("Arc<{alternate}>"),
Applicability::MachineApplicable,
app,
);
} else if let Some(ty) = qpath_generic_tys(qpath).next() {
let Some(id) = path_def_id(cx, ty) else { return false };
if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
return false;
}
let qpath = match &ty.kind {
TyKind::Path(qpath) => qpath,
_ => return false,
};
let TyKind::Path(qpath) = &ty.kind else { return false };
let inner_span = match qpath_generic_tys(qpath).next() {
Some(ty) => ty.span,
None => return false,
};
let mut applicability = Applicability::MachineApplicable;
let mut applicability = app;
span_lint_and_sugg(
cx,
RC_BUFFER,
@ -84,7 +79,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
"Arc<[{}]>",
snippet_with_applicability(cx, inner_span, "..", &mut applicability)
),
Applicability::MachineApplicable,
app,
);
return true;
}

View file

@ -10,6 +10,7 @@ use rustc_span::symbol::sym;
use super::{utils, REDUNDANT_ALLOCATION};
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
let mut applicability = Applicability::MaybeIncorrect;
let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() {
"Box"
} else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
@ -21,7 +22,6 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
};
if let Some(span) = utils::match_borrows_parameter(cx, qpath) {
let mut applicability = Applicability::MaybeIncorrect;
let generic_snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_then(
cx,
@ -47,9 +47,8 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
_ => return false,
};
let inner_qpath = match &ty.kind {
TyKind::Path(inner_qpath) => inner_qpath,
_ => return false,
let TyKind::Path(inner_qpath) = &ty.kind else {
return false
};
let inner_span = match qpath_generic_tys(inner_qpath).next() {
Some(ty) => {
@ -64,7 +63,6 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
None => return false,
};
if inner_sym == outer_sym {
let mut applicability = Applicability::MaybeIncorrect;
let generic_snippet = snippet_with_applicability(cx, inner_span, "..", &mut applicability);
span_lint_and_then(
cx,

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::{get_trait_def_id, paths};
use if_chain::if_chain;
use rustc_hir::def_id::DefId;
use rustc_hir::{Closure, Expr, ExprKind, StmtKind};
@ -7,7 +6,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{GenericPredicates, PredicateKind, ProjectionPredicate, TraitPredicate};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{BytePos, Span};
use rustc_span::{sym, BytePos, Span};
declare_clippy_lint! {
/// ### What it does
@ -80,7 +79,7 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve
let fn_sig = cx.tcx.fn_sig(def_id);
let generics = cx.tcx.predicates_of(def_id);
let fn_mut_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().fn_mut_trait());
let ord_preds = get_trait_predicates_for_trait_id(cx, generics, get_trait_def_id(cx, &paths::ORD));
let ord_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.get_diagnostic_item(sym::Ord));
let partial_ord_preds =
get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().partial_ord_trait());
// Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error
@ -99,11 +98,15 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve
if trait_pred.self_ty() == inp;
if let Some(return_ty_pred) = get_projection_pred(cx, generics, *trait_pred);
then {
if ord_preds.iter().any(|ord| Some(ord.self_ty()) == return_ty_pred.term.ty()) {
if ord_preds
.iter()
.any(|ord| Some(ord.self_ty()) == return_ty_pred.term.ty())
{
args_to_check.push((i, "Ord".to_string()));
} else if partial_ord_preds.iter().any(|pord| {
pord.self_ty() == return_ty_pred.term.ty().unwrap()
}) {
} else if partial_ord_preds
.iter()
.any(|pord| pord.self_ty() == return_ty_pred.term.ty().unwrap())
{
args_to_check.push((i, "PartialOrd".to_string()));
}
}

View file

@ -163,9 +163,8 @@ fn unnest_or_patterns(pat: &mut P<Pat>) -> bool {
noop_visit_pat(p, self);
// Don't have an or-pattern? Just quit early on.
let alternatives = match &mut p.kind {
Or(ps) => ps,
_ => return,
let Or(alternatives) = &mut p.kind else {
return
};
// Collapse or-patterns directly nested in or-patterns.

View file

@ -47,9 +47,8 @@ declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
let expr = match s.kind {
hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
_ => return,
let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
return
};
match expr.kind {

View file

@ -55,9 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
match e.kind {
ExprKind::Match(_, arms, MatchSource::TryDesugar) => {
let e = match arms[0].body.kind {
ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e,
_ => return,
let (ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e))) = arms[0].body.kind else {
return
};
if let ExprKind::Call(_, [arg, ..]) = e.kind {
self.try_desugar_arm.push(arg.hir_id);

View file

@ -12,6 +12,7 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::{Ident, Symbol};
use std::cell::Cell;
use std::fmt::{Display, Formatter, Write as _};
declare_clippy_lint! {
@ -37,15 +38,13 @@ declare_clippy_lint! {
///
/// ```rust,ignore
/// // ./tests/ui/new_lint.stdout
/// if_chain! {
/// if let ExprKind::If(ref cond, ref then, None) = item.kind,
/// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind,
/// if let ExprKind::Path(ref path) = left.kind,
/// if let ExprKind::Lit(ref lit) = right.kind,
/// if let LitKind::Int(42, _) = lit.node,
/// then {
/// // report your lint here
/// }
/// if ExprKind::If(ref cond, ref then, None) = item.kind
/// && let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind
/// && let ExprKind::Path(ref path) = left.kind
/// && let ExprKind::Lit(ref lit) = right.kind
/// && let LitKind::Int(42, _) = lit.node
/// {
/// // report your lint here
/// }
/// ```
pub LINT_AUTHOR,
@ -91,15 +90,16 @@ macro_rules! field {
};
}
fn prelude() {
println!("if_chain! {{");
}
fn done() {
println!(" then {{");
println!(" // report your lint here");
println!(" }}");
println!("}}");
/// Print a condition of a let chain, `chain!(self, "let Some(x) = y")` will print
/// `if let Some(x) = y` on the first call and ` && let Some(x) = y` thereafter
macro_rules! chain {
($self:ident, $($t:tt)*) => {
if $self.first.take() {
println!("if {}", format_args!($($t)*));
} else {
println!(" && {}", format_args!($($t)*));
}
}
}
impl<'tcx> LateLintPass<'tcx> for Author {
@ -149,9 +149,10 @@ fn check_item(cx: &LateContext<'_>, hir_id: HirId) {
fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) {
if has_attr(cx, hir_id) {
prelude();
f(&PrintVisitor::new(cx));
done();
println!("{{");
println!(" // report your lint here");
println!("}}");
}
}
@ -195,7 +196,9 @@ struct PrintVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
/// Fields are the current index that needs to be appended to pattern
/// binding names
ids: std::cell::Cell<FxHashMap<&'static str, u32>>,
ids: Cell<FxHashMap<&'static str, u32>>,
/// Currently at the first condition in the if chain
first: Cell<bool>,
}
#[allow(clippy::unused_self)]
@ -203,7 +206,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
cx,
ids: std::cell::Cell::default(),
ids: Cell::default(),
first: Cell::new(true),
}
}
@ -226,10 +230,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn option<T: Copy>(&self, option: &Binding<Option<T>>, name: &'static str, f: impl Fn(&Binding<T>)) {
match option.value {
None => out!("if {option}.is_none();"),
None => chain!(self, "{option}.is_none()"),
Some(value) => {
let value = &self.bind(name, value);
out!("if let Some({value}) = {option};");
chain!(self, "let Some({value}) = {option}");
f(value);
},
}
@ -237,9 +241,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn slice<T>(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) {
if slice.value.is_empty() {
out!("if {slice}.is_empty();");
chain!(self, "{slice}.is_empty()");
} else {
out!("if {slice}.len() == {};", slice.value.len());
chain!(self, "{slice}.len() == {}", slice.value.len());
for (i, value) in slice.value.iter().enumerate() {
let name = format!("{slice}[{i}]");
f(&Binding { name, value });
@ -254,23 +258,23 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
}
fn ident(&self, ident: &Binding<Ident>) {
out!("if {ident}.as_str() == {:?};", ident.value.as_str());
chain!(self, "{ident}.as_str() == {:?}", ident.value.as_str());
}
fn symbol(&self, symbol: &Binding<Symbol>) {
out!("if {symbol}.as_str() == {:?};", symbol.value.as_str());
chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
}
fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
if let QPath::LangItem(lang_item, ..) = *qpath.value {
out!("if matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _));");
chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
} else {
out!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath.value));
chain!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath.value));
}
}
fn lit(&self, lit: &Binding<&Lit>) {
let kind = |kind| out!("if let LitKind::{kind} = {lit}.node;");
let kind = |kind| chain!(self, "let LitKind::{kind} = {lit}.node");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
}
@ -298,7 +302,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
LitKind::ByteStr(ref vec) => {
bind!(self, vec);
kind!("ByteStr(ref {vec})");
out!("if let [{:?}] = **{vec};", vec.value);
chain!(self, "let [{:?}] = **{vec}", vec.value);
},
LitKind::Str(s, _) => {
bind!(self, s);
@ -311,15 +315,15 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn arm(&self, arm: &Binding<&hir::Arm<'_>>) {
self.pat(field!(arm.pat));
match arm.value.guard {
None => out!("if {arm}.guard.is_none();"),
None => chain!(self, "{arm}.guard.is_none()"),
Some(hir::Guard::If(expr)) => {
bind!(self, expr);
out!("if let Some(Guard::If({expr})) = {arm}.guard;");
chain!(self, "let Some(Guard::If({expr})) = {arm}.guard");
self.expr(expr);
},
Some(hir::Guard::IfLet(let_expr)) => {
bind!(self, let_expr);
out!("if let Some(Guard::IfLet({let_expr}) = {arm}.guard;");
chain!(self, "let Some(Guard::IfLet({let_expr}) = {arm}.guard");
self.pat(field!(let_expr.pat));
self.expr(field!(let_expr.init));
},
@ -331,9 +335,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn expr(&self, expr: &Binding<&hir::Expr<'_>>) {
if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) {
bind!(self, condition, body);
out!(
"if let Some(higher::While {{ condition: {condition}, body: {body} }}) \
= higher::While::hir({expr});"
chain!(
self,
"let Some(higher::While {{ condition: {condition}, body: {body} }}) \
= higher::While::hir({expr})"
);
self.expr(condition);
self.expr(body);
@ -347,9 +352,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
}) = higher::WhileLet::hir(expr.value)
{
bind!(self, let_pat, let_expr, if_then);
out!(
"if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
= higher::WhileLet::hir({expr});"
chain!(
self,
"let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
= higher::WhileLet::hir({expr})"
);
self.pat(let_pat);
self.expr(let_expr);
@ -359,9 +365,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) {
bind!(self, pat, arg, body);
out!(
"if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
= higher::ForLoop::hir({expr});"
chain!(
self,
"let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
= higher::ForLoop::hir({expr})"
);
self.pat(pat);
self.expr(arg);
@ -369,7 +376,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
return;
}
let kind = |kind| out!("if let ExprKind::{kind} = {expr}.kind;");
let kind = |kind| chain!(self, "let ExprKind::{kind} = {expr}.kind");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
}
@ -383,7 +390,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
// if it's a path
if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
bind!(self, qpath);
out!("if let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind;");
chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
self.qpath(qpath);
}
self.expr(field!(let_expr.init));
@ -419,7 +426,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ExprKind::Binary(op, left, right) => {
bind!(self, op, left, right);
kind!("Binary({op}, {left}, {right})");
out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
chain!(self, "BinOpKind::{:?} == {op}.node", op.value.node);
self.expr(left);
self.expr(right);
},
@ -438,7 +445,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
kind!("Cast({expr}, {cast_ty})");
if let TyKind::Path(ref qpath) = cast_ty.value.kind {
bind!(self, qpath);
out!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;");
chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
self.qpath(qpath);
}
self.expr(expr);
@ -485,7 +492,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
bind!(self, fn_decl, body_id);
kind!("Closure(CaptureBy::{capture_clause:?}, {fn_decl}, {body_id}, _, {movability})");
out!("if let {ret_ty} = {fn_decl}.output;");
chain!(self, "let {ret_ty} = {fn_decl}.output");
self.body(body_id);
},
ExprKind::Yield(sub, source) => {
@ -509,7 +516,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ExprKind::AssignOp(op, target, value) => {
bind!(self, op, target, value);
kind!("AssignOp({op}, {target}, {value})");
out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
chain!(self, "BinOpKind::{:?} == {op}.node", op.value.node);
self.expr(target);
self.expr(value);
},
@ -573,10 +580,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
kind!("Repeat({value}, {length})");
self.expr(value);
match length.value {
ArrayLen::Infer(..) => out!("if let ArrayLen::Infer(..) = length;"),
ArrayLen::Infer(..) => chain!(self, "let ArrayLen::Infer(..) = length"),
ArrayLen::Body(anon_const) => {
bind!(self, anon_const);
out!("if let ArrayLen::Body({anon_const}) = {length};");
chain!(self, "let ArrayLen::Body({anon_const}) = {length}");
self.body(field!(anon_const.body));
},
}
@ -600,12 +607,12 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn body(&self, body_id: &Binding<hir::BodyId>) {
let expr = self.cx.tcx.hir().body(body_id.value).value;
bind!(self, expr);
out!("let {expr} = &cx.tcx.hir().body({body_id}).value;");
chain!(self, "{expr} = &cx.tcx.hir().body({body_id}).value");
self.expr(expr);
}
fn pat(&self, pat: &Binding<&hir::Pat<'_>>) {
let kind = |kind| out!("if let PatKind::{kind} = {pat}.kind;");
let kind = |kind| chain!(self, "let PatKind::{kind} = {pat}.kind");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
}
@ -688,7 +695,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
}
fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) {
let kind = |kind| out!("if let StmtKind::{kind} = {stmt}.kind;");
let kind = |kind| chain!(self, "let StmtKind::{kind} = {stmt}.kind");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::{Crate, ItemKind, ModKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for various things we like to keep tidy in clippy.
///
/// ### Why is this bad?
/// We like to pretend we're an example of tidy code.
///
/// ### Example
/// Wrong ordering of the util::paths constants.
pub CLIPPY_LINTS_INTERNAL,
internal,
"various things that will negatively affect your clippy experience"
}
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
let mut last_name: Option<&str> = None;
for item in items {
let name = item.ident.as_str();
if let Some(last_name) = last_name {
if *last_name > *name {
span_lint(
cx,
CLIPPY_LINTS_INTERNAL,
item.span,
"this constant should be before the previous constant due to lexical \
ordering",
);
}
}
last_name = Some(name);
}
}
}
}
}
}
}

View file

@ -0,0 +1,245 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{is_expr_path_def_path, is_lint_allowed, peel_blocks_with_stmt, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Closure, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::borrow::{Borrow, Cow};
declare_clippy_lint! {
/// ### What it does
/// Lints `span_lint_and_then` function calls, where the
/// closure argument has only one statement and that statement is a method
/// call to `span_suggestion`, `span_help`, `span_note` (using the same
/// span), `help` or `note`.
///
/// These usages of `span_lint_and_then` should be replaced with one of the
/// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
/// `span_lint_and_note`.
///
/// ### Why is this bad?
/// Using the wrapper `span_lint_and_*` functions, is more
/// convenient, readable and less error prone.
///
/// ### Example
/// ```rust,ignore
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
/// diag.span_suggestion(
/// expr.span,
/// help_msg,
/// sugg.to_string(),
/// Applicability::MachineApplicable,
/// );
/// });
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
/// diag.span_help(expr.span, help_msg);
/// });
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
/// diag.help(help_msg);
/// });
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
/// diag.span_note(expr.span, note_msg);
/// });
/// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
/// diag.note(note_msg);
/// });
/// ```
///
/// Use instead:
/// ```rust,ignore
/// span_lint_and_sugg(
/// cx,
/// TEST_LINT,
/// expr.span,
/// lint_msg,
/// help_msg,
/// sugg.to_string(),
/// Applicability::MachineApplicable,
/// );
/// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
/// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
/// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
/// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
/// ```
pub COLLAPSIBLE_SPAN_LINT_CALLS,
internal,
"found collapsible `span_lint_and_then` calls"
}
declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) {
return;
}
if_chain! {
if let ExprKind::Call(func, and_then_args) = expr.kind;
if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
if and_then_args.len() == 5;
if let ExprKind::Closure(&Closure { body, .. }) = &and_then_args[4].kind;
let body = cx.tcx.hir().body(body);
let only_expr = peel_blocks_with_stmt(body.value);
if let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind;
if let ExprKind::Path(..) = recv.kind;
then {
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match ps.ident.as_str() {
"span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
suggest_suggestion(
cx,
expr,
&and_then_snippets,
&span_suggestion_snippets(cx, span_call_args),
);
},
"span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
},
"span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
},
"help" => {
let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
},
"note" => {
let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
},
_ => (),
}
}
}
}
}
struct AndThenSnippets<'a> {
cx: Cow<'a, str>,
lint: Cow<'a, str>,
span: Cow<'a, str>,
msg: Cow<'a, str>,
}
fn get_and_then_snippets<'a, 'hir>(cx: &LateContext<'_>, and_then_snippets: &'hir [Expr<'hir>]) -> AndThenSnippets<'a> {
let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx");
let lint_snippet = snippet(cx, and_then_snippets[1].span, "..");
let span_snippet = snippet(cx, and_then_snippets[2].span, "span");
let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#);
AndThenSnippets {
cx: cx_snippet,
lint: lint_snippet,
span: span_snippet,
msg: msg_snippet,
}
}
struct SpanSuggestionSnippets<'a> {
help: Cow<'a, str>,
sugg: Cow<'a, str>,
applicability: Cow<'a, str>,
}
fn span_suggestion_snippets<'a, 'hir>(
cx: &LateContext<'_>,
span_call_args: &'hir [Expr<'hir>],
) -> SpanSuggestionSnippets<'a> {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
SpanSuggestionSnippets {
help: help_snippet,
sugg: sugg_snippet,
applicability: applicability_snippet,
}
}
fn suggest_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
) {
span_lint_and_sugg(
cx,
COLLAPSIBLE_SPAN_LINT_CALLS,
expr.span,
"this call is collapsible",
"collapse into",
format!(
"span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
and_then_snippets.cx,
and_then_snippets.lint,
and_then_snippets.span,
and_then_snippets.msg,
span_suggestion_snippets.help,
span_suggestion_snippets.sugg,
span_suggestion_snippets.applicability
),
Applicability::MachineApplicable,
);
}
fn suggest_help(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
help: &str,
with_span: bool,
) {
let option_span = if with_span {
format!("Some({})", and_then_snippets.span)
} else {
"None".to_string()
};
span_lint_and_sugg(
cx,
COLLAPSIBLE_SPAN_LINT_CALLS,
expr.span,
"this call is collapsible",
"collapse into",
format!(
"span_lint_and_help({}, {}, {}, {}, {}, {help})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
),
Applicability::MachineApplicable,
);
}
fn suggest_note(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
note: &str,
with_span: bool,
) {
let note_span = if with_span {
format!("Some({})", and_then_snippets.span)
} else {
"None".to_string()
};
span_lint_and_sugg(
cx,
COLLAPSIBLE_SPAN_LINT_CALLS,
expr.span,
"this call is collapsible",
"collapse into",
format!(
"span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
),
Applicability::MachineApplicable,
);
}

View file

@ -0,0 +1,77 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::match_type;
use clippy_utils::{is_lint_allowed, paths};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `cx.span_lint*` and suggests to use the `utils::*`
/// variant of the function.
///
/// ### Why is this bad?
/// The `utils::*` variants also add a link to the Clippy documentation to the
/// warning/error messages.
///
/// ### Example
/// ```rust,ignore
/// cx.span_lint(LINT_NAME, "message");
/// ```
///
/// Use instead:
/// ```rust,ignore
/// utils::span_lint(cx, LINT_NAME, "message");
/// ```
pub COMPILER_LINT_FUNCTIONS,
internal,
"usage of the lint functions of the compiler instead of the utils::* variant"
}
impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]);
#[derive(Clone, Default)]
pub struct CompilerLintFunctions {
map: FxHashMap<&'static str, &'static str>,
}
impl CompilerLintFunctions {
#[must_use]
pub fn new() -> Self {
let mut map = FxHashMap::default();
map.insert("span_lint", "utils::span_lint");
map.insert("struct_span_lint", "utils::span_lint");
map.insert("lint", "utils::span_lint");
map.insert("span_lint_note", "utils::span_lint_and_note");
map.insert("span_lint_help", "utils::span_lint_and_help");
Self { map }
}
}
impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if is_lint_allowed(cx, COMPILER_LINT_FUNCTIONS, expr.hir_id) {
return;
}
if_chain! {
if let ExprKind::MethodCall(path, self_arg, _, _) = &expr.kind;
let fn_name = path.ident;
if let Some(sugg) = self.map.get(fn_name.as_str());
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if match_type(cx, ty, &paths::EARLY_CONTEXT) || match_type(cx, ty, &paths::LATE_CONTEXT);
then {
span_lint_and_help(
cx,
COMPILER_LINT_FUNCTIONS,
path.ident.span,
"usage of a compiler lint function",
None,
&format!("please use the Clippy variant of this function: `{sugg}`"),
);
}
}
}
}

View file

@ -0,0 +1,164 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::{higher, is_else_clause, is_expn_of};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Local, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{BytePos, Span};
declare_clippy_lint! {
/// Finds unidiomatic usage of `if_chain!`
pub IF_CHAIN_STYLE,
internal,
"non-idiomatic `if_chain!` usage"
}
declare_lint_pass!(IfChainStyle => [IF_CHAIN_STYLE]);
impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let (local, after, if_chain_span) = if_chain! {
if let [Stmt { kind: StmtKind::Local(local), .. }, after @ ..] = block.stmts;
if let Some(if_chain_span) = is_expn_of(block.span, "if_chain");
then { (local, after, if_chain_span) } else { return }
};
if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be above the `if_chain!`",
);
} else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be inside `then { .. }`",
);
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let (cond, then, els) = if let Some(higher::IfOrIfLet { cond, r#else, then }) = higher::IfOrIfLet::hir(expr) {
(cond, then, r#else.is_some())
} else {
return;
};
let ExprKind::Block(then_block, _) = then.kind else { return };
let if_chain_span = is_expn_of(expr.span, "if_chain");
if !els {
check_nested_if_chains(cx, expr, then_block, if_chain_span);
}
let Some(if_chain_span) = if_chain_span else { return };
// check for `if a && b;`
if_chain! {
if let ExprKind::Binary(op, _, _) = cond.kind;
if op.node == BinOpKind::And;
if cx.sess().source_map().is_multiline(cond.span);
then {
span_lint(cx, IF_CHAIN_STYLE, cond.span, "`if a && b;` should be `if a; if b;`");
}
}
if is_first_if_chain_expr(cx, expr.hir_id, if_chain_span)
&& is_if_chain_then(then_block.stmts, then_block.expr, if_chain_span)
{
span_lint(cx, IF_CHAIN_STYLE, expr.span, "`if_chain!` only has one `if`");
}
}
}
fn check_nested_if_chains(
cx: &LateContext<'_>,
if_expr: &Expr<'_>,
then_block: &Block<'_>,
if_chain_span: Option<Span>,
) {
#[rustfmt::skip]
let (head, tail) = match *then_block {
Block { stmts, expr: Some(tail), .. } => (stmts, tail),
Block {
stmts: &[
ref head @ ..,
Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
],
..
} => (head, tail),
_ => return,
};
if_chain! {
if let Some(higher::IfOrIfLet { r#else: None, .. }) = higher::IfOrIfLet::hir(tail);
let sm = cx.sess().source_map();
if head
.iter()
.all(|stmt| matches!(stmt.kind, StmtKind::Local(..)) && !sm.is_multiline(stmt.span));
if if_chain_span.is_some() || !is_else_clause(cx.tcx, if_expr);
then {
} else {
return;
}
}
let (span, msg) = match (if_chain_span, is_expn_of(tail.span, "if_chain")) {
(None, Some(_)) => (if_expr.span, "this `if` can be part of the inner `if_chain!`"),
(Some(_), None) => (tail.span, "this `if` can be part of the outer `if_chain!`"),
(Some(a), Some(b)) if a != b => (b, "this `if_chain!` can be merged with the outer `if_chain!`"),
_ => return,
};
span_lint_and_then(cx, IF_CHAIN_STYLE, span, msg, |diag| {
let (span, msg) = match head {
[] => return,
[stmt] => (stmt.span, "this `let` statement can also be in the `if_chain!`"),
[a, .., b] => (
a.span.to(b.span),
"these `let` statements can also be in the `if_chain!`",
),
};
diag.span_help(span, msg);
});
}
fn is_first_if_chain_expr(cx: &LateContext<'_>, hir_id: HirId, if_chain_span: Span) -> bool {
cx.tcx
.hir()
.parent_iter(hir_id)
.find(|(_, node)| {
#[rustfmt::skip]
!matches!(node, Node::Expr(Expr { kind: ExprKind::Block(..), .. }) | Node::Stmt(_))
})
.map_or(false, |(id, _)| {
is_expn_of(cx.tcx.hir().span(id), "if_chain") != Some(if_chain_span)
})
}
/// Checks a trailing slice of statements and expression of a `Block` to see if they are part
/// of the `then {..}` portion of an `if_chain!`
fn is_if_chain_then(stmts: &[Stmt<'_>], expr: Option<&Expr<'_>>, if_chain_span: Span) -> bool {
let span = if let [stmt, ..] = stmts {
stmt.span
} else if let Some(expr) = expr {
expr.span
} else {
// empty `then {}`
return true;
};
is_expn_of(span, "if_chain").map_or(true, |span| span != if_chain_span)
}
/// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: Span) -> Span {
let mut span = local.pat.span;
if let Some(init) = local.init {
span = span.to(init.span);
}
span.adjust(if_chain_span.ctxt().outer_expn());
let sm = cx.sess().source_map();
let span = sm.span_extend_to_prev_str(span, "let", false, true).unwrap_or(span);
let span = sm.span_extend_to_next_char(span, ';', false);
Span::new(
span.lo() - BytePos(3),
span.hi() + BytePos(1),
span.ctxt(),
span.parent(),
)
}

View file

@ -0,0 +1,239 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::match_type;
use clippy_utils::{def_path_res, is_expn_of, match_def_path, paths};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::ty::{self};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
/// Checks for interning symbols that have already been pre-interned and defined as constants.
///
/// ### Why is this bad?
/// It's faster and easier to use the symbol constant.
///
/// ### Example
/// ```rust,ignore
/// let _ = sym!(f32);
/// ```
///
/// Use instead:
/// ```rust,ignore
/// let _ = sym::f32;
/// ```
pub INTERNING_DEFINED_SYMBOL,
internal,
"interning a symbol that is pre-interned and defined as a constant"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for unnecessary conversion from Symbol to a string.
///
/// ### Why is this bad?
/// It's faster use symbols directly instead of strings.
///
/// ### Example
/// ```rust,ignore
/// symbol.as_str() == "clippy";
/// ```
///
/// Use instead:
/// ```rust,ignore
/// symbol == sym::clippy;
/// ```
pub UNNECESSARY_SYMBOL_STR,
internal,
"unnecessary conversion between Symbol and string"
}
#[derive(Default)]
pub struct InterningDefinedSymbol {
// Maps the symbol value to the constant DefId.
symbol_map: FxHashMap<u32, DefId>,
}
impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]);
impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
fn check_crate(&mut self, cx: &LateContext<'_>) {
if !self.symbol_map.is_empty() {
return;
}
for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
if let Some(def_id) = def_path_res(cx, module, None).opt_def_id() {
for item in cx.tcx.module_children(def_id).iter() {
if_chain! {
if let Res::Def(DefKind::Const, item_def_id) = item.res;
let ty = cx.tcx.type_of(item_def_id);
if match_type(cx, ty, &paths::SYMBOL);
if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id);
if let Ok(value) = value.to_u32();
then {
self.symbol_map.insert(value, item_def_id);
}
}
}
}
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(func, [arg]) = &expr.kind;
if let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind();
if match_def_path(cx, *def_id, &paths::SYMBOL_INTERN);
if let Some(Constant::Str(arg)) = constant_simple(cx, cx.typeck_results(), arg);
let value = Symbol::intern(&arg).as_u32();
if let Some(&def_id) = self.symbol_map.get(&value);
then {
span_lint_and_sugg(
cx,
INTERNING_DEFINED_SYMBOL,
is_expn_of(expr.span, "sym").unwrap_or(expr.span),
"interning a defined symbol",
"try",
cx.tcx.def_path_str(def_id),
Applicability::MachineApplicable,
);
}
}
if let ExprKind::Binary(op, left, right) = expr.kind {
if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) {
let data = [
(left, self.symbol_str_expr(left, cx)),
(right, self.symbol_str_expr(right, cx)),
];
match data {
// both operands are a symbol string
[(_, Some(left)), (_, Some(right))] => {
span_lint_and_sugg(
cx,
UNNECESSARY_SYMBOL_STR,
expr.span,
"unnecessary `Symbol` to string conversion",
"try",
format!(
"{} {} {}",
left.as_symbol_snippet(cx),
op.node.as_str(),
right.as_symbol_snippet(cx),
),
Applicability::MachineApplicable,
);
},
// one of the operands is a symbol string
[(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => {
// creating an owned string for comparison
if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) {
span_lint_and_sugg(
cx,
UNNECESSARY_SYMBOL_STR,
expr.span,
"unnecessary string allocation",
"try",
format!("{}.as_str()", symbol.as_symbol_snippet(cx)),
Applicability::MachineApplicable,
);
}
},
// nothing found
[(_, None), (_, None)] => {},
}
}
}
}
}
impl InterningDefinedSymbol {
fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD];
static SYMBOL_STR_PATHS: &[&[&str]] = &[
&paths::SYMBOL_AS_STR,
&paths::SYMBOL_TO_IDENT_STRING,
&paths::TO_STRING_METHOD,
];
let call = if_chain! {
if let ExprKind::AddrOf(_, _, e) = expr.kind;
if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
then { e } else { expr }
};
if_chain! {
// is a method call
if let ExprKind::MethodCall(_, item, [], _) = call.kind;
if let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id);
let ty = cx.typeck_results().expr_ty(item);
// ...on either an Ident or a Symbol
if let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) {
Some(false)
} else if match_type(cx, ty, &paths::IDENT) {
Some(true)
} else {
None
};
// ...which converts it to a string
let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path));
then {
let is_to_owned = path.last().unwrap().ends_with("string");
return Some(SymbolStrExpr::Expr {
item,
is_ident,
is_to_owned,
});
}
}
// is a string constant
if let Some(Constant::Str(s)) = constant_simple(cx, cx.typeck_results(), expr) {
let value = Symbol::intern(&s).as_u32();
// ...which matches a symbol constant
if let Some(&def_id) = self.symbol_map.get(&value) {
return Some(SymbolStrExpr::Const(def_id));
}
}
None
}
}
enum SymbolStrExpr<'tcx> {
/// a string constant with a corresponding symbol constant
Const(DefId),
/// a "symbol to string" expression like `symbol.as_str()`
Expr {
/// part that evaluates to `Symbol` or `Ident`
item: &'tcx Expr<'tcx>,
is_ident: bool,
/// whether an owned `String` is created like `to_ident_string()`
is_to_owned: bool,
},
}
impl<'tcx> SymbolStrExpr<'tcx> {
/// Returns a snippet that evaluates to a `Symbol` and is const if possible
fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> {
match *self {
Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(),
Self::Expr { item, is_ident, .. } => {
let mut snip = snippet(cx, item.span.source_callsite(), "..");
if is_ident {
// get `Ident.name`
snip.to_mut().push_str(".name");
}
snip
},
}
}
}

View file

@ -0,0 +1,108 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::def_path_res;
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::Item;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, fast_reject::SimplifiedTypeGen, FloatTy};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
declare_clippy_lint! {
/// ### What it does
/// Checks the paths module for invalid paths.
///
/// ### Why is this bad?
/// It indicates a bug in the code.
///
/// ### Example
/// None.
pub INVALID_PATHS,
internal,
"invalid path"
}
declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let local_def_id = &cx.tcx.parent_module(item.hir_id());
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! {
if mod_name.as_str() == "paths";
if let hir::ItemKind::Const(ty, body_id) = item.kind;
let ty = hir_ty_to_ty(cx.tcx, ty);
if let ty::Array(el_ty, _) = &ty.kind();
if let ty::Ref(_, el_ty, _) = &el_ty.kind();
if el_ty.is_str();
let body = cx.tcx.hir().body(body_id);
let typeck_results = cx.tcx.typeck_body(body_id);
if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
let path: Vec<&str> = path
.iter()
.map(|x| {
if let Constant::Str(s) = x {
s.as_str()
} else {
// We checked the type of the constant above
unreachable!()
}
})
.collect();
if !check_path(cx, &path[..]);
then {
span_lint(cx, INVALID_PATHS, item.span, "invalid path");
}
}
}
}
// This is not a complete resolver for paths. It works on all the paths currently used in the paths
// module. That's all it does and all it needs to do.
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
if def_path_res(cx, path, None) != Res::Err {
return true;
}
// Some implementations can't be found by `path_to_res`, particularly inherent
// implementations of native types. Check lang items.
let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
let lang_items = cx.tcx.lang_items();
// This list isn't complete, but good enough for our current list of paths.
let incoherent_impls = [
SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F32),
SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F64),
SimplifiedTypeGen::SliceSimplifiedType,
SimplifiedTypeGen::StrSimplifiedType,
]
.iter()
.flat_map(|&ty| cx.tcx.incoherent_impls(ty));
for item_def_id in lang_items.items().iter().flatten().chain(incoherent_impls) {
let lang_item_path = cx.get_def_path(*item_def_id);
if path_syms.starts_with(&lang_item_path) {
if let [item] = &path_syms[lang_item_path.len()..] {
if matches!(
cx.tcx.def_kind(*item_def_id),
DefKind::Mod | DefKind::Enum | DefKind::Trait
) {
for child in cx.tcx.module_children(*item_def_id) {
if child.ident.name == *item {
return true;
}
}
} else {
for child in cx.tcx.associated_item_def_ids(*item_def_id) {
if cx.tcx.item_name(*child) == *item {
return true;
}
}
}
}
}
}
false
}

View file

@ -0,0 +1,342 @@
use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::{is_lint_allowed, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast as ast;
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Spanned;
use rustc_span::symbol::Symbol;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Ensures every lint is associated to a `LintPass`.
///
/// ### Why is this bad?
/// The compiler only knows lints via a `LintPass`. Without
/// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
/// know the name of the lint.
///
/// ### Known problems
/// Only checks for lints associated using the
/// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
///
/// ### Example
/// ```rust,ignore
/// declare_lint! { pub LINT_1, ... }
/// declare_lint! { pub LINT_2, ... }
/// declare_lint! { pub FORGOTTEN_LINT, ... }
/// // ...
/// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
/// // missing FORGOTTEN_LINT
/// ```
pub LINT_WITHOUT_LINT_PASS,
internal,
"declaring a lint without associating it in a LintPass"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for cases of an auto-generated lint without an updated description,
/// i.e. `default lint description`.
///
/// ### Why is this bad?
/// Indicates that the lint is not finished.
///
/// ### Example
/// ```rust,ignore
/// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
/// ```
///
/// Use instead:
/// ```rust,ignore
/// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
/// ```
pub DEFAULT_LINT,
internal,
"found 'default lint description' in a lint declaration"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for invalid `clippy::version` attributes.
///
/// Valid values are:
/// * "pre 1.29.0"
/// * any valid semantic version
pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
internal,
"found an invalid `clippy::version` attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for declared clippy lints without the `clippy::version` attribute.
///
pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
internal,
"found clippy lint without `clippy::version` attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for cases of an auto-generated deprecated lint without an updated reason,
/// i.e. `"default deprecation note"`.
///
/// ### Why is this bad?
/// Indicates that the documentation is incomplete.
///
/// ### Example
/// ```rust,ignore
/// declare_deprecated_lint! {
/// /// ### What it does
/// /// Nothing. This lint has been deprecated.
/// ///
/// /// ### Deprecation reason
/// /// TODO
/// #[clippy::version = "1.63.0"]
/// pub COOL_LINT,
/// "default deprecation note"
/// }
/// ```
///
/// Use instead:
/// ```rust,ignore
/// declare_deprecated_lint! {
/// /// ### What it does
/// /// Nothing. This lint has been deprecated.
/// ///
/// /// ### Deprecation reason
/// /// This lint has been replaced by `cooler_lint`
/// #[clippy::version = "1.63.0"]
/// pub COOL_LINT,
/// "this lint has been replaced by `cooler_lint`"
/// }
/// ```
pub DEFAULT_DEPRECATION_REASON,
internal,
"found 'default deprecation note' in a deprecated lint declaration"
}
#[derive(Clone, Debug, Default)]
pub struct LintWithoutLintPass {
declared_lints: FxHashMap<Symbol, Span>,
registered_lints: FxHashSet<Symbol>,
}
impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
|| is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
{
return;
}
if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
let is_lint_ref_ty = is_lint_ref_type(cx, ty);
if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
check_invalid_clippy_version_attribute(cx, item);
let expr = &cx.tcx.hir().body(body_id).value;
let fields;
if is_lint_ref_ty {
if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
&& let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind {
fields = struct_fields;
} else {
return;
}
} else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
fields = struct_fields;
} else {
return;
}
let field = fields
.iter()
.find(|f| f.ident.as_str() == "desc")
.expect("lints must have a description field");
if let ExprKind::Lit(Spanned {
node: LitKind::Str(ref sym, _),
..
}) = field.expr.kind
{
let sym_str = sym.as_str();
if is_lint_ref_ty {
if sym_str == "default lint description" {
span_lint(
cx,
DEFAULT_LINT,
item.span,
&format!("the lint `{}` has the default lint description", item.ident.name),
);
}
self.declared_lints.insert(item.ident.name, item.span);
} else if sym_str == "default deprecation note" {
span_lint(
cx,
DEFAULT_DEPRECATION_REASON,
item.span,
&format!("the lint `{}` has the default deprecation reason", item.ident.name),
);
}
}
}
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
if !matches!(
cx.tcx.item_name(macro_call.def_id).as_str(),
"impl_lint_pass" | "declare_lint_pass"
) {
return;
}
if let hir::ItemKind::Impl(hir::Impl {
of_trait: None,
items: impl_item_refs,
..
}) = item.kind
{
let mut collector = LintCollector {
output: &mut self.registered_lints,
cx,
};
let body_id = cx.tcx.hir().body_owned_by(
cx.tcx.hir().local_def_id(
impl_item_refs
.iter()
.find(|iiref| iiref.ident.as_str() == "get_lints")
.expect("LintPass needs to implement get_lints")
.id
.hir_id(),
),
);
collector.visit_expr(cx.tcx.hir().body(body_id).value);
}
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
return;
}
for (lint_name, &lint_span) in &self.declared_lints {
// When using the `declare_tool_lint!` macro, the original `lint_span`'s
// file points to "<rustc macros>".
// `compiletest-rs` thinks that's an error in a different file and
// just ignores it. This causes the test in compile-fail/lint_pass
// not able to capture the error.
// Therefore, we need to climb the macro expansion tree and find the
// actual span that invoked `declare_tool_lint!`:
let lint_span = lint_span.ctxt().outer_expn_data().call_site;
if !self.registered_lints.contains(lint_name) {
span_lint(
cx,
LINT_WITHOUT_LINT_PASS,
lint_span,
&format!("the lint `{lint_name}` is not added to any `LintPass`"),
);
}
}
}
}
pub(super) fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &hir::Ty<'_>) -> bool {
if let TyKind::Rptr(
_,
MutTy {
ty: inner,
mutbl: Mutability::Not,
},
) = ty.kind
{
if let TyKind::Path(ref path) = inner.kind {
if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
return match_def_path(cx, def_id, &paths::LINT);
}
}
}
false
}
fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
if let Some(value) = extract_clippy_version_value(cx, item) {
// The `sym!` macro doesn't work as it only expects a single token.
// It's better to keep it this way and have a direct `Symbol::intern` call here.
if value == Symbol::intern("pre 1.29.0") {
return;
}
if RustcVersion::parse(value.as_str()).is_err() {
span_lint_and_help(
cx,
INVALID_CLIPPY_VERSION_ATTRIBUTE,
item.span,
"this item has an invalid `clippy::version` attribute",
None,
"please use a valid semantic version, see `doc/adding_lints.md`",
);
}
} else {
span_lint_and_help(
cx,
MISSING_CLIPPY_VERSION_ATTRIBUTE,
item.span,
"this lint is missing the `clippy::version` attribute or version value",
None,
"please use a `clippy::version` attribute, see `doc/adding_lints.md`",
);
}
}
/// This function extracts the version value of a `clippy::version` attribute if the given value has
/// one
pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
let attrs = cx.tcx.hir().attrs(item.hir_id());
attrs.iter().find_map(|attr| {
if_chain! {
// Identify attribute
if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind;
if let [tool_name, attr_name] = &attr_kind.item.path.segments[..];
if tool_name.ident.name == sym::clippy;
if attr_name.ident.name == sym::version;
if let Some(version) = attr.value_str();
then { Some(version) } else { None }
}
})
}
struct LintCollector<'a, 'tcx> {
output: &'a mut FxHashSet<Symbol>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) {
if path.segments.len() == 1 {
self.output.insert(path.segments[0].ident.name);
}
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}

View file

@ -8,7 +8,7 @@
//! a simple mistake)
use crate::renamed_lints::RENAMED_LINTS;
use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type};
use crate::utils::internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
@ -532,7 +532,11 @@ fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
// Extract lints
doc_comment.make_ascii_lowercase();
let lints: Vec<String> = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect();
let lints: Vec<String> = doc_comment
.split_off(DOC_START.len())
.split(", ")
.map(str::to_string)
.collect();
// Format documentation correctly
// split off leading `.` from lint name list and indent for correct formatting

View file

@ -0,0 +1,63 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::match_type;
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, subst::GenericArgKind};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
///
pub MISSING_MSRV_ATTR_IMPL,
internal,
"checking if all necessary steps were taken when adding a MSRV to a lint"
}
declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
impl LateLintPass<'_> for MsrvAttrImpl {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! {
if let hir::ItemKind::Impl(hir::Impl {
of_trait: Some(lint_pass_trait_ref),
self_ty,
items,
..
}) = &item.kind;
if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
if let ty::Adt(self_ty_def, _) = self_ty.kind();
if self_ty_def.is_struct();
if self_ty_def.all_fields().any(|f| {
cx.tcx
.type_of(f.did)
.walk()
.filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
.any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
});
if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
then {
let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
let span = cx.sess().source_map().span_through_char(item.span, '{');
span_lint_and_sugg(
cx,
MISSING_MSRV_ATTR_IMPL,
span,
&format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
&format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
format!("{}\n extract_msrv_attr!({context});", snippet(cx, span, "..")),
Applicability::MachineApplicable,
);
}
}
}
}

View file

@ -0,0 +1,62 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::match_type;
use clippy_utils::{is_lint_allowed, method_calls, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `cx.outer().expn_data()` and suggests to use
/// the `cx.outer_expn_data()`
///
/// ### Why is this bad?
/// `cx.outer_expn_data()` is faster and more concise.
///
/// ### Example
/// ```rust,ignore
/// expr.span.ctxt().outer().expn_data()
/// ```
///
/// Use instead:
/// ```rust,ignore
/// expr.span.ctxt().outer_expn_data()
/// ```
pub OUTER_EXPN_EXPN_DATA,
internal,
"using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
}
declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]);
impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if is_lint_allowed(cx, OUTER_EXPN_EXPN_DATA, expr.hir_id) {
return;
}
let (method_names, arg_lists, spans) = method_calls(expr, 2);
let method_names: Vec<&str> = method_names.iter().map(Symbol::as_str).collect();
if_chain! {
if let ["expn_data", "outer_expn"] = method_names.as_slice();
let (self_arg, args) = arg_lists[1];
if args.is_empty();
let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT);
then {
span_lint_and_sugg(
cx,
OUTER_EXPN_EXPN_DATA,
spans[1].with_hi(expr.span.hi()),
"usage of `outer_expn().expn_data()`",
"try",
"outer_expn_data()".to_string(),
Applicability::MachineApplicable,
);
}
}
}
}

View file

@ -0,0 +1,37 @@
use rustc_ast::ast::NodeId;
use rustc_ast::visit::FnKind;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Not an actual lint. This lint is only meant for testing our customized internal compiler
/// error message by calling `panic`.
///
/// ### Why is this bad?
/// ICE in large quantities can damage your teeth
///
/// ### Example
/// ```rust,ignore
/// 🍦🍦🍦🍦🍦
/// ```
pub PRODUCE_ICE,
internal,
"this message should not appear anywhere as we ICE before and don't emit the lint"
}
declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
impl EarlyLintPass for ProduceIce {
fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
assert!(!is_trigger_fn(fn_kind), "Would you like some help with that?");
}
}
fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
match fn_kind {
FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
FnKind::Closure(..) => false,
}
}

View file

@ -0,0 +1,343 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{def_path_res, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Namespace, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
use rustc_middle::ty::{self, AssocKind, DefIdTree, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::Span;
use std::str;
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
///
/// ### Why is this bad?
/// The path for an item is subject to change and is less efficient to look up than a
/// diagnostic item or a `LangItem`.
///
/// ### Example
/// ```rust,ignore
/// utils::match_type(cx, ty, &paths::VEC)
/// ```
///
/// Use instead:
/// ```rust,ignore
/// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
/// ```
pub UNNECESSARY_DEF_PATH,
internal,
"using a def path when a diagnostic item or a `LangItem` is available"
}
impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
#[derive(Default)]
pub struct UnnecessaryDefPath {
array_def_ids: FxHashSet<(DefId, Span)>,
linted_def_ids: FxHashSet<DefId>,
}
impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
return;
}
match expr.kind {
ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span),
ExprKind::Array(elements) => self.check_array(cx, elements, expr.span),
_ => {},
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for &(def_id, span) in &self.array_def_ids {
if self.linted_def_ids.contains(&def_id) {
continue;
}
let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) {
("diagnostic item", format!("sym::{sym}"))
} else if let Some(sym) = get_lang_item_name(cx, def_id) {
("language item", format!("LangItem::{sym}"))
} else {
continue;
};
span_lint_and_help(
cx,
UNNECESSARY_DEF_PATH,
span,
&format!("hardcoded path to a {msg}"),
None,
&format!("convert all references to use `{sugg}`"),
);
}
}
}
impl UnnecessaryDefPath {
#[allow(clippy::too_many_lines)]
fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) {
enum Item {
LangItem(Symbol),
DiagnosticItem(Symbol),
}
static PATHS: &[&[&str]] = &[
&["clippy_utils", "match_def_path"],
&["clippy_utils", "match_trait_method"],
&["clippy_utils", "ty", "match_type"],
&["clippy_utils", "is_expr_path_def_path"],
];
if_chain! {
if let [cx_arg, def_arg, args @ ..] = args;
if let ExprKind::Path(path) = &func.kind;
if let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if let Some(which_path) = match_any_def_paths(cx, id, PATHS);
let item_arg = if which_path == 4 { &args[1] } else { &args[0] };
// Extract the path to the matched type
if let Some(segments) = path_to_matched_type(cx, item_arg);
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
if let Some(def_id) = inherent_def_path_res(cx, &segments[..]);
then {
// Check if the target item is a diagnostic item or LangItem.
#[rustfmt::skip]
let (msg, item) = if let Some(item_name)
= cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id)
{
(
"use of a def path to a diagnostic item",
Item::DiagnosticItem(*item_name),
)
} else if let Some(item_name) = get_lang_item_name(cx, def_id) {
(
"use of a def path to a `LangItem`",
Item::LangItem(item_name),
)
} else {
return;
};
let has_ctor = match cx.tcx.def_kind(def_id) {
DefKind::Struct => {
let variant = cx.tcx.adt_def(def_id).non_enum_variant();
variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
},
DefKind::Variant => {
let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id);
variant.ctor_def_id.is_some() && variant.fields.iter().all(|f| f.vis.is_public())
},
_ => false,
};
let mut app = Applicability::MachineApplicable;
let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app);
let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app);
let (sugg, with_note) = match (which_path, item) {
// match_def_path
(0, Item::DiagnosticItem(item)) => (
format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"),
has_ctor,
),
(0, Item::LangItem(item)) => (
format!("{cx_snip}.tcx.lang_items().require(LangItem::{item}).ok() == Some({def_snip})"),
has_ctor,
),
// match_trait_method
(1, Item::DiagnosticItem(item)) => {
(format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false)
},
// match_type
(2, Item::DiagnosticItem(item)) => (
format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
false,
),
(2, Item::LangItem(item)) => (
format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"),
false,
),
// is_expr_path_def_path
(3, Item::DiagnosticItem(item)) if has_ctor => (
format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",),
false,
),
(3, Item::LangItem(item)) if has_ctor => (
format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",),
false,
),
(3, Item::DiagnosticItem(item)) => (
format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"),
false,
),
(3, Item::LangItem(item)) => (
format!(
"path_res({cx_snip}, {def_snip}).opt_def_id()\
.map_or(false, |id| {cx_snip}.tcx.lang_items().require(LangItem::{item}).ok() == Some(id))",
),
false,
),
_ => return,
};
span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| {
diag.span_suggestion(span, "try", sugg, app);
if with_note {
diag.help(
"if this `DefId` came from a constructor expression or pattern then the \
parent `DefId` should be used instead",
);
}
});
self.linted_def_ids.insert(def_id);
}
}
}
fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) {
let Some(path) = path_from_array(elements) else { return };
if let Some(def_id) = inherent_def_path_res(cx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) {
self.array_def_ids.insert((def_id, span));
}
}
}
fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<String>> {
match peel_hir_expr_refs(expr).0.kind {
ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) {
Res::Local(hir_id) => {
let parent_id = cx.tcx.hir().get_parent_node(hir_id);
if let Some(Node::Local(Local { init: Some(init), .. })) = cx.tcx.hir().find(parent_id) {
path_to_matched_type(cx, init)
} else {
None
}
},
Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
cx,
cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
cx.tcx.type_of(def_id),
),
Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => {
read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id))
},
_ => None,
},
_ => None,
},
ExprKind::Array(exprs) => path_from_array(exprs),
_ => None,
}
}
fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> {
let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() {
let &alloc = alloc.provenance().values().next()?;
if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
(alloc.inner(), ty)
} else {
return None;
}
} else {
(alloc, ty)
};
if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
&& let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
&& ty.is_str()
{
alloc
.provenance()
.values()
.map(|&alloc| {
if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
let alloc = alloc.inner();
str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()))
.ok().map(ToOwned::to_owned)
} else {
None
}
})
.collect()
} else {
None
}
}
fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> {
exprs
.iter()
.map(|expr| {
if let ExprKind::Lit(lit) = &expr.kind {
if let LitKind::Str(sym, _) = lit.node {
return Some((*sym.as_str()).to_owned());
}
}
None
})
.collect()
}
// def_path_res will match field names before anything else, but for this we want to match
// inherent functions first.
fn inherent_def_path_res(cx: &LateContext<'_>, segments: &[&str]) -> Option<DefId> {
def_path_res(cx, segments, None).opt_def_id().map(|def_id| {
if cx.tcx.def_kind(def_id) == DefKind::Field {
let method_name = *segments.last().unwrap();
cx.tcx
.def_key(def_id)
.parent
.and_then(|parent_idx| {
cx.tcx
.inherent_impls(DefId {
index: parent_idx,
krate: def_id.krate,
})
.iter()
.find_map(|impl_id| {
cx.tcx.associated_items(*impl_id).find_by_name_and_kind(
cx.tcx,
Ident::from_str(method_name),
AssocKind::Fn,
*impl_id,
)
})
})
.map_or(def_id, |item| item.def_id)
} else {
def_id
}
})
}
fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<Symbol> {
if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) {
let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id();
let item_name = cx
.tcx
.adt_def(lang_items)
.variants()
.iter()
.nth(lang_item)
.unwrap()
.name;
Some(item_name)
} else {
None
}
}

View file

@ -136,7 +136,7 @@ pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'s
.emit();
},
ast::AttrStyle::Outer => {
sess.span_err(attr.span, &format!("`{name}` cannot be an outer attribute"));
sess.span_err(attr.span, format!("`{name}` cannot be an outer attribute"));
},
}
}

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