mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-10 07:04:18 +00:00
Merge commit '4f142aa1058f14f153f8bfd2d82f04ddb9982388' into clippyup
This commit is contained in:
parent
2ed404937f
commit
cd0bb7de01
284 changed files with 8555 additions and 4250 deletions
1
.github/workflows/clippy.yml
vendored
1
.github/workflows/clippy.yml
vendored
|
@ -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:
|
||||
|
|
1
.github/workflows/clippy_bors.yml
vendored
1
.github/workflows/clippy_bors.yml
vendored
|
@ -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:
|
||||
|
|
2
.github/workflows/clippy_dev.yml
vendored
2
.github/workflows/clippy_dev.yml
vendored
|
@ -15,6 +15,8 @@ on:
|
|||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_UNSTABLE_SPARSE_REGISTRY: true
|
||||
|
||||
jobs:
|
||||
clippy_dev:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
38
clippy_lints/src/casts/as_ptr_cast_mut.rs
Normal file
38
clippy_lints/src/casts/as_ptr_cast_mut.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
28
clippy_lints/src/casts/cast_nan_to_int.rs
Normal file
28
clippy_lints/src/casts/cast_nan_to_int.rs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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() =>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
153
clippy_lints/src/matches/manual_filter.rs
Normal file
153
clippy_lints/src/matches/manual_filter.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
277
clippy_lints/src/matches/manual_utils.rs
Normal file
277
clippy_lints/src/matches/manual_utils.rs
Normal 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)
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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()])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
98
clippy_lints/src/missing_trait_methods.rs
Normal file
98
clippy_lints/src/missing_trait_methods.rs
Normal 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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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}`")))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
81
clippy_lints/src/partial_pub_fields.rs
Normal file
81
clippy_lints/src/partial_pub_fields.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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`");
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
245
clippy_lints/src/utils/internal_lints/collapsible_calls.rs
Normal file
245
clippy_lints/src/utils/internal_lints/collapsible_calls.rs
Normal 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,
|
||||
);
|
||||
}
|
|
@ -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}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
164
clippy_lints/src/utils/internal_lints/if_chain_style.rs
Normal file
164
clippy_lints/src/utils/internal_lints/if_chain_style.rs
Normal 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(),
|
||||
)
|
||||
}
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
108
clippy_lints/src/utils/internal_lints/invalid_paths.rs
Normal file
108
clippy_lints/src/utils/internal_lints/invalid_paths.rs
Normal 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
|
||||
}
|
342
clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
Normal file
342
clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
63
clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
Normal file
63
clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
clippy_lints/src/utils/internal_lints/produce_ice.rs
Normal file
37
clippy_lints/src/utils/internal_lints/produce_ice.rs
Normal 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,
|
||||
}
|
||||
}
|
343
clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
Normal file
343
clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue