Merge commit 'edb720b199083f4107b858a8761648065bf38d86' into clippyup

This commit is contained in:
Philipp Krones 2023-11-16 19:13:24 +01:00
parent 9aa2330e41
commit 6246f0446a
326 changed files with 10349 additions and 10380 deletions

View File

@ -6,11 +6,70 @@ document.
## Unreleased / Beta / In Rust Nightly
[1e8fdf49...master](https://github.com/rust-lang/rust-clippy/compare/1e8fdf49...master)
[7671c283...master](https://github.com/rust-lang/rust-clippy/compare/7671c283...master)
## Rust 1.74
Current stable, released 2023-11-16
[View all 94 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-08-11T15%3A29%3A18Z..2023-09-25T08%3A48%3A22Z+base%3Amaster)
### New Lints
* [`redundant_as_str`]
[#11526](https://github.com/rust-lang/rust-clippy/pull/11526)
* [`needless_borrows_for_generic_args`]
[#11511](https://github.com/rust-lang/rust-clippy/pull/11511)
* [`path_ends_with_ext`]
[#11483](https://github.com/rust-lang/rust-clippy/pull/11483)
* [`unnecessary_map_on_constructor`]
[#11413](https://github.com/rust-lang/rust-clippy/pull/11413)
* [`missing_asserts_for_indexing`]
[#10692](https://github.com/rust-lang/rust-clippy/pull/10692)
* [`iter_out_of_bounds`]
[#11396](https://github.com/rust-lang/rust-clippy/pull/11396)
* [`implied_bounds_in_impls`]
[#11362](https://github.com/rust-lang/rust-clippy/pull/11362)
* [`reserve_after_initialization`]
[#11373](https://github.com/rust-lang/rust-clippy/pull/11373)
* [`should_panic_without_expect`]
[#11204](https://github.com/rust-lang/rust-clippy/pull/11204)
### Moves and Deprecations
* Renamed `incorrect_clone_impl_on_copy_type` to [`non_canonical_clone_impl`]
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
* Renamed `incorrect_partial_ord_impl_on_ord_type` to [`non_canonical_partial_ord_impl`]
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
* Moved [`non_canonical_clone_impl`] to `suspicious` (Now warn-by-default)
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
* Moved [`non_canonical_partial_ord_impl`] to `suspicious` (Now warn-by-default)
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
* Moved [`needless_pass_by_ref_mut`] to `nursery` (Now allow-by-default)
[#11596](https://github.com/rust-lang/rust-clippy/pull/11596)
### Enhancements
* [`undocumented_unsafe_blocks`]: The config values [`accept-comment-above-statement`] and
[`accept-comment-above-attributes`] to `true` by default
[#11170](https://github.com/rust-lang/rust-clippy/pull/11170)
* [`explicit_iter_loop`]: Added [`enforce-iter-loop-reborrow`] to disable reborrow linting by default
[#11418](https://github.com/rust-lang/rust-clippy/pull/11418)
### ICE Fixes
* [`enum_variant_names`]: No longer crashes if the threshold is 0 and the enum has no variants
[#11552](https://github.com/rust-lang/rust-clippy/pull/11552)
* [`cast_possible_truncation`]: No longer crashes on values larger than `u64::MAX`
[#11517](https://github.com/rust-lang/rust-clippy/pull/11517)
* [`tuple_array_conversions`]: No longer crashes if the array length is not usize
[#11379](https://github.com/rust-lang/rust-clippy/pull/11379)
* [`useless_conversion`]: No longer crashes, when the receiver is a non-fn item
[#11070](https://github.com/rust-lang/rust-clippy/pull/11070)
## Rust 1.73
Current stable, released 2023-10-05
Released 2023-10-05
[View all 103 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-07-02T12%3A24%3A40Z..2023-08-11T11%3A09%3A56Z+base%3Amaster)
@ -5123,6 +5182,7 @@ Released 2018-09-13
[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
[`iter_out_of_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_out_of_bounds
[`iter_over_hash_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_over_hash_type
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero

View File

@ -146,16 +146,10 @@ For example, the [`else_if_without_else`][else_if_without_else] lint is register
pub mod else_if_without_else;
// ...
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
// ...
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
// ...
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
// ...
LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
// ...
]);
}
```

View File

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.75"
version = "0.1.76"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View File

@ -270,7 +270,7 @@ When using `cargo dev new_lint`, the lint is automatically registered and
nothing more has to be done.
When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
pass may have to be registered manually in the `register_plugins` function in
pass may have to be registered manually in the `register_lints` function in
`clippy_lints/src/lib.rs`:
```rust,ignore
@ -436,7 +436,7 @@ need to ensure that the MSRV configured for the project is >= the MSRV of the
required Rust feature. If multiple features are required, just use the one with
a lower MSRV.
First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`].
First, add an MSRV alias for the required feature in [`clippy_config::msrvs`].
This can be accessed later as `msrvs::STR_STRIP_PREFIX`, for example.
```rust
@ -506,7 +506,7 @@ fn msrv_1_45() {
```
As a last step, the lint should be added to the lint documentation. This is done
in `clippy_lints/src/utils/conf.rs`:
in `clippy_config/src/conf.rs`:
```rust
define_Conf! {
@ -516,7 +516,7 @@ define_Conf! {
}
```
[`clippy_utils::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/msrvs/index.html
[`clippy_config::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_config/msrvs/index.html
## Author lint
@ -657,7 +657,7 @@ Adding a configuration to a lint can be useful for
thresholds or to constrain some behavior that can be seen as a false positive
for some users. Adding a configuration is done in the following steps:
1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this:
1. Adding a new configuration entry to [`clippy_config::conf`] like this:
```rust,ignore
/// Lint: LINT_NAME.
@ -736,7 +736,7 @@ for some users. Adding a configuration is done in the following steps:
Run `cargo collect-metadata` to generate documentation changes for the book.
[`clippy_lints::utils::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/conf.rs
[`clippy_config::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_config/src/conf.rs
[`clippy_lints` lib file]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs
[`tests/ui`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui
[`tests/ui-toml`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui-toml

View File

@ -186,7 +186,7 @@ However, sometimes we might want to declare a new lint by hand. In this case,
we'd use `cargo dev update_lints` command afterwards.
When a lint is manually declared, we might need to register the lint pass
manually in the `register_plugins` function in `clippy_lints/src/lib.rs`:
manually in the `register_lints` function in `clippy_lints/src/lib.rs`:
```rust
store.register_late_pass(|_| Box::new(foo_functions::FooFunctions));

View File

@ -1 +1,7 @@
avoid-breaking-exported-api = false
# use the various `span_lint_*` methods instead, which also add a link to the docs
disallowed-methods = [
"rustc_lint::context::LintContext::struct_span_lint",
"rustc_middle::ty::context::TyCtxt::struct_span_lint_hir"
]

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.75"
version = "0.1.76"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,6 @@
use serde::de::{self, Deserializer, Visitor};
use serde::{ser, Deserialize, Serialize};
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Deserialize)]
pub struct Rename {
@ -33,32 +32,19 @@ impl DisallowedPath {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum MatchLintBehaviour {
AllTypes,
WellKnownTypes,
Never,
}
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct MacroMatcher {
pub name: String,
pub braces: (String, String),
pub braces: (char, char),
}
impl Hash for MacroMatcher {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl PartialEq for MacroMatcher {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for MacroMatcher {}
impl<'de> Deserialize<'de> for MacroMatcher {
fn deserialize<D>(deser: D) -> Result<Self, D::Error>
where
@ -83,7 +69,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
V: de::MapAccess<'de>,
{
let mut name = None;
let mut brace: Option<String> = None;
let mut brace: Option<char> = None;
while let Some(key) = map.next_key()? {
match key {
Field::Name => {
@ -104,7 +90,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
Ok(MacroMatcher {
name,
braces: [("(", ")"), ("{", "}"), ("[", "]")]
braces: [('(', ')'), ('{', '}'), ('[', ']')]
.into_iter()
.find(|b| b.0 == brace)
.map(|(o, c)| (o.to_owned(), c.to_owned()))

View File

@ -320,8 +320,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
extract_msrv_attr!({context_import});
}}
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
// TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed.
// TODO: Update msrv config comment in `clippy_config/src/conf.rs`
"#
)
} else {

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.75"
version = "0.1.76"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@ -14,7 +14,6 @@ cargo_metadata = "0.15.3"
clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
if_chain = "1.0"
itertools = "0.10.1"
quine-mc_cluskey = "0.2"
regex-syntax = "0.7"

View File

@ -52,24 +52,22 @@ declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
impl LateLintPass<'_> for AllowAttribute {
// Separate each crate's features.
fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) {
if_chain! {
if !in_external_macro(cx.sess(), attr.span);
if cx.tcx.features().lint_reasons;
if let AttrStyle::Outer = attr.style;
if let Some(ident) = attr.ident();
if ident.name == rustc_span::symbol::sym::allow;
if !is_from_proc_macro(cx, &attr);
then {
span_lint_and_sugg(
cx,
ALLOW_ATTRIBUTES,
ident.span,
"#[allow] attribute found",
"replace it with",
"expect".into(),
Applicability::MachineApplicable,
);
}
if !in_external_macro(cx.sess(), attr.span)
&& cx.tcx.features().lint_reasons
&& let AttrStyle::Outer = attr.style
&& let Some(ident) = attr.ident()
&& ident.name == rustc_span::symbol::sym::allow
&& !is_from_proc_macro(cx, &attr)
{
span_lint_and_sugg(
cx,
ALLOW_ATTRIBUTES,
ident.span,
"#[allow] attribute found",
"replace it with",
"expect".into(),
Applicability::MachineApplicable,
);
}
}
}

View File

@ -14,7 +14,9 @@ declare_clippy_lint! {
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
///
/// ### Why is this bad?
/// `Arc<T>` is only `Send`/`Sync` when `T` is [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send-for-Arc%3CT%3E),
/// `Arc<T>` is an Atomic `RC<T>` and guarantees that updates to the reference counter are
/// Atomic. This is useful in multiprocessing scenarios. To send an `Arc<T>` across processes
/// and make use of the atomic ref counter, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send-for-Arc%3CT%3E),
/// either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
///
/// ### Example
@ -34,7 +36,7 @@ declare_clippy_lint! {
#[clippy::version = "1.72.0"]
pub ARC_WITH_NON_SEND_SYNC,
suspicious,
"using `Arc` with a type that does not implement `Send` or `Sync`"
"using `Arc` with a type that does not implement `Send` and `Sync`"
}
declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]);
@ -61,19 +63,25 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync {
cx,
ARC_WITH_NON_SEND_SYNC,
expr.span,
"usage of an `Arc` that is not `Send` or `Sync`",
"usage of an `Arc` that is not `Send` and `Sync`",
|diag| {
with_forced_trimmed_paths!({
diag.note(format!("`Arc<{arg_ty}>` is not `Send` and `Sync` as:"));
if !is_send {
diag.note(format!("the trait `Send` is not implemented for `{arg_ty}`"));
diag.note(format!("- the trait `Send` is not implemented for `{arg_ty}`"));
}
if !is_sync {
diag.note(format!("the trait `Sync` is not implemented for `{arg_ty}`"));
diag.note(format!("- the trait `Sync` is not implemented for `{arg_ty}`"));
}
diag.note(format!("required for `{ty}` to implement `Send` and `Sync`"));
diag.help("consider using an `Rc` instead. `Arc` does not provide benefits for non `Send` and `Sync` types");
diag.help("consider using an `Rc` instead or wrapping the inner type with a `Mutex`");
diag.note("if you intend to use `Arc` with `Send` and `Sync` traits");
diag.note(format!(
"wrap the inner type with a `Mutex` or implement `Send` and `Sync` for `{arg_ty}`"
));
});
},
);

View File

@ -5,7 +5,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sug
use clippy_utils::is_from_proc_macro;
use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use if_chain::if_chain;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::TokenTree;
use rustc_ast::{
@ -20,7 +19,7 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
use rustc_span::{sym, DUMMY_SP, Span};
use rustc_span::{sym, Span, DUMMY_SP};
use semver::Version;
static UNIX_SYSTEMS: &[&str] = &[
@ -371,7 +370,7 @@ declare_clippy_lint! {
/// let _ = 1 / random();
/// }
/// ```
#[clippy::version = "1.73.0"]
#[clippy::version = "1.74.0"]
pub SHOULD_PANIC_WITHOUT_EXPECT,
pedantic,
"ensures that all `should_panic` attributes specify its expected panic message"
@ -470,13 +469,11 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return;
}
for item in items {
if_chain! {
if let NestedMetaItem::MetaItem(mi) = &item;
if let MetaItemKind::NameValue(lit) = &mi.kind;
if mi.has_name(sym::since);
then {
check_semver(cx, item.span(), lit);
}
if let NestedMetaItem::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
check_semver(cx, item.span(), lit);
}
}
}
@ -579,15 +576,13 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
/// Returns the lint name if it is clippy lint.
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
if_chain! {
if let Some(meta_item) = lint.meta_item();
if meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy;
then {
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name);
}
if let Some(meta_item) = lint.meta_item()
&& meta_item.path.segments.len() > 1
&& let tool_name = meta_item.path.segments[0].ident
&& tool_name.name == sym::clippy
{
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name);
}
None
}
@ -856,18 +851,17 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
}
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
if_chain! {
if msrv.meets(msrvs::TOOL_ATTRIBUTES);
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
// check cfg_attr
if attr.has_name(sym::cfg_attr);
if let Some(items) = attr.meta_item_list();
if items.len() == 2;
&& attr.has_name(sym::cfg_attr)
&& let Some(items) = attr.meta_item_list()
&& items.len() == 2
// check for `rustfmt`
if let Some(feature_item) = items[0].meta_item();
if feature_item.has_name(sym::rustfmt);
&& let Some(feature_item) = items[0].meta_item()
&& feature_item.has_name(sym::rustfmt)
// check for `rustfmt_skip` and `rustfmt::skip`
if let Some(skip_item) = &items[1].meta_item();
if skip_item.has_name(sym!(rustfmt_skip))
&& let Some(skip_item) = &items[1].meta_item()
&& (skip_item.has_name(sym!(rustfmt_skip))
|| skip_item
.path
.segments
@ -875,21 +869,20 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
.expect("empty path in attribute")
.ident
.name
== sym::skip;
== sym::skip)
// Only lint outer attributes, because custom inner attributes are unstable
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
if attr.style == AttrStyle::Outer;
then {
span_lint_and_sugg(
cx,
DEPRECATED_CFG_ATTR,
attr.span,
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
"use",
"#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable,
);
}
&& attr.style == AttrStyle::Outer
{
span_lint_and_sugg(
cx,
DEPRECATED_CFG_ATTR,
attr.span,
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
"use",
"#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable,
);
}
}
@ -989,12 +982,10 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched.extend(find_mismatched_target_os(list));
},
MetaItemKind::Word => {
if_chain! {
if let Some(ident) = meta.ident();
if let Some(os) = find_os(ident.name.as_str());
then {
mismatched.push((os, ident.span));
}
if let Some(ident) = meta.ident()
&& let Some(os) = find_os(ident.name.as_str())
{
mismatched.push((os, ident.span));
}
},
MetaItemKind::NameValue(..) => {},
@ -1005,30 +996,28 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched
}
if_chain! {
if attr.has_name(sym::cfg);
if let Some(list) = attr.meta_item_list();
let mismatched = find_mismatched_target_os(&list);
if !mismatched.is_empty();
then {
let mess = "operating system used in target family position";
if attr.has_name(sym::cfg)
&& let Some(list) = attr.meta_item_list()
&& let mismatched = find_mismatched_target_os(&list)
&& !mismatched.is_empty()
{
let mess = "operating system used in target family position";
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems
let mut unix_suggested = false;
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems
let mut unix_suggested = false;
for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?");
unix_suggested = true;
}
if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?");
unix_suggested = true;
}
});
}
}
});
}
}

View File

@ -4,7 +4,6 @@ use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{get_parent_expr, higher};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -114,15 +113,13 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
let _: Option<!> = for_each_expr(cond, |e| {
if let ExprKind::Closure(closure) = e.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(cx, e);
if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
let caller = cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(cx, caller, iter_id, &[]);
then {
return ControlFlow::Continue(Descend::No);
}
if let Some(parent) = get_parent_expr(cx, e)
&& let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
&& let caller = cx.typeck_results().expr_ty(self_arg)
&& let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& implements_trait(cx, caller, iter_id, &[])
{
return ControlFlow::Continue(Descend::No);
}
let body = cx.tcx.hir().body(closure.body);

View File

@ -2,7 +2,6 @@ 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 if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
@ -151,17 +150,15 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
return Ok(Bool::Term(n as u8));
}
if_chain! {
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
if implements_ord(self.cx, e_lhs);
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
if negate(e_binop.node) == Some(expr_binop.node);
if eq_expr_value(self.cx, e_lhs, expr_lhs);
if eq_expr_value(self.cx, e_rhs, expr_rhs);
then {
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
}
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind
&& implements_ord(self.cx, e_lhs)
&& let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
&& negate(e_binop.node) == Some(expr_binop.node)
&& eq_expr_value(self.cx, e_lhs, expr_lhs)
&& eq_expr_value(self.cx, e_rhs, expr_rhs)
{
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
}
}
let n = self.terminals.len();

View File

@ -49,69 +49,62 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
if_chain! {
if !e.span.from_expansion();
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind;
if !addrof_target.span.from_expansion();
if let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind;
if !deref_target.span.from_expansion();
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) );
let ref_ty = cx.typeck_results().expr_ty(deref_target);
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind();
if !is_from_proc_macro(cx, e);
then{
if let Some(parent_expr) = get_parent_expr(cx, e){
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..)) &&
!is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id) {
return;
}
// modification to `&mut &*x` is different from `&mut x`
if matches!(deref_target.kind, ExprKind::Path(..)
| ExprKind::Field(..)
| ExprKind::Index(..)
| ExprKind::Unary(UnOp::Deref, ..))
&& matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _)) {
return;
}
if !e.span.from_expansion()
&& let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
&& !addrof_target.span.from_expansion()
&& let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
&& !deref_target.span.from_expansion()
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
&& let ref_ty = cx.typeck_results().expr_ty(deref_target)
&& let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind()
&& !is_from_proc_macro(cx, e)
{
if let Some(parent_expr) = get_parent_expr(cx, e) {
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..))
&& !is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id)
{
return;
}
span_lint_and_then(
cx,
BORROW_DEREF_REF,
e.span,
"deref on an immutable reference",
|diag| {
diag.span_suggestion(
e.span,
"if you would like to reborrow, try removing `&*`",
snippet_opt(cx, deref_target.span).unwrap(),
Applicability::MachineApplicable
);
// has deref trait -> give 2 help
// doesn't have deref trait -> give 1 help
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait(){
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
return;
}
}
diag.span_suggestion(
e.span,
"if you would like to deref, try using `&**`",
format!(
"&**{}",
&snippet_opt(cx, deref_target.span).unwrap(),
),
Applicability::MaybeIncorrect
);
}
);
// modification to `&mut &*x` is different from `&mut x`
if matches!(
deref_target.kind,
ExprKind::Path(..) | ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, ..)
) && matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _))
{
return;
}
}
span_lint_and_then(
cx,
BORROW_DEREF_REF,
e.span,
"deref on an immutable reference",
|diag| {
diag.span_suggestion(
e.span,
"if you would like to reborrow, try removing `&*`",
snippet_opt(cx, deref_target.span).unwrap(),
Applicability::MachineApplicable,
);
// has deref trait -> give 2 help
// doesn't have deref trait -> give 1 help
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() {
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
return;
}
}
diag.span_suggestion(
e.span,
"if you would like to deref, try using `&**`",
format!("&**{}", &snippet_opt(cx, deref_target.span).unwrap()),
Applicability::MaybeIncorrect,
);
},
);
}
}
}

View File

@ -2,7 +2,6 @@
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use itertools::Itertools;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_lint::LateContext;
@ -15,31 +14,33 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
let mut packages = metadata.packages.clone();
packages.sort_by(|a, b| a.name.cmp(&b.name));
if_chain! {
if let Some(resolve) = &metadata.resolve;
if let Some(local_id) = packages
.iter()
.find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None });
then {
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
let group: Vec<&Package> = group.collect();
if let Some(resolve) = &metadata.resolve
&& let Some(local_id) = packages.iter().find_map(|p| {
if p.name == local_name.as_str() {
Some(&p.id)
} else {
None
}
})
{
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
let group: Vec<&Package> = group.collect();
if group.len() <= 1 {
continue;
}
if group.len() <= 1 {
continue;
}
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
versions.sort();
let versions = versions.iter().join(", ");
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
versions.sort();
let versions = versions.iter().join(", ");
span_lint(
cx,
MULTIPLE_CRATE_VERSIONS,
DUMMY_SP,
&format!("multiple versions for dependency `{name}`: {versions}"),
);
}
span_lint(
cx,
MULTIPLE_CRATE_VERSIONS,
DUMMY_SP,
&format!("multiple versions for dependency `{name}`: {versions}"),
);
}
}
}

View File

@ -1,6 +1,5 @@
use cargo_metadata::Metadata;
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use rustc_lint::LateContext;
use rustc_span::DUMMY_SP;
@ -9,19 +8,17 @@ use super::WILDCARD_DEPENDENCIES;
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
for dep in &metadata.packages[0].dependencies {
// VersionReq::any() does not work
if_chain! {
if let Ok(wildcard_ver) = semver::VersionReq::parse("*");
if let Some(ref source) = dep.source;
if !source.starts_with("git");
if dep.req == wildcard_ver;
then {
span_lint(
cx,
WILDCARD_DEPENDENCIES,
DUMMY_SP,
&format!("wildcard dependency for `{}`", dep.name),
);
}
if let Ok(wildcard_ver) = semver::VersionReq::parse("*")
&& let Some(ref source) = dep.source
&& !source.starts_with("git")
&& dep.req == wildcard_ver
{
span_lint(
cx,
WILDCARD_DEPENDENCIES,
DUMMY_SP,
&format!("wildcard dependency for `{}`", dep.name),
);
}
}
}

View File

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LintContext};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use super::{utils, CAST_POSSIBLE_WRAP};
@ -78,13 +79,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
),
};
cx.struct_span_lint(CAST_POSSIBLE_WRAP, expr.span, message, |diag| {
span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, &message, |diag| {
if let EmitState::LintOnPtrSize(16) = should_lint {
diag
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types")
} else {
diag
}
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
};
});
}

View File

@ -1,7 +1,6 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{method_chain_args, sext};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
@ -28,13 +27,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
// Don't lint for positive constants.
let const_val = constant(cx, cx.typeck_results(), cast_op);
if_chain! {
if let Some(Constant::Int(n)) = const_val;
if let ty::Int(ity) = *cast_from.kind();
if sext(cx.tcx, n, ity) >= 0;
then {
return false;
}
if let Some(Constant::Int(n)) = const_val
&& let ty::Int(ity) = *cast_from.kind()
&& sext(cx.tcx, n, ity) >= 0
{
return false;
}
// Don't lint for the result of methods that always return non-negative values.
@ -42,13 +39,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
let mut method_name = path.ident.name.as_str();
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
if_chain! {
if method_name == "unwrap";
if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
if let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind;
then {
method_name = inner_path.ident.name.as_str();
}
if method_name == "unwrap"
&& let Some(arglist) = method_chain_args(cast_op, &["unwrap"])
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
{
method_name = inner_path.ident.name.as_str();
}
if allowed_methods.iter().any(|&name| method_name == name) {

View File

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source;
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
@ -69,26 +68,24 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let map = cx.tcx.hir();
if_chain! {
if let Some(parent_id) = map.opt_parent_id(expr.hir_id);
if let Some(parent) = map.find(parent_id);
then {
let expr = match parent {
Node::Block(block) => {
if let Some(parent_expr) = block.expr {
parent_expr
} else {
return false;
}
},
Node::Expr(expr) => expr,
_ => return false,
};
if let Some(parent_id) = map.opt_parent_id(expr.hir_id)
&& let Some(parent) = map.find(parent_id)
{
let expr = match parent {
Node::Block(block) => {
if let Some(parent_expr) = block.expr {
parent_expr
} else {
return false;
}
},
Node::Expr(expr) => expr,
_ => return false,
};
matches!(expr.kind, ExprKind::Cast(..))
} else {
false
}
matches!(expr.kind, ExprKind::Cast(..))
} else {
false
}
}

View File

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind};
@ -25,34 +24,32 @@ fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> {
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: &Msrv) {
if_chain! {
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS);
if let ty::RawPtr(ptrty) = cast_to.kind();
if let ty::Slice(_) = ptrty.ty.kind();
if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind;
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
if let Some(rpk) = raw_parts_kind(cx, fun_def_id);
let ctxt = expr.span.ctxt();
if cast_expr.span.ctxt() == ctxt;
then {
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
RawPartsKind::Mutable => "from_raw_parts_mut"
};
let span = expr.span;
let mut applicability = Applicability::MachineApplicable;
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
span,
&format!("casting the result of `{func}` to {cast_to}"),
"replace with",
format!("core::ptr::slice_{func}({ptr}, {len})"),
applicability
);
}
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS)
&& let ty::RawPtr(ptrty) = cast_to.kind()
&& let ty::Slice(_) = ptrty.ty.kind()
&& let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
&& let Some(rpk) = raw_parts_kind(cx, fun_def_id)
&& let ctxt = expr.span.ctxt()
&& cast_expr.span.ctxt() == ctxt
{
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
RawPartsKind::Mutable => "from_raw_parts_mut",
};
let span = expr.span;
let mut applicability = Applicability::MachineApplicable;
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
span,
&format!("casting the result of `{func}` to {cast_to}"),
"replace with",
format!("core::ptr::slice_{func}({ptr}, {len})"),
applicability,
);
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
@ -10,32 +9,31 @@ use rustc_middle::ty::{self, UintTy};
use super::CHAR_LIT_AS_U8;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Cast(e, _) = &expr.kind;
if let ExprKind::Lit(l) = &e.kind;
if let LitKind::Char(c) = l.node;
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind();
then {
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
if let ExprKind::Cast(e, _) = &expr.kind
&& let ExprKind::Lit(l) = &e.kind
&& let LitKind::Char(c) = l.node
&& ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind()
{
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
span_lint_and_then(
cx,
CHAR_LIT_AS_U8,
expr.span,
"casting a character literal to `u8` truncates",
|diag| {
diag.note("`char` is four bytes wide, but `u8` is a single byte");
span_lint_and_then(
cx,
CHAR_LIT_AS_U8,
expr.span,
"casting a character literal to `u8` truncates",
|diag| {
diag.note("`char` is four bytes wide, but `u8` is a single byte");
if c.is_ascii() {
diag.span_suggestion(
expr.span,
"use a byte literal instead",
format!("b{snippet}"),
applicability,
);
}
});
}
if c.is_ascii() {
diag.span_suggestion(
expr.span,
"use a byte literal instead",
format!("b{snippet}"),
applicability,
);
}
},
);
}
}

View File

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
@ -17,29 +16,35 @@ pub(super) fn check<'tcx>(
cast_to: Ty<'tcx>,
msrv: &Msrv,
) {
if_chain! {
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS);
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, ty: from_ty }) = cast_from.kind();
if let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, ty: to_ty }) = cast_to.kind();
if matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not));
if from_ty == to_ty;
then {
let sugg = Sugg::hir(cx, cast_expr, "_");
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS)
&& let ty::RawPtr(TypeAndMut {
mutbl: from_mutbl,
ty: from_ty,
}) = cast_from.kind()
&& let ty::RawPtr(TypeAndMut {
mutbl: to_mutbl,
ty: to_ty,
}) = cast_to.kind()
&& matches!(
(from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not)
)
&& from_ty == to_ty
{
let sugg = Sugg::hir(cx, cast_expr, "_");
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
span_lint_and_sugg(
cx,
PTR_CAST_CONSTNESS,
expr.span,
"`as` casting between raw pointers while changing only its constness",
&format!("try `pointer::cast_{constness}`, a safer alternative"),
format!("{}.cast_{constness}()", sugg.maybe_par()),
Applicability::MachineApplicable,
);
}
span_lint_and_sugg(
cx,
PTR_CAST_CONSTNESS,
expr.span,
"`as` casting between raw pointers while changing only its constness",
&format!("try `pointer::cast_{constness}`, a safer alternative"),
format!("{}.cast_{constness}()", sugg.maybe_par()),
Applicability::MachineApplicable,
);
}
}

View File

@ -3,7 +3,6 @@ use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
use if_chain::if_chain;
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -25,40 +24,40 @@ pub(super) fn check<'tcx>(
) -> bool {
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
if_chain! {
if let ty::RawPtr(..) = cast_from.kind();
if let ty::RawPtr(..) = cast_from.kind()
// check both mutability and type are the same
if cast_from.kind() == cast_to.kind();
if let ExprKind::Cast(_, cast_to_hir) = expr.kind;
&& cast_from.kind() == cast_to.kind()
&& let ExprKind::Cast(_, cast_to_hir) = expr.kind
// Ignore casts to e.g. type aliases and infer types
// - p as pointer_alias
// - p as _
if let TyKind::Ptr(to_pointee) = cast_to_hir.kind;
then {
match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
},
// Ignore `p as *const _`
TyKind::Infer => return false,
_ => {},
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!("casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"),
"try",
cast_str.clone(),
Applicability::MaybeIncorrect,
);
&& let TyKind::Ptr(to_pointee) = cast_to_hir.kind
{
match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
},
// Ignore `p as *const _`
TyKind::Infer => return false,
_ => {},
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!(
"casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"
),
"try",
cast_str.clone(),
Applicability::MaybeIncorrect,
);
}
// skip cast of local that is a type alias
@ -86,14 +85,12 @@ pub(super) fn check<'tcx>(
}
// skip cast to non-primitive type
if_chain! {
if let ExprKind::Cast(_, cast_to) = expr.kind;
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
if let Res::PrimTy(_) = path.res;
then {}
else {
return false;
}
if let ExprKind::Cast(_, cast_to) = expr.kind
&& let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind
&& let Res::PrimTy(_) = path.res
{
} else {
return false;
}
// skip cast of fn call that returns type alias
@ -106,18 +103,19 @@ pub(super) fn check<'tcx>(
if let Some(lit) = get_numeric_literal(cast_expr) {
let literal_str = &cast_str;
if_chain! {
if let LitKind::Int(n, _) = lit.node;
if let Some(src) = snippet_opt(cx, cast_expr.span);
if cast_to.is_floating_point();
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node);
let from_nbits = 128 - n.leading_zeros();
let to_nbits = fp_ty_mantissa_nbits(cast_to);
if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal();
then {
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true
}
if let LitKind::Int(n, _) = lit.node
&& let Some(src) = snippet_opt(cx, cast_expr.span)
&& cast_to.is_floating_point()
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
&& let from_nbits = 128 - n.leading_zeros()
&& let to_nbits = fp_ty_mantissa_nbits(cast_to)
&& from_nbits != 0
&& to_nbits != 0
&& from_nbits <= to_nbits
&& num_lit.is_decimal()
{
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
match lit.node {

View File

@ -4,7 +4,6 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -55,20 +54,17 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
return;
}
let result = if_chain! {
if !in_constant(cx, item.hir_id);
if !in_external_macro(cx.sess(), item.span);
if let ExprKind::Binary(op, left, right) = &item.kind;
then {
match op.node {
BinOpKind::Ge | BinOpKind::Le => single_check(item),
BinOpKind::And => double_check(cx, left, right),
_ => None,
}
} else {
None
let result = if !in_constant(cx, item.hir_id)
&& !in_external_macro(cx.sess(), item.span)
&& let ExprKind::Binary(op, left, right) = &item.kind
{
match op.node {
BinOpKind::Ge | BinOpKind::Le => single_check(item),
BinOpKind::And => double_check(cx, left, right),
_ => None,
}
} else {
None
};
if let Some(cv) = result {
@ -193,16 +189,13 @@ impl ConversionType {
/// Check for `expr <= (to_type::MAX as from_type)`
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
if_chain! {
if let ExprKind::Binary(ref op, left, right) = &expr.kind;
if let Some((candidate, check)) = normalize_le_ge(op, left, right);
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
then {
Conversion::try_new(candidate, from, to)
} else {
None
}
if let ExprKind::Binary(ref op, left, right) = &expr.kind
&& let Some((candidate, check)) = normalize_le_ge(op, left, right)
&& let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX")
{
Conversion::try_new(candidate, from, to)
} else {
None
}
}
@ -243,33 +236,27 @@ fn get_types_from_cast<'a>(
) -> Option<(&'a str, &'a str)> {
// `to_type::max_value() as from_type`
// or `to_type::MAX as from_type`
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind
// to_type::max_value(), from_type
if let ExprKind::Cast(limit, from_type) = &expr.kind;
if let TyKind::Path(ref from_type_path) = &from_type.kind;
if let Some(from_sym) = int_ty_to_sym(from_type_path);
then {
Some((limit, from_sym))
} else {
None
}
&& let TyKind::Path(ref from_type_path) = &from_type.kind
&& let Some(from_sym) = int_ty_to_sym(from_type_path)
{
Some((limit, from_sym))
} else {
None
};
// `from_type::from(to_type::max_value())`
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
if_chain! {
if let ExprKind::Call(from_func, [limit]) = &expr.kind
// `from_type::from, to_type::max_value()`
if let ExprKind::Call(from_func, [limit]) = &expr.kind;
// `from_type::from`
if let ExprKind::Path(ref path) = &from_func.kind;
if let Some(from_sym) = get_implementing_type(path, INTS, "from");
then {
Some((limit, from_sym))
} else {
None
}
&& let ExprKind::Path(ref path) = &from_func.kind
&& let Some(from_sym) = get_implementing_type(path, INTS, "from")
{
Some((limit, from_sym))
} else {
None
}
});
@ -298,31 +285,27 @@ fn get_types_from_cast<'a>(
/// Gets the type which implements the called function
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
if_chain! {
if let QPath::TypeRelative(ty, path) = &path;
if path.ident.name.as_str() == function;
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
if let [int] = tp.segments;
then {
let name = int.ident.name.as_str();
candidates.iter().find(|c| &name == *c).copied()
} else {
None
}
if let QPath::TypeRelative(ty, path) = &path
&& path.ident.name.as_str() == function
&& let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind
&& let [int] = tp.segments
{
let name = int.ident.name.as_str();
candidates.iter().find(|c| &name == *c).copied()
} else {
None
}
}
/// Gets the type as a string, if it is a supported integer
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! {
if let QPath::Resolved(_, path) = *path;
if let [ty] = path.segments;
then {
let name = ty.ident.name.as_str();
INTS.iter().find(|c| &name == *c).copied()
} else {
None
}
if let QPath::Resolved(_, path) = *path
&& let [ty] = path.segments
{
let name = ty.ident.name.as_str();
INTS.iter().find(|c| &name == *c).copied()
} else {
None
}
}

View File

@ -15,7 +15,6 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -121,49 +120,55 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
}
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
if_chain! {
if let ast::ExprKind::Block(ref block, _) = else_.kind;
if !block_starts_with_comment(cx, block);
if let Some(else_) = expr_block(block);
if else_.attrs.is_empty();
if !else_.span.from_expansion();
if let ast::ExprKind::If(..) = else_.kind;
then {
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { !c.is_whitespace() } else { false };
if let ast::ExprKind::Block(ref block, _) = else_.kind
&& !block_starts_with_comment(cx, block)
&& let Some(else_) = expr_block(block)
&& else_.attrs.is_empty()
&& !else_.span.from_expansion()
&& let ast::ExprKind::If(..) = else_.kind
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
),
applicability,
);
}
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
),
applicability,
);
}
}
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
if_chain! {
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if inner.attrs.is_empty();
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
if !block_starts_with_comment(cx, then)
&& let Some(inner) = expr_block(then)
&& inner.attrs.is_empty()
&& let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind
// Prevent triggering on `if c { if let a = b { .. } }`.
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
let ctxt = expr.span.ctxt();
if inner.span.ctxt() == ctxt;
then {
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
{
span_lint_and_then(
cx,
COLLAPSIBLE_IF,
expr.span,
"this `if` statement can be collapsed",
|diag| {
let mut app = Applicability::MachineApplicable;
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
@ -177,8 +182,8 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
),
app, // snippet
);
});
}
},
);
}
}

View File

@ -117,7 +117,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub IF_SAME_THEN_ELSE,
correctness,
style,
"`if` with the same `then` and `else` blocks"
}

View File

@ -5,8 +5,6 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use if_chain::if_chain;
declare_clippy_lint! {
/// ### What it does
/// Checks for types that implement `Copy` as well as
@ -38,25 +36,23 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
impl<'tcx> LateLintPass<'tcx> for CopyIterator {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind;
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
if is_copy(cx, ty);
if let Some(trait_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id);
then {
span_lint_and_note(
cx,
COPY_ITERATOR,
item.span,
"you are implementing `Iterator` on a `Copy` type",
None,
"consider implementing `IntoIterator` instead",
);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& is_copy(cx, ty)
&& let Some(trait_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Iterator, trait_id)
{
span_lint_and_note(
cx,
COPY_ITERATOR,
item.span,
"you are implementing `Iterator` on a `Copy` type",
None,
"consider implementing `IntoIterator` instead",
);
}
}
}

View File

@ -53,35 +53,31 @@ declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
impl EarlyLintPass for CrateInMacroDef {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! {
if item.attrs.iter().any(is_macro_export);
if let ItemKind::MacroDef(macro_def) = &item.kind;
let tts = macro_def.body.tokens.clone();
if let Some(span) = contains_unhygienic_crate_reference(&tts);
then {
span_lint_and_sugg(
cx,
CRATE_IN_MACRO_DEF,
span,
"`crate` references the macro call's crate",
"to reference the macro definition's crate, use",
String::from("$crate"),
Applicability::MachineApplicable,
);
}
if item.attrs.iter().any(is_macro_export)
&& let ItemKind::MacroDef(macro_def) = &item.kind
&& let tts = macro_def.body.tokens.clone()
&& let Some(span) = contains_unhygienic_crate_reference(&tts)
{
span_lint_and_sugg(
cx,
CRATE_IN_MACRO_DEF,
span,
"`crate` references the macro call's crate",
"to reference the macro definition's crate, use",
String::from("$crate"),
Applicability::MachineApplicable,
);
}
}
}
fn is_macro_export(attr: &Attribute) -> bool {
if_chain! {
if let AttrKind::Normal(normal) = &attr.kind;
if let [segment] = normal.item.path.segments.as_slice();
then {
segment.ident.name == sym::macro_export
} else {
false
}
if let AttrKind::Normal(normal) = &attr.kind
&& let [segment] = normal.item.path.segments.as_slice()
{
segment.ident.name == sym::macro_export
} else {
false
}
}
@ -89,14 +85,12 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
let mut prev_is_dollar = false;
let mut cursor = tts.trees();
while let Some(curr) = cursor.next() {
if_chain! {
if !prev_is_dollar;
if let Some(span) = is_crate_keyword(curr);
if let Some(next) = cursor.look_ahead(0);
if is_token(next, &TokenKind::ModSep);
then {
return Some(span);
}
if !prev_is_dollar
&& let Some(span) = is_crate_keyword(curr)
&& let Some(next) = cursor.look_ahead(0)
&& is_token(next, &TokenKind::ModSep)
{
return Some(span);
}
if let TokenTree::Delimited(_, _, tts) = &curr {
let span = contains_unhygienic_crate_reference(tts);
@ -110,10 +104,18 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
}
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
if_chain! {
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt;
if symbol.as_str() == "crate";
then { Some(*span) } else { None }
if let TokenTree::Token(
Token {
kind: TokenKind::Ident(symbol, _),
span,
},
_,
) = tt
&& symbol.as_str() == "crate"
{
Some(*span)
} else {
None
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -33,22 +32,20 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Call(func, [arg, ..]) = expr.kind;
if let ExprKind::Path(ref path) = func.kind;
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id);
then {
span_lint_and_sugg(
cx,
CREATE_DIR,
expr.span,
"calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect,
)
}
if let ExprKind::Call(func, [arg, ..]) = expr.kind
&& let ExprKind::Path(ref path) = func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)
{
span_lint_and_sugg(
cx,
CREATE_DIR,
expr.span,
"calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect,
);
}
}
}

View File

@ -6,7 +6,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, BytePos, Pos, Span};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -31,31 +31,6 @@ declare_clippy_lint! {
"`dbg!` macro is intended as a debugging tool"
}
/// Gets the span of the statement up to the next semicolon, if and only if the next
/// non-whitespace character actually is a semicolon.
/// E.g.
/// ```rust,ignore
///
/// dbg!();
/// ^^^^^^^ this span is returned
///
/// foo!(dbg!());
/// no span is returned
/// ```
fn span_including_semi(cx: &LateContext<'_>, span: Span) -> Option<Span> {
let sm = cx.sess().source_map();
let sf = sm.lookup_source_file(span.hi());
let src = sf.src.as_ref()?.get(span.hi().to_usize()..)?;
let first_non_whitespace = src.find(|c: char| !c.is_whitespace())?;
if src.as_bytes()[first_non_whitespace] == b';' {
let hi = span.hi() + BytePos::from_usize(first_non_whitespace + 1);
Some(span.with_hi(hi))
} else {
None
}
}
#[derive(Copy, Clone)]
pub struct DbgMacro {
allow_dbg_in_tests: bool,
@ -88,10 +63,10 @@ impl LateLintPass<'_> for DbgMacro {
ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement.
if let Some(Node::Stmt(stmt)) = cx.tcx.hir().find_parent(expr.hir_id)
&& let Some(span) = span_including_semi(cx, stmt.span.source_callsite())
if let Some(Node::Stmt(_)) = cx.tcx.hir().find_parent(expr.hir_id)
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
{
(span, String::new())
(macro_call.span.to(semi_span), String::new())
} else {
(macro_call.span, String::from("()"))
}

View File

@ -10,8 +10,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")]
crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::if_chain_style::IF_CHAIN_STYLE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
@ -141,6 +139,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::doc::MISSING_PANICS_DOC_INFO,
crate::doc::MISSING_SAFETY_DOC_INFO,
crate::doc::NEEDLESS_DOCTEST_MAIN_INFO,
crate::doc::SUSPICIOUS_DOC_COMMENTS_INFO,
crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
crate::double_parens::DOUBLE_PARENS_INFO,
crate::drop_forget_ref::DROP_NON_DROP_INFO,
@ -232,6 +231,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
crate::iter_over_hash_type::ITER_OVER_HASH_TYPE_INFO,
crate::iter_without_into_iter::INTO_ITER_WITHOUT_ITER_INFO,
crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO,
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
@ -628,7 +628,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::strings::STR_TO_STRING_INFO,
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO,
crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -81,33 +80,31 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
impl<'tcx> LateLintPass<'tcx> for Default {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if !expr.span.from_expansion();
if !expr.span.from_expansion()
// Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span);
if let ExprKind::Call(path, ..) = expr.kind;
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
if !is_update_syntax_base(cx, expr);
&& !self.reassigned_linted.contains(&expr.span)
&& let ExprKind::Call(path, ..) = expr.kind
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
&& let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
&& !is_update_syntax_base(cx, expr)
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath;
let expr_ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, ..) = expr_ty.kind();
if !is_from_proc_macro(cx, expr);
then {
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
span_lint_and_sugg(
cx,
DEFAULT_TRAIT_ACCESS,
expr.span,
&format!("calling `{replacement}` is more clear than this expression"),
"try",
replacement,
Applicability::Unspecified, // First resolve the TODO above
);
}
&& let QPath::Resolved(None, _path) = qpath
&& let expr_ty = cx.typeck_results().expr_ty(expr)
&& let ty::Adt(def, ..) = expr_ty.kind()
&& !is_from_proc_macro(cx, expr)
{
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
span_lint_and_sugg(
cx,
DEFAULT_TRAIT_ACCESS,
expr.span,
&format!("calling `{replacement}` is more clear than this expression"),
"try",
replacement,
Applicability::Unspecified, // First resolve the TODO above
);
}
}
@ -124,38 +121,36 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
// `default` method of the `Default` trait, and store statement index in current block being
// checked and the name of the bound variable
let (local, variant, binding_name, binding_type, span) = if_chain! {
let (local, variant, binding_name, binding_type, span) = if let StmtKind::Local(local) = stmt.kind
// only take `let ...` statements
if let StmtKind::Local(local) = stmt.kind;
if let Some(expr) = local.init;
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if !expr.span.from_expansion();
&& let Some(expr) = local.init
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
&& !expr.span.from_expansion()
// only take bindings to identifiers
if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
&& let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind
// only when assigning `... = Default::default()`
if is_expr_default(expr, cx);
let binding_type = cx.typeck_results().node_type(binding_id);
if let Some(adt) = binding_type.ty_adt_def();
if adt.is_struct();
let variant = adt.non_enum_variant();
if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
let module_did = cx.tcx.parent_module(stmt.hir_id);
if variant
&& is_expr_default(expr, cx)
&& let binding_type = cx.typeck_results().node_type(binding_id)
&& let Some(adt) = binding_type.ty_adt_def()
&& adt.is_struct()
&& let variant = adt.non_enum_variant()
&& (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
&& let module_did = cx.tcx.parent_module(stmt.hir_id)
&& variant
.fields
.iter()
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
let all_fields_are_copy = variant
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx))
&& let all_fields_are_copy = variant
.fields
.iter()
.all(|field| {
is_copy(cx, cx.tcx.type_of(field.did).instantiate_identity())
});
if !has_drop(cx, binding_type) || all_fields_are_copy;
then {
(local, variant, ident.name, binding_type, expr.span)
} else {
continue;
}
})
&& (!has_drop(cx, binding_type) || all_fields_are_copy)
{
(local, variant, ident.name, binding_type, expr.span)
} else {
continue;
};
let init_ctxt = local.span.ctxt();
@ -216,21 +211,19 @@ impl<'tcx> LateLintPass<'tcx> for Default {
.join(", ");
// give correct suggestion if generics are involved (see #6944)
let binding_type = if_chain! {
if let ty::Adt(adt_def, args) = binding_type.kind();
if !args.is_empty();
then {
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let generic_args = args.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{adt_def_ty_name}::<{}>", &tys_str)
} else {
binding_type.to_string()
}
let binding_type = if let ty::Adt(adt_def, args) = binding_type.kind()
&& !args.is_empty()
{
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let generic_args = args.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{adt_def_ty_name}::<{}>", &tys_str)
} else {
binding_type.to_string()
};
let sugg = if ext_with_default {
@ -260,48 +253,42 @@ impl<'tcx> LateLintPass<'tcx> for Default {
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
if_chain! {
if let ExprKind::Call(fn_expr, _) = &expr.kind;
if let ExprKind::Path(qpath) = &fn_expr.kind;
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
then {
// right hand side of assignment is `Default::default`
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
} else {
false
}
if let ExprKind::Call(fn_expr, _) = &expr.kind
&& let ExprKind::Path(qpath) = &fn_expr.kind
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
{
// right hand side of assignment is `Default::default`
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
} else {
false
}
}
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
if_chain! {
if let StmtKind::Semi(later_expr) = this.kind
// only take assignments
if let StmtKind::Semi(later_expr) = this.kind;
if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
&& let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind
// only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement
if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name;
then {
Some((field_ident, assign_rhs))
} else {
None
}
&& let ExprKind::Field(binding, field_ident) = assign_lhs.kind
&& let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind
&& let Some(second_binding_name) = path.segments.last()
&& second_binding_name.ident.name == binding_name
{
Some((field_ident, assign_rhs))
} else {
None
}
}
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if_chain! {
if let Some(parent) = get_parent_expr(cx, expr);
if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
then {
base.hir_id == expr.hir_id
} else {
false
}
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
{
base.hir_id == expr.hir_id
} else {
false
}
}

View File

@ -56,32 +56,30 @@ fn is_alias(ty: hir::Ty<'_>) -> bool {
impl LateLintPass<'_> for DefaultConstructedUnitStructs {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
if_chain!(
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind
// make sure we have a call to `Default::default`
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind;
if let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind;
&& let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind
// make sure this isn't a type alias:
// `<Foo as Bar>::Assoc` cannot be used as a constructor
if !is_alias(*base);
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
&& !is_alias(*base)
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
// make sure we have a struct with no fields (unit struct)
if let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind();
if def.is_struct();
if let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant();
if !var.is_field_list_non_exhaustive();
if !expr.span.from_expansion() && !qpath.span().from_expansion();
then {
span_lint_and_sugg(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
)
}
);
&& let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind()
&& def.is_struct()
&& let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant()
&& !var.is_field_list_non_exhaustive()
&& !expr.span.from_expansion() && !qpath.span().from_expansion()
{
span_lint_and_sugg(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
);
};
}
}

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
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};
@ -82,40 +81,40 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
/// Check whether a passed literal has potential to cause fallback or not.
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 matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)));
if matches!(lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
then {
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return,
};
if !in_external_macro(self.cx.sess(), lit.span)
&& matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)))
&& matches!(
lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
)
{
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return,
};
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
emit_hir_id,
lit.span,
"default numeric fallback might occur",
|diag| {
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
}
);
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
emit_hir_id,
lit.span,
"default numeric fallback might occur",
|diag| {
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
},
);
}
}
}
@ -149,36 +148,33 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
ExprKind::Struct(_, fields, base) => {
let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! {
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants().iter().next();
then {
let fields_def = &variant.fields;
if let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
&& let Some(variant) = adt_def.variants().iter().next()
{
let fields_def = &variant.fields;
// Push field type then visit each field expr.
for field in *fields {
let bound =
fields_def
.iter()
.find_map(|f_def| {
if f_def.ident(self.cx.tcx) == field.ident
{ Some(self.cx.tcx.type_of(f_def.did).instantiate_identity()) }
else { None }
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
// Push field type then visit each field expr.
for field in *fields {
let bound = fields_def.iter().find_map(|f_def| {
if f_def.ident(self.cx.tcx) == field.ident {
Some(self.cx.tcx.type_of(f_def.did).instantiate_identity())
} else {
None
}
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
}
},

View File

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, peel_mid_ty_refs};
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
use clippy_utils::{
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
};
use core::mem;
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
@ -170,9 +171,7 @@ pub struct Dereferencing<'tcx> {
#[derive(Debug)]
struct StateData<'tcx> {
/// Span of the top level expression
span: Span,
hir_id: HirId,
first_expr: &'tcx Expr<'tcx>,
adjusted_ty: Ty<'tcx>,
}
@ -198,6 +197,7 @@ enum State {
},
ExplicitDerefField {
name: Symbol,
derefs_manually_drop: bool,
},
Reborrow {
mutability: Mutability,
@ -242,7 +242,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
// Stop processing sub expressions when a macro call is seen
if expr.span.from_expansion() {
if let Some((state, data)) = self.state.take() {
report(cx, expr, state, data);
report(cx, expr, state, data, cx.typeck_results());
}
return;
}
@ -251,7 +251,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
let Some((kind, sub_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
// The whole chain of reference operations has been seen
if let Some((state, data)) = self.state.take() {
report(cx, expr, state, data);
report(cx, expr, state, data, typeck);
}
return;
};
@ -272,14 +272,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
(Some(use_cx), RefOp::Deref) => {
let sub_ty = typeck.expr_ty(sub_expr);
if let ExprUseNode::FieldAccess(name) = use_cx.node
&& adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union())
&& !use_cx.moved_before_use
&& !ty_contains_field(sub_ty, name.name)
{
self.state = Some((
State::ExplicitDerefField { name: name.name },
State::ExplicitDerefField {
name: name.name,
derefs_manually_drop: is_manually_drop(sub_ty),
},
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty,
},
));
@ -293,8 +295,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.state = Some((
State::ExplicitDeref { mutability: None },
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty,
},
));
@ -313,8 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
mutbl,
},
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty,
},
));
@ -342,8 +342,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
});
let can_auto_borrow = match use_cx.node {
ExprUseNode::Callee => true,
ExprUseNode::FieldAccess(_) => adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()),
ExprUseNode::FieldAccess(_)
if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
{
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
// field expressions when the base type is a union and the parent
// expression is also a field access.
//
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
// deref through `ManuallyDrop<_>` will not compile.
!adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
},
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) => true,
ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
// Check for calls to trait methods where the trait is implemented
// on a reference.
@ -357,11 +367,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
.tcx
.erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target))
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
&& let args = cx
.typeck_results()
.node_args_opt(hir_id)
.map(|args| &args[1..])
.unwrap_or_default()
&& let args =
typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default()
&& let impl_ty =
if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder().inputs()[0]
.is_ref()
@ -436,14 +443,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
count: deref_count - required_refs,
msg,
stability,
for_field_access: match use_cx.node {
ExprUseNode::FieldAccess(name) => Some(name.name),
_ => None,
for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node
&& !use_cx.moved_before_use
{
Some(name.name)
} else {
None
},
}),
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
},
));
@ -455,8 +464,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.state = Some((
State::Borrow { mutability },
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
},
));
@ -501,13 +509,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => {
let adjusted_ty = data.adjusted_ty;
let stability = state.stability;
report(cx, expr, State::DerefedBorrow(state), data);
report(cx, expr, State::DerefedBorrow(state), data, typeck);
if stability.is_deref_stable() {
self.state = Some((
State::Borrow { mutability },
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty,
},
));
@ -517,15 +524,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
let adjusted_ty = data.adjusted_ty;
let stability = state.stability;
let for_field_access = state.for_field_access;
report(cx, expr, State::DerefedBorrow(state), data);
report(cx, expr, State::DerefedBorrow(state), data, typeck);
if let Some(name) = for_field_access
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
&& let sub_expr_ty = typeck.expr_ty(sub_expr)
&& !ty_contains_field(sub_expr_ty, name)
{
self.state = Some((
State::ExplicitDerefField { name },
State::ExplicitDerefField {
name,
derefs_manually_drop: is_manually_drop(sub_expr_ty),
},
StateData {
span: expr.span,
hir_id: expr.hir_id,
first_expr: expr,
adjusted_ty,
},
));
@ -535,8 +545,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.state = Some((
State::ExplicitDeref { mutability: None },
StateData {
span: parent.span,
hir_id: parent.hir_id,
first_expr: parent,
adjusted_ty,
},
));
@ -566,13 +575,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
self.state = state;
},
(Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
(
Some((
State::ExplicitDerefField {
name,
derefs_manually_drop,
},
data,
)),
RefOp::Deref,
) if let sub_expr_ty = typeck.expr_ty(sub_expr)
&& !ty_contains_field(sub_expr_ty, name) =>
{
self.state = Some((State::ExplicitDerefField { name }, data));
self.state = Some((
State::ExplicitDerefField {
name,
derefs_manually_drop: derefs_manually_drop || is_manually_drop(sub_expr_ty),
},
data,
));
},
(Some((state, data)), _) => report(cx, expr, state, data),
(Some((state, data)), _) => report(cx, expr, state, data, typeck),
}
}
@ -597,26 +621,24 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
return;
}
if_chain! {
if !pat.span.from_expansion();
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
if !pat.span.from_expansion()
&& let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind()
// only lint immutable refs, because borrowed `&mut T` cannot be moved out
if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
then {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
self.current_body = self.current_body.or(cx.enclosing_body);
self.ref_locals.insert(
id,
Some(RefPat {
always_deref: true,
spans: vec![pat.span],
app,
replacements: vec![(pat.span, snip.into())],
hir_id: pat.hir_id,
}),
);
}
&& let ty::Ref(_, _, Mutability::Not) = *tam.kind()
{
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
self.current_body = self.current_body.or(cx.enclosing_body);
self.ref_locals.insert(
id,
Some(RefPat {
always_deref: true,
spans: vec![pat.span],
app,
replacements: vec![(pat.span, snip.into())],
hir_id: pat.hir_id,
}),
);
}
}
}
@ -689,6 +711,14 @@ fn try_parse_ref_op<'tcx>(
}
}
// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
adjustments.iter().any(|a| {
let ty = mem::replace(&mut ty, a.target);
matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
})
}
// Checks whether the type for a deref call actually changed the type, not just the mutability of
// the reference.
fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
@ -898,7 +928,13 @@ fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
}
#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData<'tcx>) {
fn report<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
state: State,
data: StateData<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
) {
match state {
State::DerefMethod {
ty_changed_count,
@ -906,8 +942,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
mutbl,
} => {
let mut app = Applicability::MachineApplicable;
let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
let ty = cx.typeck_results().expr_ty(expr);
let (expr_str, _expr_is_macro_call) =
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
let ty = typeck.expr_ty(expr);
let (_, ref_count) = peel_mid_ty_refs(ty);
let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
// a deref call changing &T -> &U requires two deref operators the first time
@ -947,7 +984,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
span_lint_and_sugg(
cx,
EXPLICIT_DEREF_METHODS,
data.span,
data.first_expr.span,
match mutbl {
Mutability::Not => "explicit `deref` method call",
Mutability::Mut => "explicit `deref_mut` method call",
@ -959,26 +996,34 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
},
State::DerefedBorrow(state) => {
let mut app = Applicability::MachineApplicable;
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.hir_id) {
Some(Node::Expr(e)) => match e.kind {
ExprKind::Call(callee, _) if callee.hir_id != data.hir_id => (0, false),
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
_ => (e.precedence().order(), false),
},
_ => (0, false),
};
let sugg = if !snip_is_macro
&& (calls_field || expr.precedence().order() < precedence)
&& !has_enclosing_paren(&snip)
{
format!("({snip})")
} else {
snip.into()
};
diag.span_suggestion(data.span, "change this to", sugg, app);
});
let (snip, snip_is_macro) =
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
span_lint_hir_and_then(
cx,
NEEDLESS_BORROW,
data.first_expr.hir_id,
data.first_expr.span,
state.msg,
|diag| {
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) {
Some(Node::Expr(e)) => match e.kind {
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
_ => (e.precedence().order(), false),
},
_ => (0, false),
};
let sugg = if !snip_is_macro
&& (calls_field || expr.precedence().order() < precedence)
&& !has_enclosing_paren(&snip)
{
format!("({snip})")
} else {
snip.into()
};
diag.span_suggestion(data.first_expr.span, "change this to", sugg, app);
},
);
},
State::ExplicitDeref { mutability } => {
if matches!(
@ -996,7 +1041,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
}
let (prefix, precedence) = if let Some(mutability) = mutability
&& !cx.typeck_results().expr_ty(expr).is_ref()
&& !typeck.expr_ty(expr).is_ref()
{
let prefix = match mutability {
Mutability::Not => "&",
@ -1009,53 +1054,61 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,
data.hir_id,
data.span,
data.first_expr.hir_id,
data.first_expr.span,
"deref which would be done by auto-deref",
|diag| {
let mut app = Applicability::MachineApplicable;
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
let (snip, snip_is_macro) =
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
let sugg =
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
format!("{prefix}({snip})")
} else {
format!("{prefix}{snip}")
};
diag.span_suggestion(data.span, "try", sugg, app);
diag.span_suggestion(data.first_expr.span, "try", sugg, app);
},
);
},
State::ExplicitDerefField { .. } => {
if matches!(
expr.kind,
ExprKind::Block(..)
| ExprKind::ConstBlock(_)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
) && data.adjusted_ty.is_sized(cx.tcx, cx.param_env)
{
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.
return;
}
if let ExprKind::Field(parent_expr, _) = expr.kind
&& let ty::Adt(adt, _) = cx.typeck_results().expr_ty(parent_expr).kind()
&& adt.is_union()
{
// Auto deref does not apply on union field
return;
}
State::ExplicitDerefField {
derefs_manually_drop, ..
} => {
let (snip_span, needs_parens) = if matches!(expr.kind, ExprKind::Field(..))
&& (derefs_manually_drop
|| adjust_derefs_manually_drop(
typeck.expr_adjustments(data.first_expr),
typeck.expr_ty(data.first_expr),
)) {
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
// field expressions when the base type is a union and the parent
// expression is also a field access.
//
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
// deref through `ManuallyDrop<_>` will not compile.
let parent_id = cx.tcx.hir().parent_id(expr.hir_id);
if parent_id == data.first_expr.hir_id {
return;
}
(cx.tcx.hir().get(parent_id).expect_expr().span, true)
} else {
(expr.span, false)
};
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,
data.hir_id,
data.span,
data.first_expr.hir_id,
data.first_expr.span,
"deref which would be done by auto-deref",
|diag| {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
diag.span_suggestion(data.span, "try", snip.into_owned(), app);
let snip = snippet_with_context(cx, snip_span, data.first_expr.span.ctxt(), "..", &mut app).0;
let sugg = if needs_parens {
format!("({snip})")
} else {
snip.into_owned()
};
diag.span_suggestion(data.first_expr.span, "try", sugg, app);
},
);
},

View File

@ -148,83 +148,65 @@ fn check_struct<'tcx>(
}
fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) {
if_chain! {
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind;
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res;
if let variant_id = cx.tcx.parent(id);
if let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id);
if variant_def.fields.is_empty();
if !variant_def.is_field_list_non_exhaustive();
then {
let enum_span = cx.tcx.def_span(adt_def.did());
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
span_lint_and_then(
cx,
DERIVABLE_IMPLS,
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
&& let variant_id = cx.tcx.parent(id)
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id)
&& variant_def.fields.is_empty()
&& !variant_def.is_field_list_non_exhaustive()
{
let enum_span = cx.tcx.def_span(adt_def.did());
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| {
diag.span_suggestion_hidden(
item.span,
"this `impl` can be derived",
|diag| {
diag.span_suggestion_hidden(
item.span,
"remove the manual implementation...",
String::new(),
Applicability::MachineApplicable
);
diag.span_suggestion(
enum_span.shrink_to_lo(),
"...and instead derive it...",
format!(
"#[derive(Default)]\n{indent}",
indent = " ".repeat(indent_enum),
),
Applicability::MachineApplicable
);
diag.span_suggestion(
variant_span.shrink_to_lo(),
"...and mark the default variant",
format!(
"#[default]\n{indent}",
indent = " ".repeat(indent_variant),
),
Applicability::MachineApplicable
);
}
"remove the manual implementation...",
String::new(),
Applicability::MachineApplicable,
);
}
diag.span_suggestion(
enum_span.shrink_to_lo(),
"...and instead derive it...",
format!("#[derive(Default)]\n{indent}", indent = " ".repeat(indent_enum),),
Applicability::MachineApplicable,
);
diag.span_suggestion(
variant_span.shrink_to_lo(),
"...and mark the default variant",
format!("#[default]\n{indent}", indent = " ".repeat(indent_variant),),
Applicability::MachineApplicable,
);
});
}
}
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
self_ty,
..
}) = item.kind;
if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
if !item.span.from_expansion();
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind();
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|attr| attr.doc_str().is_some());
if cx.tcx.hir().attrs(impl_item_hir).is_empty();
then {
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
self_ty,
..
}) = item.kind
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
&& !item.span.from_expansion()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)
&& let impl_item_hir = child.id.hir_id()
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
&& let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
&& !attrs.iter().any(|attr| attr.doc_str().is_some())
&& cx.tcx.hir().attrs(impl_item_hir).is_empty()
{
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}
}
}

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{is_lint_allowed, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
@ -232,42 +231,37 @@ fn check_hash_peq<'tcx>(
ty: Ty<'tcx>,
hash_is_automatically_derived: bool,
) {
if_chain! {
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
then {
// Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
{
// Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
}
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
}
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
// Only care about `impl PartialEq<Foo> for Foo`
// For `impl PartialEq<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
span_lint_and_then(
cx,
DERIVED_HASH_WITH_MANUAL_EQ,
span,
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(
cx.tcx.hir().span(hir_id),
"`PartialEq` implemented here"
);
}
// Only care about `impl PartialEq<Foo> for Foo`
// For `impl PartialEq<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
span_lint_and_then(
cx,
DERIVED_HASH_WITH_MANUAL_EQ,
span,
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialEq` implemented here");
}
);
}
});
}
},
);
}
});
}
}
@ -279,49 +273,38 @@ fn check_ord_partial_ord<'tcx>(
ty: Ty<'tcx>,
ord_is_automatically_derived: bool,
) {
if_chain! {
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
if let Some(def_id) = &trait_ref.trait_def_id();
if *def_id == ord_trait_def_id;
then {
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
&& let Some(def_id) = &trait_ref.trait_def_id()
&& *def_id == ord_trait_def_id
{
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
}
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
}
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
// Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
};
// Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
};
span_lint_and_then(
cx,
DERIVE_ORD_XOR_PARTIAL_ORD,
span,
mess,
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(
cx.tcx.hir().span(hir_id),
"`PartialOrd` implemented here"
);
}
}
);
}
});
}
span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialOrd` implemented here");
}
});
}
});
}
}
@ -394,27 +377,27 @@ fn check_unsafe_derive_deserialize<'tcx>(
visitor.has_unsafe
}
if_chain! {
if let Some(trait_def_id) = trait_ref.trait_def_id();
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
if let ty::Adt(def, _) = ty.kind();
if let Some(local_def_id) = def.did().as_local();
let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
if cx.tcx.inherent_impls(def.did())
if let Some(trait_def_id) = trait_ref.trait_def_id()
&& match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
&& let ty::Adt(def, _) = ty.kind()
&& let Some(local_def_id) = def.did().as_local()
&& let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id)
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
&& cx
.tcx
.inherent_impls(def.did())
.iter()
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
.any(|imp| has_unsafe(cx, imp));
then {
span_lint_and_help(
cx,
UNSAFE_DERIVE_DESERIALIZE,
item.span,
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
None,
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
);
}
.any(|imp| has_unsafe(cx, imp))
{
span_lint_and_help(
cx,
UNSAFE_DERIVE_DESERIALIZE,
item.span,
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
None,
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
);
}
}
@ -431,12 +414,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
return;
}
if_chain! {
if let Some(header) = kind.header();
if header.unsafety == Unsafety::Unsafe;
then {
self.has_unsafe = true;
}
if let Some(header) = kind.header()
&& header.unsafety == Unsafety::Unsafe
{
self.has_unsafe = true;
}
walk_fn(self, kind, decl, body_id, id);
@ -463,30 +444,28 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
if_chain! {
if let ty::Adt(adt, args) = ty.kind();
if cx.tcx.visibility(adt.did()).is_public();
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]);
if let ty::Adt(adt, args) = ty.kind()
&& cx.tcx.visibility(adt.did()).is_public()
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
&& let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
&& !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])
// If all of our fields implement `Eq`, we can implement `Eq` too
if adt
&& adt
.all_fields()
.map(|f| f.ty(cx.tcx, args))
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]));
then {
span_lint_and_sugg(
cx,
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
span.ctxt().outer_expn_data().call_site,
"you are deriving `PartialEq` and can implement `Eq`",
"consider deriving `Eq` as well",
"PartialEq, Eq".to_string(),
Applicability::MachineApplicable,
)
}
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]))
{
span_lint_and_sugg(
cx,
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
span.ctxt().outer_expn_data().call_site,
"you are deriving `PartialEq` and can implement `Eq`",
"consider deriving `Eq` as well",
"PartialEq, Eq".to_string(),
Applicability::MachineApplicable,
);
}
}

View File

@ -31,9 +31,9 @@ pub struct DisallowedNames {
}
impl DisallowedNames {
pub fn new(disallow: FxHashSet<String>) -> Self {
pub fn new(disallowed_names: &[String]) -> Self {
Self {
disallow,
disallow: disallowed_names.iter().cloned().collect(),
test_modules_deep: 0,
}
}

View File

@ -4,13 +4,14 @@ use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
use if_chain::if_chain;
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
use rustc_ast::ast::{Async, Attribute, Fn, FnRetTy, ItemKind};
use rustc_ast::token::CommentKind;
use rustc_ast::{AttrKind, AttrStyle};
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::EmitterWriter;
@ -30,8 +31,8 @@ use rustc_resolve::rustdoc::{
use rustc_session::parse::ParseSess;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::edition::Edition;
use rustc_span::{sym, BytePos, FileName, Pos, Span};
use rustc_span::source_map::{FilePathMapping, SourceMap};
use rustc_span::{sym, BytePos, FileName, Pos, Span};
use std::ops::Range;
use std::{io, thread};
use url::Url;
@ -261,6 +262,53 @@ declare_clippy_lint! {
"`pub fn` or `pub trait` with `# Safety` docs"
}
declare_clippy_lint! {
/// ### What it does
/// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!`
///
/// ### Why is this bad?
/// Triple-slash comments (known as "outer doc comments") apply to items that follow it.
/// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning.
///
/// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which
/// applies to the parent item (i.e. the item that the comment is contained in,
/// usually a module or crate).
///
/// ### Known problems
/// Inner doc comments can only appear before items, so there are certain cases where the suggestion
/// made by this lint is not valid code. For example:
/// ```rs
/// fn foo() {}
/// ///!
/// fn bar() {}
/// ```
/// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment
/// is not valid at that position.
///
/// ### Example
/// In this example, the doc comment is attached to the *function*, rather than the *module*.
/// ```no_run
/// pub mod util {
/// ///! This module contains utility functions.
///
/// pub fn dummy() {}
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// pub mod util {
/// //! This module contains utility functions.
///
/// pub fn dummy() {}
/// }
/// ```
#[clippy::version = "1.70.0"]
pub SUSPICIOUS_DOC_COMMENTS,
suspicious,
"suspicious usage of (outer) doc comments"
}
#[expect(clippy::module_name_repetitions)]
#[derive(Clone)]
pub struct DocMarkdown {
@ -269,9 +317,9 @@ pub struct DocMarkdown {
}
impl DocMarkdown {
pub fn new(valid_idents: FxHashSet<String>) -> Self {
pub fn new(valid_idents: &[String]) -> Self {
Self {
valid_idents,
valid_idents: valid_idents.iter().cloned().collect(),
in_trait_impl: false,
}
}
@ -285,6 +333,7 @@ impl_lint_pass!(DocMarkdown => [
MISSING_PANICS_DOC,
NEEDLESS_DOCTEST_MAIN,
UNNECESSARY_SAFETY_DOC,
SUSPICIOUS_DOC_COMMENTS
]);
impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
@ -428,25 +477,21 @@ fn lint_for_missing_headers(
span,
"docs for function returning `Result` missing `# Errors` section",
);
} else {
if_chain! {
if let Some(body_id) = body_id;
if let Some(future) = cx.tcx.lang_items().future_trait();
let typeck = cx.tcx.typeck_body(body_id);
let body = cx.tcx.hir().body(body_id);
let ret_ty = typeck.expr_ty(body.value);
if implements_trait(cx, ret_ty, future, &[]);
if let ty::Coroutine(_, subs, _) = ret_ty.kind();
if is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result);
then {
span_lint(
cx,
MISSING_ERRORS_DOC,
span,
"docs for function returning `Result` missing `# Errors` section",
);
}
}
} else if let Some(body_id) = body_id
&& let Some(future) = cx.tcx.lang_items().future_trait()
&& let typeck = cx.tcx.typeck_body(body_id)
&& let body = cx.tcx.hir().body(body_id)
&& let ret_ty = typeck.expr_ty(body.value)
&& implements_trait(cx, ret_ty, future, &[])
&& let ty::Coroutine(_, subs, _) = ret_ty.kind()
&& is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
{
span_lint(
cx,
MISSING_ERRORS_DOC,
span,
"docs for function returning `Result` missing `# Errors` section",
);
}
}
}
@ -483,6 +528,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
return None;
}
check_almost_inner_doc(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
let mut doc = String::new();
for fragment in &fragments {
@ -511,6 +558,43 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
))
}
/// Looks for `///!` and `/**!` comments, which were probably meant to be `//!` and `/*!`
fn check_almost_inner_doc(cx: &LateContext<'_>, attrs: &[Attribute]) {
let replacements: Vec<_> = attrs
.iter()
.filter_map(|attr| {
if let AttrKind::DocComment(com_kind, sym) = attr.kind
&& let AttrStyle::Outer = attr.style
&& let Some(com) = sym.as_str().strip_prefix('!')
{
let sugg = match com_kind {
CommentKind::Line => format!("//!{com}"),
CommentKind::Block => format!("/*!{com}*/"),
};
Some((attr.span, sugg))
} else {
None
}
})
.collect();
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
span_lint_and_then(
cx,
SUSPICIOUS_DOC_COMMENTS,
lo_span.to(hi_span),
"this is an outer doc comment and does not apply to the parent module or crate",
|diag| {
diag.multipart_suggestion(
"use an inner doc comment to document the parent module or crate",
replacements,
Applicability::MaybeIncorrect,
);
},
);
}
}
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
#[allow(clippy::too_many_lines)] // Only a big match statement
@ -649,11 +733,12 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<u
rustc_span::create_session_globals_then(edition, || {
let filename = FileName::anon_source_code(&code);
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle =
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
#[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_span_handler
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let sess = ParseSess::with_span_handler(handler, sm);
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::peel_blocks;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
@ -36,31 +35,30 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
impl LateLintPass<'_> for EmptyDrop {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind;
if trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait();
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
let func_expr = peel_blocks(func_expr);
if let ExprKind::Block(block, _) = func_expr.kind;
if block.stmts.is_empty() && block.expr.is_none();
then {
span_lint_and_sugg(
cx,
EMPTY_DROP,
item.span,
"empty drop implementation",
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect
);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind
&& trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait()
&& let impl_item_hir = child.id.hir_id()
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
&& let func_expr = peel_blocks(func_expr)
&& let ExprKind::Block(block, _) = func_expr.kind
&& block.stmts.is_empty()
&& block.expr.is_none()
{
span_lint_and_sugg(
cx,
EMPTY_DROP,
item.span,
"empty drop implementation",
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect,
);
}
}
}

View File

@ -114,27 +114,23 @@ impl LateLintPass<'_> for EndianBytes {
return;
}
if_chain! {
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind;
if args.is_empty();
let ty = cx.typeck_results().expr_ty(receiver);
if ty.is_primitive_ty();
if maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty);
then {
return;
}
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind
&& args.is_empty()
&& let ty = cx.typeck_results().expr_ty(receiver)
&& ty.is_primitive_ty()
&& maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty)
{
return;
}
if_chain! {
if let ExprKind::Call(function, ..) = expr.kind;
if let ExprKind::Path(qpath) = function.kind;
if let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id();
if let Some(function_name) = cx.get_def_path(def_id).last();
let ty = cx.typeck_results().expr_ty(expr);
if ty.is_primitive_ty();
then {
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
}
if let ExprKind::Call(function, ..) = expr.kind
&& let ExprKind::Path(qpath) = function.kind
&& let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id()
&& let Some(function_name) = cx.get_def_path(def_id).last()
&& let ty = cx.typeck_results().expr_ty(expr)
&& ty.is_primitive_ty()
{
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
}
}
}

View File

@ -8,8 +8,8 @@ use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, TraitRef, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_span::symbol::kw;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
#[derive(Copy, Clone)]

View File

@ -247,8 +247,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig:
/// This is needed because rustc is unable to late bind early-bound regions in a function signature.
fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool {
fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
matches!(from_region.kind(), RegionKind::ReBound(..))
&& !matches!(to_region.kind(), RegionKind::ReBound(..))
matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..))
}
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool {

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::indent_of;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -71,40 +70,31 @@ declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
impl LateLintPass<'_> for ExhaustiveItems {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! {
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
then {
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
if v.fields().iter().any(|f| {
!cx.tcx.visibility(f.def_id).is_public()
}) {
// skip structs with private fields
return;
}
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
} else {
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
};
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(
cx,
lint,
item.span,
msg,
|diag| {
let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion(suggestion_span,
"try adding #[non_exhaustive]",
sugg,
Applicability::MaybeIncorrect);
}
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
{
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
if v.fields().iter().any(|f| !cx.tcx.visibility(f.def_id).is_public()) {
// skip structs with private fields
return;
}
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
} else {
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
};
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(cx, lint, item.span, msg, |diag| {
let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion(
suggestion_span,
"try adding #[non_exhaustive]",
sugg,
Applicability::MaybeIncorrect,
);
}
});
}
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_entrypoint_fn;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -42,19 +41,17 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(path_expr, _args) = e.kind;
if let ExprKind::Path(ref path) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::process_exit, def_id);
let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent);
if let ExprKind::Call(path_expr, _args) = e.kind
&& let ExprKind::Path(ref path) = path_expr.kind
&& let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::process_exit, def_id)
&& let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id
&& let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent)
// If the next item up is a function we check if it is an entry point
// and only then emit a linter warning
if !is_entrypoint_fn(cx, parent.to_def_id());
then {
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
}
&& !is_entrypoint_fn(cx, parent.to_def_id())
{
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
}
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, path_def_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
@ -101,30 +100,28 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
/// If `kind` is a block that looks like `{ let result = $expr; result }` then
/// returns $expr. Otherwise returns `kind`.
fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) -> &'tcx ExprKind<'hir> {
if_chain! {
if let ExprKind::Block(block, _label @ None) = kind;
if let Block {
if let ExprKind::Block(block, _label @ None) = kind
&& let Block {
stmts: [Stmt { kind: StmtKind::Local(local), .. }],
expr: Some(expr_end_of_block),
rules: BlockCheckMode::DefaultBlock,
..
} = block;
} = block
// Find id of the local that expr_end_of_block resolves to
if let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind;
if let Res::Local(expr_res) = expr_path.res;
if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res);
&& let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind
&& let Res::Local(expr_res) = expr_path.res
&& let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res)
// Find id of the local we found in the block
if let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind;
&& let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind
// If those two are the same hir id
if res_pat.hir_id == local_hir_id;
&& res_pat.hir_id == local_hir_id
if let Some(init) = local.init;
then {
return &init.kind;
}
&& let Some(init) = local.init
{
return &init.kind;
}
kind
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::method_chain_args;
use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
@ -53,13 +52,13 @@ declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
// check for `impl From<???> for ..`
if_chain! {
if let hir::ItemKind::Impl(impl_) = &item.kind;
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id);
if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id);
then {
lint_impl_body(cx, item.span, impl_.items);
}
if let hir::ItemKind::Impl(impl_) = &item.kind
&& let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id)
&& cx
.tcx
.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id)
{
lint_impl_body(cx, item.span, impl_.items);
}
}
}
@ -98,34 +97,33 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
}
for impl_item in impl_items {
if_chain! {
if impl_item.ident.name == sym::from;
if let ImplItemKind::Fn(_, body_id) =
cx.tcx.hir().impl_item(impl_item.id).kind;
then {
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
result: Vec::new(),
};
fpu.visit_expr(body.value);
if impl_item.ident.name == sym::from
&& let ImplItemKind::Fn(_, body_id) = cx.tcx.hir().impl_item(impl_item.id).kind
{
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
result: Vec::new(),
};
fpu.visit_expr(body.value);
// if we've found one, lint
if !fpu.result.is_empty() {
span_lint_and_then(
cx,
FALLIBLE_IMPL_FROM,
impl_span,
"consider implementing `TryFrom` instead",
move |diag| {
diag.help(
"`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail");
diag.span_note(fpu.result, "potential failure(s)");
});
}
// if we've found one, lint
if !fpu.result.is_empty() {
span_lint_and_then(
cx,
FALLIBLE_IMPL_FROM,
impl_span,
"consider implementing `TryFrom` instead",
move |diag| {
diag.help(
"`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail",
);
diag.span_note(fpu.result, "potential failure(s)");
},
);
}
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal;
use if_chain::if_chain;
use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -64,73 +63,70 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if let ty::Float(fty) = *ty.kind();
if let hir::ExprKind::Lit(lit) = expr.kind;
if let LitKind::Float(sym, lit_float_ty) = lit.node;
then {
let sym_str = sym.as_str();
let formatter = FloatFormat::new(sym_str);
// Try to bail out if the float is for sure fine.
// If its within the 2 decimal digits of being out of precision we
// check if the parsed representation is the same as the string
// since we'll need the truncated string anyway.
let digits = count_digits(sym_str);
let max = max_digits(fty);
let type_suffix = match lit_float_ty {
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Unsuffixed => None
};
let (is_whole, is_inf, mut float_str) = match fty {
FloatTy::F32 => {
let value = sym_str.parse::<f32>().unwrap();
if let ty::Float(fty) = *ty.kind()
&& let hir::ExprKind::Lit(lit) = expr.kind
&& let LitKind::Float(sym, lit_float_ty) = lit.node
{
let sym_str = sym.as_str();
let formatter = FloatFormat::new(sym_str);
// Try to bail out if the float is for sure fine.
// If its within the 2 decimal digits of being out of precision we
// check if the parsed representation is the same as the string
// since we'll need the truncated string anyway.
let digits = count_digits(sym_str);
let max = max_digits(fty);
let type_suffix = match lit_float_ty {
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Unsuffixed => None,
};
let (is_whole, is_inf, mut float_str) = match fty {
FloatTy::F32 => {
let value = sym_str.parse::<f32>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
};
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
};
if is_inf {
return;
}
if is_inf {
return;
}
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be
// incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
span_lint_and_sugg(
cx,
LOSSY_FLOAT_LITERAL,
expr.span,
"literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be
// incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
} else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg(
cx,
EXCESSIVE_PRECISION,
LOSSY_FLOAT_LITERAL,
expr.span,
"float has excessive precision",
"consider changing the type or truncating it to",
"literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
}
} else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg(
cx,
EXCESSIVE_PRECISION,
expr.span,
"float has excessive precision",
"consider changing the type or truncating it to",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
}
}
}

View File

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{
eq_expr_value, get_parent_expr, higher, in_constant, is_no_std_crate, numeric_literal, peel_blocks, sugg,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -133,30 +132,25 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
expr = inner_expr;
}
if_chain! {
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind()
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
if let ExprKind::Lit(lit) = &expr.kind;
if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
then {
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') {
"0"
} else {
""
},
float_ty.name_str()
).into();
&& let ExprKind::Lit(lit) = &expr.kind
&& let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') { "0" } else { "" },
float_ty.name_str()
)
.into();
suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op)
};
}
suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op),
};
}
suggestion.maybe_par()
@ -359,35 +353,59 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
) = receiver.kind
{
// check if expression of the form x * x + y * y
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
if eq_expr_value(cx, lmul_lhs, lmul_rhs);
if eq_expr_value(cx, rmul_lhs, rmul_rhs);
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, "..")));
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lmul_lhs,
lmul_rhs,
) = add_lhs.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
rmul_lhs,
rmul_rhs,
) = add_rhs.kind
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
Sugg::hir(cx, rmul_lhs, "..")
));
}
// check if expression of the form x.powi(2) + y.powi(2)
if_chain! {
if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. },
largs_0, [largs_1, ..],
_
) = &add_lhs.kind;
if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. },
rargs_0, [rargs_1, ..],
_
) = &add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1);
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1);
if Int(2) == lvalue && Int(2) == rvalue;
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
}
if let ExprKind::MethodCall(
PathSegment {
ident: lmethod_name, ..
},
largs_0,
[largs_1, ..],
_,
) = &add_lhs.kind
&& let ExprKind::MethodCall(
PathSegment {
ident: rmethod_name, ..
},
rargs_0,
[rargs_1, ..],
_,
) = &add_rhs.kind
&& lmethod_name.as_str() == "powi"
&& rmethod_name.as_str() == "powi"
&& let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1)
&& let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1)
&& Int(2) == lvalue
&& Int(2) == rvalue
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, largs_0, "..").maybe_par(),
Sugg::hir(cx, rargs_0, "..")
));
}
}
@ -411,39 +429,44 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some(value) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
if path.ident.name.as_str() == "exp";
then {
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!(
"{}.exp_m1()",
Sugg::hir(cx, self_arg, "..").maybe_par()
),
Applicability::MachineApplicable,
);
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
lhs,
rhs,
) = expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = constant(cx, cx.typeck_results(), rhs)
&& (F32(1.0) == value || F64(1.0) == value)
&& let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
&& path.ident.name.as_str() == "exp"
{
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
Applicability::MachineApplicable,
);
}
}
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if cx.typeck_results().expr_ty(rhs).is_floating_point();
then {
return Some((lhs, rhs));
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lhs,
rhs,
) = &expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
return Some((lhs, rhs));
}
None
@ -553,60 +576,72 @@ fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a
}
fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr);
let if_body_expr = peel_blocks(then);
let else_body_expr = peel_blocks(r#else);
if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
then {
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
positive_abs_sugg
} else {
negative_abs_sugg
}
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
{
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
positive_abs_sugg
} else {
return;
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
sugg.0,
"try",
sugg.1,
Applicability::MachineApplicable,
);
}
negative_abs_sugg
}
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
} else {
return;
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
sugg.0,
"try",
sugg.1,
Applicability::MachineApplicable,
);
}
}
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
then {
return method_name_a.as_str() == method_name_b.as_str() &&
args_a.len() == args_b.len() &&
(
["ln", "log2", "log10"].contains(&method_name_a.as_str()) ||
method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])
);
}
if let ExprKind::MethodCall(
PathSegment {
ident: method_name_a, ..
},
_,
args_a,
_,
) = expr_a.kind
&& let ExprKind::MethodCall(
PathSegment {
ident: method_name_b, ..
},
_,
args_b,
_,
) = expr_b.kind
{
return method_name_a.as_str() == method_name_b.as_str()
&& args_a.len() == args_b.len()
&& (["ln", "log2", "log10"].contains(&method_name_a.as_str())
|| method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
}
false
@ -614,103 +649,98 @@ fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
// check if expression of the form x.logN() / y.logN()
if_chain! {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind;
if let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind;
then {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),),
Applicability::MachineApplicable,
);
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = &expr.kind
&& are_same_base_logs(cx, lhs, rhs)
&& let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind
&& let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!(
"{}.log({})",
Sugg::hir(cx, largs_self, "..").maybe_par(),
Sugg::hir(cx, rargs_self, ".."),
),
Applicability::MachineApplicable,
);
}
}
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = &expr.kind;
if let ExprKind::Binary(
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = &expr.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
mul_lhs,
mul_rhs,
) = &div_lhs.kind;
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs);
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs);
then {
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
(F32(180_f32) == lvalue || F64(180_f64) == lvalue)
) = &div_lhs.kind
&& let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs)
&& let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs)
{
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
if_chain! {
if let ExprKind::Lit(literal) = mul_lhs.kind;
if let ast::LitKind::Float(ref value, float_type) = literal.node;
if float_type == ast::LitFloatType::Unsuffixed;
then {
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
}
}
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
} else if
(F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
(F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
if_chain! {
if let ExprKind::Lit(literal) = mul_lhs.kind;
if let ast::LitKind::Float(ref value, float_type) = literal.node;
if float_type == ast::LitFloatType::Unsuffixed;
then {
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
}
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
}
}
}

View File

@ -8,7 +8,6 @@ use clippy_utils::macros::{
};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
@ -404,49 +403,43 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
}
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, [], 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);
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
let (n_needed_derefs, target) =
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter());
if implements_trait(cx, target, display_trait_id, &[]);
if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait();
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.hi()),
&format!(
"`to_string` applied to a type that implements `Display` in `{name}!` args"
),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!(
"`to_string` applied to a type that implements `Display` in `{name}!` args"
),
"use this",
format!(
"{}{:*>n_needed_derefs$}{receiver_snippet}",
if needs_ref { "&" } else { "" },
""
),
Applicability::MachineApplicable,
);
}
if !value.span.from_expansion()
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
&& let (n_needed_derefs, target) =
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
&& implements_trait(cx, target, display_trait_id, &[])
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.hi()),
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"use this",
format!(
"{}{:*>n_needed_derefs$}{receiver_snippet}",
if needs_ref { "&" } else { "" },
""
),
Applicability::MachineApplicable,
);
}
}
}

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use if_chain::if_chain;
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
@ -141,27 +140,25 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
}
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind
// Get the hir_id of the object we are calling the method on
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind;
// Is the method to_string() ?
if path.ident.name == sym::to_string;
&& path.ident.name == sym::to_string
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
// separately)
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& is_diag_trait_item(cx, expr_def_id, sym::ToString)
// Is the method is called on self
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
if let [segment] = path.segments;
if segment.ident.name == kw::SelfLower;
then {
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
);
}
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
&& let [segment] = path.segments
&& segment.ident.name == kw::SelfLower
{
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
);
}
}
@ -215,55 +212,53 @@ fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_
}
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
then {
let replacement = match name {
sym::print_macro | sym::eprint_macro => "write",
sym::println_macro | sym::eprintln_macro => "writeln",
_ => return,
};
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
{
let replacement = match name {
sym::print_macro | sym::eprint_macro => "write",
sym::println_macro | sym::eprintln_macro => "writeln",
_ => return,
};
let name = name.as_str().strip_suffix("_macro").unwrap();
let name = name.as_str().strip_suffix("_macro").unwrap();
span_lint_and_sugg(
cx,
PRINT_IN_FORMAT_IMPL,
macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
"replace with",
if let Some(formatter_name) = impl_trait.formatter_name {
format!("{replacement}!({formatter_name}, ..)")
} else {
format!("{replacement}!(..)")
},
Applicability::HasPlaceholders,
);
}
span_lint_and_sugg(
cx,
PRINT_IN_FORMAT_IMPL,
macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
"replace with",
if let Some(formatter_name) = impl_trait.formatter_name {
format!("{replacement}!({formatter_name}, ..)")
} else {
format!("{replacement}!(..)")
},
Applicability::HasPlaceholders,
);
}
}
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if_chain! {
if impl_item.ident.name == sym::fmt;
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
if let Some(did) = trait_ref.trait_def_id();
if let Some(name) = cx.tcx.get_diagnostic_name(did);
if matches!(name, sym::Debug | sym::Display);
then {
let body = cx.tcx.hir().body(body_id);
let formatter_name = body.params.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
if impl_item.ident.name == sym::fmt
&& let ImplItemKind::Fn(_, body_id) = impl_item.kind
&& let Some(Impl {
of_trait: Some(trait_ref),
..
}) = get_parent_as_impl(cx.tcx, impl_item.hir_id())
&& let Some(did) = trait_ref.trait_def_id()
&& let Some(name) = cx.tcx.get_diagnostic_name(did)
&& matches!(name, sym::Debug | sym::Display)
{
let body = cx.tcx.hir().body(body_id);
let formatter_name = body
.params
.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
Some(FormatTraitNames {
name,
formatter_name,
})
} else {
None
}
Some(FormatTraitNames { name, formatter_name })
} else {
None
}
}

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
use clippy_utils::is_span_if;
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -168,93 +167,84 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! {
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind
&& !lhs.span.from_expansion() && !rhs.span.from_expansion()
// span between BinOp LHS and RHS
let binop_span = lhs.span.between(rhs.span);
&& let binop_span = lhs.span.between(rhs.span)
// if RHS is an UnOp
if let ExprKind::Unary(op, ref un_rhs) = rhs.kind;
&& let ExprKind::Unary(op, ref un_rhs) = rhs.kind
// from UnOp operator to UnOp operand
let unop_operand_span = rhs.span.until(un_rhs.span);
if let Some(binop_snippet) = snippet_opt(cx, binop_span);
if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span);
let binop_str = BinOpKind::to_string(&binop.node);
&& let unop_operand_span = rhs.span.until(un_rhs.span)
&& let Some(binop_snippet) = snippet_opt(cx, binop_span)
&& let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span)
&& let binop_str = BinOpKind::to_string(&binop.node)
// no space after BinOp operator and space after UnOp operator
if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ');
then {
let unop_str = UnOp::to_string(op);
let eqop_span = lhs.span.between(un_rhs.span);
span_lint_and_help(
cx,
SUSPICIOUS_UNARY_OP_FORMATTING,
eqop_span,
&format!(
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
`{binop_str}{unop_str}` is a single operator"
),
None,
&format!(
"put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"
),
);
}
&& binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ')
{
let unop_str = UnOp::to_string(op);
let eqop_span = lhs.span.between(un_rhs.span);
span_lint_and_help(
cx,
SUSPICIOUS_UNARY_OP_FORMATTING,
eqop_span,
&format!(
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
`{binop_str}{unop_str}` is a single operator"
),
None,
&format!("put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"),
);
}
}
/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! {
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
if is_block(else_) || is_if(else_);
if !then.span.from_expansion() && !else_.span.from_expansion();
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::If(_, then, Some(else_)) = &expr.kind
&& (is_block(else_) || is_if(else_))
&& !then.span.from_expansion() && !else_.span.from_expansion()
&& !in_external_macro(cx.sess(), expr.span)
// workaround for rust-lang/rust#43081
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
&& expr.span.lo().0 != 0 && expr.span.hi().0 != 0
// this will be a span from the closing } of the “then” block (excluding) to
// the “if” of the “else if” block (excluding)
let else_span = then.span.between(else_.span);
&& let else_span = then.span.between(else_.span)
// the snippet should look like " else \n " with maybe comments anywhere
// its bad when there is a \n after the “else”
if let Some(else_snippet) = snippet_opt(cx, else_span);
if let Some((pre_else, post_else)) = else_snippet.split_once("else");
if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
then {
// Allow allman style braces `} \n else \n {`
if_chain! {
if is_block(else_);
if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
// Exactly one eol before and after the else
if !pre_else_post_eol.contains('\n');
if !post_else_post_eol.contains('\n');
then {
return;
}
}
// Don't warn if the only thing inside post_else_post_eol is a comment block.
let trimmed_post_else_post_eol = post_else_post_eol.trim();
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
return
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this is an `else {else_desc}` but the formatting might hide it"),
None,
&format!(
"to remove this lint, remove the `else` or remove the new line between \
`else` and `{else_desc}`",
),
);
&& let Some(else_snippet) = snippet_opt(cx, else_span)
&& let Some((pre_else, post_else)) = else_snippet.split_once("else")
&& let Some((_, post_else_post_eol)) = post_else.split_once('\n')
{
// Allow allman style braces `} \n else \n {`
if is_block(else_)
&& let Some((_, pre_else_post_eol)) = pre_else.split_once('\n')
// Exactly one eol before and after the else
&& !pre_else_post_eol.contains('\n')
&& !post_else_post_eol.contains('\n')
{
return;
}
// Don't warn if the only thing inside post_else_post_eol is a comment block.
let trimmed_post_else_post_eol = post_else_post_eol.trim();
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
return;
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this is an `else {else_desc}` but the formatting might hide it"),
None,
&format!(
"to remove this lint, remove the `else` or remove the new line between \
`else` and `{else_desc}`",
),
);
}
}
@ -272,61 +262,56 @@ fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize {
fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Array(ref array) = expr.kind {
for element in array {
if_chain! {
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
if has_unary_equivalent(op.node) && lhs.span.eq_ctxt(op.span);
let space_span = lhs.span.between(op.span);
if let Some(space_snippet) = snippet_opt(cx, space_span);
let lint_span = lhs.span.with_lo(lhs.span.hi());
if space_snippet.contains('\n');
if indentation(cx, op.span) <= indentation(cx, lhs.span);
then {
span_lint_and_note(
cx,
POSSIBLE_MISSING_COMMA,
lint_span,
"possibly missing a comma here",
None,
"to remove this lint, add a comma or write the expr in a single line",
);
}
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind
&& has_unary_equivalent(op.node)
&& lhs.span.eq_ctxt(op.span)
&& let space_span = lhs.span.between(op.span)
&& let Some(space_snippet) = snippet_opt(cx, space_span)
&& let lint_span = lhs.span.with_lo(lhs.span.hi())
&& space_snippet.contains('\n')
&& indentation(cx, op.span) <= indentation(cx, lhs.span)
{
span_lint_and_note(
cx,
POSSIBLE_MISSING_COMMA,
lint_span,
"possibly missing a comma here",
None,
"to remove this lint, add a comma or write the expr in a single line",
);
}
}
}
}
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
if_chain! {
if !first.span.from_expansion() && !second.span.from_expansion();
if matches!(first.kind, ExprKind::If(..));
if is_block(second) || is_if(second);
if !first.span.from_expansion() && !second.span.from_expansion()
&& matches!(first.kind, ExprKind::If(..))
&& (is_block(second) || is_if(second))
// Proc-macros can give weird spans. Make sure this is actually an `if`.
if is_span_if(cx, first.span);
&& is_span_if(cx, first.span)
// If there is a line break between the two expressions, don't lint.
// If there is a non-whitespace character, this span came from a proc-macro.
let else_span = first.span.between(second.span);
if let Some(else_snippet) = snippet_opt(cx, else_span);
if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace());
then {
let (looks_like, next_thing) = if is_if(second) {
("an `else if`", "the second `if`")
} else {
("an `else {..}`", "the next block")
};
&& let else_span = first.span.between(second.span)
&& let Some(else_snippet) = snippet_opt(cx, else_span)
&& !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace())
{
let (looks_like, next_thing) = if is_if(second) {
("an `else if`", "the second `if`")
} else {
("an `else {..}`", "the next block")
};
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this looks like {looks_like} but the `else` is missing"),
None,
&format!(
"to remove this lint, add the missing `else` or add a new line before {next_thing}",
),
);
}
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this looks like {looks_like} but the `else` is missing"),
None,
&format!("to remove this lint, add the missing `else` or add a new line before {next_thing}",),
);
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_integer_literal;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -46,52 +45,41 @@ declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if_chain! {
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind
// check if the first part of the path is some integer primitive
if let TyKind::Path(ty_qpath) = &ty.kind;
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
if let def::Res::PrimTy(prim_ty) = ty_res;
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
&& let TyKind::Path(ty_qpath) = &ty.kind
&& let ty_res = cx.qpath_res(ty_qpath, ty.hir_id)
&& let def::Res::PrimTy(prim_ty) = ty_res
&& matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_))
// check if the second part of the path indeed calls the associated
// function `from_str_radix`
if pathseg.ident.name.as_str() == "from_str_radix";
&& pathseg.ident.name.as_str() == "from_str_radix"
// check if the second argument is a primitive `10`
if is_integer_literal(radix, 10);
&& is_integer_literal(radix, 10)
{
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) { expr } else { &src }
} else {
&src
};
then {
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) {
expr
} else {
&src
}
} else {
&src
};
let sugg =
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
let sugg = Sugg::hir_with_applicability(
cx,
expr,
"<string>",
&mut Applicability::MachineApplicable
).maybe_par();
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
Applicability::MaybeIncorrect
);
}
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
Applicability::MaybeIncorrect,
);
}
}
}

View File

@ -5,18 +5,10 @@ use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind};
use rustc_lint::LateContext;
use rustc_span::symbol::Ident;
use rustc_span::{BytePos, Span};
use super::IMPL_TRAIT_IN_PARAMS;
fn report(
cx: &LateContext<'_>,
param: &GenericParam<'_>,
ident: &Ident,
generics: &Generics<'_>,
first_param_span: Span,
) {
fn report(cx: &LateContext<'_>, param: &GenericParam<'_>, generics: &Generics<'_>) {
// No generics with nested generics, and no generics like FnMut(x)
span_lint_and_then(
cx,
@ -35,12 +27,7 @@ fn report(
);
} else {
diag.span_suggestion_with_style(
Span::new(
first_param_span.lo() - rustc_span::BytePos(1),
ident.span.hi(),
ident.span.ctxt(),
ident.span.parent(),
),
generics.span,
"add a type parameter",
format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders,
@ -52,54 +39,47 @@ fn report(
}
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
if_chain! {
if let FnKind::ItemFn(ident, generics, _) = kind;
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
if !is_in_test_function(cx.tcx, hir_id);
then {
for param in generics.params {
if param.is_impl_trait() {
report(cx, param, ident, generics, body.params[0].span);
};
}
if let FnKind::ItemFn(_, generics, _) = kind
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, hir_id)
{
for param in generics.params {
if param.is_impl_trait() {
report(cx, param, generics);
};
}
}
}
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
if_chain! {
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id());
if let hir::ItemKind::Impl(impl_) = item.kind;
if let hir::Impl { of_trait, .. } = *impl_;
if of_trait.is_none();
let body = cx.tcx.hir().body(body_id);
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
if !is_in_test_function(cx.tcx, impl_item.hir_id());
then {
for param in impl_item.generics.params {
if param.is_impl_trait() {
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
}
if let ImplItemKind::Fn(_, body_id) = impl_item.kind
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id())
&& let hir::ItemKind::Impl(impl_) = item.kind
&& let hir::Impl { of_trait, .. } = *impl_
&& of_trait.is_none()
&& let body = cx.tcx.hir().body(body_id)
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, impl_item.hir_id())
{
for param in impl_item.generics.params {
if param.is_impl_trait() {
report(cx, param, impl_item.generics);
}
}
}
}
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
if_chain! {
if !avoid_breaking_exported_api;
if let TraitItemKind::Fn(_, _) = trait_item.kind;
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
if !avoid_breaking_exported_api
&& let TraitItemKind::Fn(_, _) = trait_item.kind
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id())
// ^^ (Will always be a trait)
if !item.vis_span.is_empty(); // Is public
if !is_in_test_function(cx.tcx, trait_item.hir_id());
then {
for param in trait_item.generics.params {
if param.is_impl_trait() {
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
}
&& !item.vis_span.is_empty() // Is public
&& !is_in_test_function(cx.tcx, trait_item.hir_id())
{
for param in trait_item.generics.params {
if param.is_impl_trait() {
report(cx, param, trait_item.generics);
}
}
}

View File

@ -43,15 +43,13 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
// Body must be &(mut) <self_data>.name
// self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if_chain! {
if let ExprKind::Block(block,_) = body.value.kind;
if block.stmts.is_empty();
if let Some(block_expr) = block.expr;
then {
block_expr
} else {
return;
}
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(block_expr) = block.expr
{
block_expr
} else {
return;
};
let expr_span = block_expr.span;
@ -61,14 +59,12 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
} else {
block_expr
};
let (self_data, used_ident) = if_chain! {
if let ExprKind::Field(self_data, ident) = expr.kind;
if ident.name.as_str() != name;
then {
(self_data, ident)
} else {
return;
}
let (self_data, used_ident) = if let ExprKind::Field(self_data, ident) = expr.kind
&& ident.name.as_str() != name
{
(self_data, ident)
} else {
return;
};
let mut used_field = None;

View File

@ -86,59 +86,60 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S
}
fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
if_chain! {
if let Adt(adt, subst) = err_ty.kind();
if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local();
if let Some(hir::Node::Item(item)) = cx
.tcx
.hir()
.find_by_def_id(local_def_id);
if let hir::ItemKind::Enum(ref def, _) = item.kind;
then {
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold
{
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag| {
diag.span_label(
def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size),
);
if let Adt(adt, subst) = err_ty.kind()
&& let Some(local_def_id) = err_ty
.ty_adt_def()
.expect("already checked this is adt")
.did()
.as_local()
&& let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id(local_def_id)
&& let hir::ItemKind::Enum(ref def, _) = item.kind
{
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold
{
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag| {
diag.span_label(
def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size),
);
for variant in variants {
if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind];
diag.span_label(
variant_def.span,
format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size),
);
}
for variant in variants {
if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind];
diag.span_label(
variant_def.span,
format!(
"the variant `{}` contains at least {} bytes",
variant_def.ident, variant.size
),
);
}
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
}
);
}
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
else {
let ty_size = approx_ty_size(cx, err_ty);
if ty_size >= large_err_threshold {
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag: &mut Diagnostic| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
} else {
let ty_size = approx_ty_size(cx, err_ty);
if ty_size >= large_err_threshold {
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag: &mut Diagnostic| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
}
}

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Diagnostic;
use rustc_hir::intravisit::{self as visit, Visitor};
use rustc_hir::{Expr, ExprKind};
@ -127,15 +126,13 @@ impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
}
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind;
if path.ident.as_str() == "lock";
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if is_type_diagnostic_item(cx, ty, sym::Mutex);
then {
Some(self_arg)
} else {
None
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
&& path.ident.as_str() == "lock"
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
{
Some(self_arg)
} else {
None
}
}

View File

@ -10,10 +10,8 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{Ty, TypeckResults};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_span::symbol::sym;
use if_chain::if_chain;
use rustc_span::Span;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt};
@ -337,42 +335,38 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(fun, args) = e.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
if let Some(ty_did) = ty_path.res.opt_def_id();
then {
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
return;
}
if let ExprKind::Call(fun, args) = e.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind
&& let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind
&& let Some(ty_did) = ty_path.res.opt_def_id()
{
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
return;
}
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashMap::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashSet::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashMap::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashSet::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
@ -40,42 +39,58 @@ declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let ExprKind::If(cond, then, None) = expr.kind;
if let ExprKind::DropTemps(expr1) = cond.kind;
if let Some((c, op_node, l)) = get_const(cx, expr1);
if let BinOpKind::Ne | BinOpKind::Lt = op_node;
if let ExprKind::Block(block, None) = then.kind;
if let Block {
if let ExprKind::If(cond, then, None) = expr.kind
&& let ExprKind::DropTemps(expr1) = cond.kind
&& let Some((c, op_node, l)) = get_const(cx, expr1)
&& let BinOpKind::Ne | BinOpKind::Lt = op_node
&& let ExprKind::Block(block, None) = then.kind
&& let Block {
stmts:
[Stmt
{ kind: StmtKind::Expr(ex) | StmtKind::Semi(ex), .. }],
expr: None, ..} |
Block { stmts: [], expr: Some(ex), ..} = block;
if let ExprKind::AssignOp(op1, target, value) = ex.kind;
let ty = cx.typeck_results().expr_ty(target);
if Some(c) == get_int_max(ty);
let ctxt = expr.span.ctxt();
if ex.span.ctxt() == ctxt;
if expr1.span.ctxt() == ctxt;
if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target);
if BinOpKind::Add == op1.node;
if let ExprKind::Lit(lit) = value.kind;
if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
if block.expr.is_none();
then {
let mut app = Applicability::MachineApplicable;
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
&& else_.hir_id == expr.hir_id
{
format!("{{{code} = {code}.saturating_add(1); }}")
} else {
format!("{code} = {code}.saturating_add(1);")
};
span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app);
[
Stmt {
kind: StmtKind::Expr(ex) | StmtKind::Semi(ex),
..
},
],
expr: None,
..
}
| Block {
stmts: [],
expr: Some(ex),
..
} = block
&& let ExprKind::AssignOp(op1, target, value) = ex.kind
&& let ty = cx.typeck_results().expr_ty(target)
&& Some(c) == get_int_max(ty)
&& let ctxt = expr.span.ctxt()
&& ex.span.ctxt() == ctxt
&& expr1.span.ctxt() == ctxt
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
&& BinOpKind::Add == op1.node
&& let ExprKind::Lit(lit) = value.kind
&& let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
&& block.expr.is_none()
{
let mut app = Applicability::MachineApplicable;
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
&& else_.hir_id == expr.hir_id
{
format!("{{{code} = {code}.saturating_add(1); }}")
} else {
format!("{code} = {code}.saturating_add(1);")
};
span_lint_and_sugg(
cx,
IMPLICIT_SATURATING_ADD,
expr.span,
"manual saturating add detected",
"use instead",
sugg,
app,
);
}
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
@ -46,83 +45,76 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if expr.span.from_expansion() {
return;
}
if_chain! {
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr)
// Check if the conditional expression is a binary operation
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
// Ensure that the binary operator is >, !=, or <
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
&& (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node)
// Check if assign operation is done
if let Some(target) = subtracts_one(cx, then);
&& let Some(target) = subtracts_one(cx, then)
// Extracting out the variable name
if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
then {
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_left, cond_right)
} else {
return;
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_right, cond_left)
} else {
return;
}
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_left, cond_right)
} else {
return;
};
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_right, cond_left)
} else {
return;
}
} else {
return;
};
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(0, _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if_chain! {
if name.ident.as_str() == "MIN";
if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
then {
print_lint_and_sugg(cx, var_name, expr)
}
}
},
ExprKind::Call(func, []) => {
if_chain! {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
if name.ident.as_str() == "min_value";
if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
then {
print_lint_and_sugg(cx, var_name, expr)
}
}
},
_ => (),
}
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
return;
}
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(0, _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if name.ident.as_str() == "MIN"
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
ExprKind::Call(func, []) => {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
&& name.ident.as_str() == "min_value"
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
_ => (),
}
}
}
@ -135,18 +127,14 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
(BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
},
ExprKind::Assign(target, value, _) => {
if_chain! {
if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
if BinOpKind::Sub == op1.node;
if SpanlessEq::new(cx).eq_expr(left1, target);
if is_integer_literal(right1, 1);
then {
Some(target)
} else {
None
}
if let ExprKind::Binary(ref op1, left1, right1) = value.kind
&& BinOpKind::Sub == op1.node
&& SpanlessEq::new(cx).eq_expr(left1, target)
&& is_integer_literal(right1, 1)
{
Some(target)
} else {
None
}
},
_ => None,

View File

@ -43,7 +43,7 @@ declare_clippy_lint! {
/// Box::new(123)
/// }
/// ```
#[clippy::version = "1.73.0"]
#[clippy::version = "1.74.0"]
pub IMPLIED_BOUNDS_IN_IMPLS,
nursery,
"specifying bounds that are implied by other bounds in `impl Trait` type"

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind};
@ -66,54 +65,53 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if !expr.span.from_expansion();
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
let ty = cx.typeck_results().expr_ty(expr);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants().iter().next();
if fields.iter().all(|f| f.is_shorthand);
then {
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!("{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
)
if !expr.span.from_expansion()
&& let ExprKind::Struct(qpath, fields, base) = expr.kind
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
&& let Some(variant) = adt_def.variants().iter().next()
&& fields.iter().all(|f| f.is_shorthand)
{
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!(
"{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
);
}
}
}

View File

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet;
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
use if_chain::if_chain;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -70,20 +69,17 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
if self.msrv.meets(msrvs::SLICE_PATTERNS);
let found_slices = find_slice_values(cx, let_pat);
if !found_slices.is_empty();
let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
if !filtered_slices.is_empty();
then {
for slice in filtered_slices.values() {
lint_slice(cx, slice);
}
if (!expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some())
&& let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
&& !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
&& self.msrv.meets(msrvs::SLICE_PATTERNS)
&& let found_slices = find_slice_values(cx, let_pat)
&& !found_slices.is_empty()
&& let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then)
&& !filtered_slices.is_empty()
{
for slice in filtered_slices.values() {
lint_slice(cx, slice);
}
}
}
@ -245,28 +241,26 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
max_suggested_slice,
} = *self;
if_chain! {
if let Some(use_info) = slice_lint_info.get_mut(&local_id)
// Check if this is even a local we're interested in
if let Some(use_info) = slice_lint_info.get_mut(&local_id);
let map = cx.tcx.hir();
&& let map = cx.tcx.hir()
// Checking for slice indexing
let parent_id = map.parent_id(expr.hir_id);
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
if let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind;
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr);
if let Ok(index_value) = index_value.try_into();
if index_value < max_suggested_slice;
&& let parent_id = map.parent_id(expr.hir_id)
&& let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id)
&& let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind
&& let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr)
&& let Ok(index_value) = index_value.try_into()
&& index_value < max_suggested_slice
// Make sure that this slice index is read only
let maybe_addrof_id = map.parent_id(parent_id);
if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
then {
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
return;
}
&& let maybe_addrof_id = map.parent_id(parent_id)
&& let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id)
&& let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind
{
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
return;
}
// The slice was used for something other than indexing

View File

@ -89,27 +89,17 @@ impl LateLintPass<'_> for InstantSubtraction {
rhs,
) = expr.kind
{
if_chain! {
if is_instant_now_call(cx, lhs);
if is_an_instant(cx, rhs);
if let Some(sugg) = Sugg::hir_opt(cx, rhs);
then {
print_manual_instant_elapsed_sugg(cx, expr, sugg)
} else {
if_chain! {
if !expr.span.from_expansion();
if self.msrv.meets(msrvs::TRY_FROM);
if is_an_instant(cx, lhs);
if is_a_duration(cx, rhs);
then {
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
}
}
}
if is_instant_now_call(cx, lhs)
&& is_an_instant(cx, rhs)
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
{
print_manual_instant_elapsed_sugg(cx, expr, sugg);
} else if !expr.span.from_expansion()
&& self.msrv.meets(msrvs::TRY_FROM)
&& is_an_instant(cx, lhs)
&& is_a_duration(cx, rhs)
{
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
}
}
}

View File

@ -7,8 +7,8 @@ use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_sta
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use rustc_span::symbol::Symbol;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does

View File

@ -0,0 +1,78 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::higher::ForLoop;
use clippy_utils::match_any_def_paths;
use clippy_utils::paths::{
HASHMAP_DRAIN, HASHMAP_ITER, HASHMAP_ITER_MUT, HASHMAP_KEYS, HASHMAP_VALUES, HASHMAP_VALUES_MUT, HASHSET_DRAIN,
HASHSET_ITER_TY,
};
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops.
///
/// ### Why is this bad?
/// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in
/// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies.
/// In addition, the unknown order of the elements may reduce readability or introduce other undesired
/// side effects.
///
/// ### Example
/// ```no_run
/// let my_map = std::collections::HashMap::<i32, String>::new();
/// for (key, value) in my_map { /* ... */ }
/// ```
/// Use instead:
/// ```no_run
/// let my_map = std::collections::HashMap::<i32, String>::new();
/// let mut keys = my_map.keys().clone().collect::<Vec<_>>();
/// keys.sort();
/// for key in keys {
/// let value = &my_map[key];
/// }
/// ```
#[clippy::version = "1.75.0"]
pub ITER_OVER_HASH_TYPE,
restriction,
"iterating over unordered hash-based types (`HashMap` and `HashSet`)"
}
declare_lint_pass!(IterOverHashType => [ITER_OVER_HASH_TYPE]);
impl LateLintPass<'_> for IterOverHashType {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
if let Some(for_loop) = ForLoop::hir(expr)
&& !for_loop.body.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(for_loop.arg).peel_refs()
&& let Some(adt) = ty.ty_adt_def()
&& let did = adt.did()
&& (match_any_def_paths(
cx,
did,
&[
&HASHMAP_KEYS,
&HASHMAP_VALUES,
&HASHMAP_VALUES_MUT,
&HASHMAP_ITER,
&HASHMAP_ITER_MUT,
&HASHMAP_DRAIN,
&HASHSET_ITER_TY,
&HASHSET_DRAIN,
],
)
.is_some()
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
|| is_type_diagnostic_item(cx, ty, sym::HashSet))
{
span_lint(
cx,
ITER_OVER_HASH_TYPE,
expr.span,
"iteration over unordered hash-based type",
);
};
}
}

View File

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -47,43 +46,40 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if !item.span.from_expansion();
if let ItemKind::Const(_, generics, _) = &item.kind;
if !item.span.from_expansion()
&& let ItemKind::Const(_, generics, _) = &item.kind
// Since static items may not have generics, skip generic const items.
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
if generics.params.is_empty() && !generics.has_where_clause_predicates;
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
if let ty::Array(element_type, cst) = ty.kind();
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);
if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes());
if self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size);
then {
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
let sugg_span = Span::new(
hi_pos - BytePos::from_usize("const".len()),
hi_pos,
item.span.ctxt(),
item.span.parent(),
);
span_lint_and_then(
cx,
LARGE_CONST_ARRAYS,
item.span,
"large array defined as const",
|diag| {
diag.span_suggestion(
sugg_span,
"make this a static item",
"static",
Applicability::MachineApplicable,
);
}
);
}
&& generics.params.is_empty() && !generics.has_where_clause_predicates
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Array(element_type, cst) = ty.kind()
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes())
&& self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size)
{
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
let sugg_span = Span::new(
hi_pos - BytePos::from_usize("const".len()),
hi_pos,
item.span.ctxt(),
item.span.parent(),
);
span_lint_and_then(
cx,
LARGE_CONST_ARRAYS,
item.span,
"large array defined as const",
|diag| {
diag.span_suggestion(
sugg_span,
"make this a static item",
"static",
Applicability::MachineApplicable,
);
},
);
}
}
}

View File

@ -50,37 +50,35 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id);
if cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id);
if let ExprKind::Lit(lit) = &expr.kind;
then {
let len = match &lit.node {
// include_bytes
LitKind::ByteStr(bstr, _) => bstr.len(),
// include_str
LitKind::Str(sym, _) => sym.as_str().len(),
_ => return,
};
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
&& let ExprKind::Lit(lit) = &expr.kind
{
let len = match &lit.node {
// include_bytes
LitKind::ByteStr(bstr, _) => bstr.len(),
// include_str
LitKind::Str(sym, _) => sym.as_str().len(),
_ => return,
};
if len as u64 <= self.max_file_size {
return;
}
span_lint_and_note(
cx,
LARGE_INCLUDE_FILE,
expr.span,
"attempted to include a large file",
None,
&format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
),
);
if len as u64 <= self.max_file_size {
return;
}
span_lint_and_note(
cx,
LARGE_INCLUDE_FILE,
expr.span,
"attempted to include a large file",
None,
&format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
),
);
}
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -15,9 +14,9 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{Span, Symbol};
use rustc_span::source_map::Spanned;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -132,37 +131,33 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if_chain! {
if item.ident.name == sym::len;
if let ImplItemKind::Fn(sig, _) = &item.kind;
if sig.decl.implicit_self.has_implicit_self();
if sig.decl.inputs.len() == 1;
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
if matches!(sig.decl.output, FnRetTy::Return(_));
if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
if imp.of_trait.is_none();
if let TyKind::Path(ty_path) = &imp.self_ty.kind;
if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
if let Some(local_id) = ty_id.as_local();
let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
if let Some(output) = parse_len_output(
cx,
cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder()
);
then {
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
Some(Node::Item(x)) => match x.kind {
ItemKind::Struct(..) => (x.ident.name, "struct"),
ItemKind::Enum(..) => (x.ident.name, "enum"),
ItemKind::Union(..) => (x.ident.name, "union"),
_ => (x.ident.name, "type"),
}
_ => return,
};
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
}
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id)
&& !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id)
&& let Some(output) =
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
Some(Node::Item(x)) => match x.kind {
ItemKind::Struct(..) => (x.ident.name, "struct"),
ItemKind::Enum(..) => (x.ident.name, "enum"),
ItemKind::Union(..) => (x.ident.name, "union"),
_ => (x.ident.name, "type"),
},
_ => return,
};
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local_id;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{BindingAnnotation, Mutability};
@ -61,76 +60,85 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let mut it = block.stmts.iter().peekable();
while let Some(stmt) = it.next() {
if_chain! {
if let Some(expr) = it.peek();
if let hir::StmtKind::Local(local) = stmt.kind;
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(if_) = expr.kind;
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
if !is_local_used(cx, *cond, canonical_id);
if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, then);
if !is_local_used(cx, value, canonical_id);
then {
let span = stmt.span.to(if_.span);
if let Some(expr) = it.peek()
&& let hir::StmtKind::Local(local) = stmt.kind
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
&& let hir::StmtKind::Expr(if_) = expr.kind
&& let hir::ExprKind::If(
hir::Expr {
kind: hir::ExprKind::DropTemps(cond),
..
},
then,
else_,
) = if_.kind
&& !is_local_used(cx, *cond, canonical_id)
&& let hir::ExprKind::Block(then, _) = then.kind
&& let Some(value) = check_assign(cx, canonical_id, then)
&& !is_local_used(cx, value, canonical_id)
{
let span = stmt.span.to(if_.span);
let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze(
cx.tcx,
cx.param_env,
);
if has_interior_mutability { return; }
let has_interior_mutability = !cx
.typeck_results()
.node_type(canonical_id)
.is_freeze(cx.tcx, cx.param_env);
if has_interior_mutability {
return;
}
let (default_multi_stmts, default) = if let Some(else_) = else_ {
if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default)
} else if let Some(default) = local.init {
(true, default)
} else {
continue;
}
let (default_multi_stmts, default) = if let Some(else_) = else_ {
if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default)
} else if let Some(default) = local.init {
(true, default)
} else {
continue;
}
} else if let Some(default) = local.init {
(false, default)
} else {
continue;
};
}
} else if let Some(default) = local.init {
(false, default)
} else {
continue;
};
let mutability = match mode {
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
_ => "",
};
let mutability = match mode {
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
_ => "",
};
// FIXME: this should not suggest `mut` if we can detect that the variable is not
// use mutably after the `if`
// FIXME: this should not suggest `mut` if we can detect that the variable is not
// use mutably after the `if`
let sug = format!(
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
name=ident.name,
cond=snippet(cx, cond.span, "_"),
then=if then.stmts.len() > 1 { " ..;" } else { "" },
else=if default_multi_stmts { " ..;" } else { "" },
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(cx,
USELESS_LET_IF_SEQ,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {
diag.span_suggestion(
span,
"it is more idiomatic to write",
sug,
Applicability::HasPlaceholders,
);
if !mutability.is_empty() {
diag.note("you might not need `mut` at all");
}
});
}
let sug = format!(
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
name=ident.name,
cond=snippet(cx, cond.span, "_"),
then=if then.stmts.len() > 1 { " ..;" } else { "" },
else=if default_multi_stmts { " ..;" } else { "" },
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(
cx,
USELESS_LET_IF_SEQ,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {
diag.span_suggestion(
span,
"it is more idiomatic to write",
sug,
Applicability::HasPlaceholders,
);
if !mutability.is_empty() {
diag.note("you might not need `mut` at all");
}
},
);
}
}
}
@ -141,20 +149,23 @@ fn check_assign<'tcx>(
decl: hir::HirId,
block: &'tcx hir::Block<'_>,
) -> Option<&'tcx hir::Expr<'tcx>> {
if_chain! {
if block.expr.is_none();
if let Some(expr) = block.stmts.iter().last();
if let hir::StmtKind::Semi(expr) = expr.kind;
if let hir::ExprKind::Assign(var, value, _) = expr.kind;
if path_to_local_id(var, decl);
then {
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
None
} else {
Some(value)
}
} else {
if block.expr.is_none()
&& let Some(expr) = block.stmts.iter().last()
&& let hir::StmtKind::Semi(expr) = expr.kind
&& let hir::ExprKind::Assign(var, value, _) = expr.kind
&& path_to_local_id(var, decl)
{
if block
.stmts
.iter()
.take(block.stmts.len() - 1)
.any(|stmt| is_local_used(cx, stmt, decl))
{
None
} else {
Some(value)
}
} else {
None
}
}

View File

@ -27,27 +27,25 @@ declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
if_chain! {
if !in_external_macro(cx.tcx.sess, local.span);
if let Some(ty) = local.ty; // Ensure that it has a type defined
if let TyKind::Infer = &ty.kind; // that type is '_'
if local.span.eq_ctxt(ty.span);
then {
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration"
)
if !in_external_macro(cx.tcx.sess, local.span)
&& let Some(ty) = local.ty // Ensure that it has a type defined
&& let TyKind::Infer = &ty.kind // that type is '_'
&& local.span.eq_ctxt(ty.span)
{
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration",
);
};
}
}

View File

@ -52,7 +52,6 @@ extern crate declare_clippy_lint;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use rustc_session::Session;
#[cfg(feature = "internal")]
pub mod deprecated_lints;
@ -165,6 +164,7 @@ mod item_name_repetitions;
mod items_after_statements;
mod items_after_test_module;
mod iter_not_returning_iterator;
mod iter_over_hash_type;
mod iter_without_into_iter;
mod large_const_arrays;
mod large_enum_variant;
@ -308,7 +308,6 @@ mod slow_vector_initialization;
mod std_instead_of_core;
mod strings;
mod strlen_on_c_strings;
mod suspicious_doc_comments;
mod suspicious_operation_groupings;
mod suspicious_trait_impl;
mod suspicious_xor_used_as_pow;
@ -492,11 +491,83 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
groups.register(store);
}
/// Register all lints and lint groups with the rustc plugin registry
/// Register all lints and lint groups with the rustc lint store
///
/// Used in `./src/driver.rs`.
#[expect(clippy::too_many_lines)]
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &'static Conf) {
pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
let Conf {
ref absolute_paths_allowed_crates,
absolute_paths_max_segments,
accept_comment_above_attributes,
accept_comment_above_statement,
allow_dbg_in_tests,
allow_expect_in_tests,
allow_mixed_uninlined_format_args,
allow_one_hash_in_raw_strings,
allow_print_in_tests,
allow_private_module_inception,
allow_unwrap_in_tests,
ref allowed_dotfiles,
ref allowed_idents_below_min_chars,
ref allowed_scripts,
ref arithmetic_side_effects_allowed_binary,
ref arithmetic_side_effects_allowed_unary,
ref arithmetic_side_effects_allowed,
array_size_threshold,
avoid_breaking_exported_api,
ref await_holding_invalid_types,
cargo_ignore_publish,
cognitive_complexity_threshold,
ref disallowed_macros,
ref disallowed_methods,
ref disallowed_names,
ref disallowed_types,
ref doc_valid_idents,
enable_raw_pointer_heuristic_for_send,
enforce_iter_loop_reborrow,
ref enforced_import_renames,
enum_variant_name_threshold,
enum_variant_size_threshold,
excessive_nesting_threshold,
future_size_threshold,
ref ignore_interior_mutability,
large_error_threshold,
literal_representation_threshold,
matches_for_let_else,
max_fn_params_bools,
max_include_file_size,
max_struct_bools,
max_suggested_slice_pattern_length,
max_trait_bounds,
min_ident_chars_threshold,
missing_docs_in_crate_items,
ref msrv,
pass_by_value_size_limit,
semicolon_inside_block_ignore_singleline,
semicolon_outside_block_ignore_multiline,
single_char_binding_names_threshold,
stack_size_threshold,
ref standard_macro_braces,
struct_field_name_threshold,
suppress_restriction_lint_in_const,
too_large_for_stack,
too_many_arguments_threshold,
too_many_lines_threshold,
trivial_copy_size_limit,
type_complexity_threshold,
unnecessary_box_size,
unreadable_literal_lint_fractions,
upper_case_acronyms_aggressive,
vec_box_size_threshold,
verbose_bit_mask_threshold,
warn_on_all_wildcard_imports,
blacklisted_names: _,
cyclomatic_complexity_threshold: _,
} = *conf;
let msrv = || msrv.clone();
register_removed_non_tool_lints(store);
register_categories(store);
@ -521,7 +592,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
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()
@ -537,9 +607,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
}
let arithmetic_side_effects_allowed = conf.arithmetic_side_effects_allowed.clone();
let arithmetic_side_effects_allowed_binary = conf.arithmetic_side_effects_allowed_binary.clone();
let arithmetic_side_effects_allowed_unary = conf.arithmetic_side_effects_allowed_unary.clone();
store.register_late_pass(move |_| {
Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(
arithmetic_side_effects_allowed
@ -557,16 +624,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author));
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
store.register_late_pass(move |_| {
Box::new(await_holding_invalid::AwaitHolding::new(
await_holding_invalid_types.clone(),
))
});
store.register_late_pass(|_| Box::new(serde_api::SerdeApi));
let vec_box_size_threshold = conf.vec_box_size_threshold;
let type_complexity_threshold = conf.type_complexity_threshold;
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
store.register_late_pass(move |_| {
Box::new(types::Types::new(
vec_box_size_threshold,
@ -599,19 +662,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
let msrv = || conf.msrv.clone();
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
let allow_expect_in_tests = conf.allow_expect_in_tests;
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const;
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
let allowed_dotfiles = conf
.allowed_dotfiles
.iter()
.cloned()
.chain(methods::DEFAULT_ALLOWED_DOTFILES.iter().copied().map(ToOwned::to_owned))
.collect::<FxHashSet<_>>();
store.register_late_pass(move |_| {
Box::new(methods::Methods::new(
avoid_breaking_exported_api,
@ -622,7 +673,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
let matches_for_let_else = conf.matches_for_let_else;
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(msrv())));
@ -639,7 +689,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv())));
store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount));
store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod));
let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
store.register_late_pass(move |_| {
Box::new(index_refutable_slice::IndexRefutableSlice::new(
max_suggested_slice_pattern_length,
@ -648,7 +697,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
let enforce_iter_loop_reborrow = conf.enforce_iter_loop_reborrow;
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv(), enforce_iter_loop_reborrow)));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
@ -662,13 +710,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(no_effect::NoEffect));
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
store.register_late_pass(move |_| {
Box::new(cognitive_complexity::CognitiveComplexity::new(
cognitive_complexity_threshold,
))
});
let too_large_for_stack = conf.too_large_for_stack;
store.register_late_pass(move |_| Box::new(escape::BoxedLocal { too_large_for_stack }));
store.register_late_pass(move |_| {
Box::new(vec::UselessVec {
@ -684,18 +730,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
store.register_late_pass(|_| Box::<regex::Regex>::default());
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
store.register_late_pass(|_| Box::new(format::UselessFormat));
store.register_late_pass(|_| Box::new(swap::Swap));
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
let disallowed_names = conf.disallowed_names.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone())));
let too_many_arguments_threshold = conf.too_many_arguments_threshold;
let too_many_lines_threshold = conf.too_many_lines_threshold;
let large_error_threshold = conf.large_error_threshold;
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names)));
store.register_late_pass(move |_| {
Box::new(functions::Functions::new(
too_many_arguments_threshold,
@ -704,9 +745,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
avoid_breaking_exported_api,
))
});
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
let missing_docs_in_crate_items = conf.missing_docs_in_crate_items;
store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents)));
store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply));
store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq));
store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
@ -716,17 +755,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk));
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
let enum_variant_size_threshold = conf.enum_variant_size_threshold;
store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite));
store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
conf.trivial_copy_size_limit,
conf.pass_by_value_size_limit,
conf.avoid_breaking_exported_api,
&sess.target,
);
store.register_late_pass(move |_| Box::new(pass_by_ref_or_value));
store.register_late_pass(move |tcx| {
Box::new(pass_by_ref_or_value::PassByRefOrValue::new(
trivial_copy_size_limit,
pass_by_value_size_limit,
avoid_breaking_exported_api,
tcx.sess.target.pointer_width,
))
});
store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef));
store.register_late_pass(|_| Box::new(infinite_iter::InfiniteIter));
store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody));
@ -746,7 +785,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
suppress_restriction_lint_in_const,
))
});
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
store.register_late_pass(move |_| Box::new(non_copy_const::NonCopyConst::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
@ -755,10 +793,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants));
store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString));
let max_trait_bounds = conf.max_trait_bounds;
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds, msrv())));
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
@ -779,21 +815,16 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(redundant_else::RedundantElse));
store.register_late_pass(|_| Box::new(create_dir::CreateDir));
store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType));
let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
store.register_early_pass(move || {
Box::new(literal_representation::LiteralDigitGrouping::new(
literal_representation_lint_fraction_readability,
unreadable_literal_lint_fractions,
))
});
let literal_representation_threshold = conf.literal_representation_threshold;
store.register_early_pass(move || {
Box::new(literal_representation::DecimalLiteralRepresentation::new(
literal_representation_threshold,
))
});
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
let struct_field_name_threshold = conf.struct_field_name_threshold;
let allow_private_module_inception = conf.allow_private_module_inception;
store.register_late_pass(move |_| {
Box::new(item_name_repetitions::ItemNameRepetitions::new(
enum_variant_name_threshold,
@ -803,7 +834,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
store.register_late_pass(move |_| {
Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
avoid_breaking_exported_api,
@ -815,15 +845,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
store.register_late_pass(|_| Box::new(exit::Exit));
store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome));
let array_size_threshold = u128::from(conf.array_size_threshold);
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold.into())));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold.into())));
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
store.register_late_pass(|_| Box::new(as_conversions::AsConversions));
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
let max_fn_params_bools = conf.max_fn_params_bools;
let max_struct_bools = conf.max_struct_bools;
store.register_late_pass(move |_| {
Box::new(excessive_bools::ExcessiveBools::new(
max_struct_bools,
@ -831,36 +858,30 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
let future_size_threshold = conf.future_size_threshold;
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn));
store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn));
let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
store.register_early_pass(move || {
Box::new(non_expressive_names::NonExpressiveNames {
single_char_binding_names_threshold,
})
});
let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::<FxHashSet<_>>();
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(&macro_matcher)));
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(standard_macro_braces)));
store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default());
store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch));
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
let disallowed_macros = conf.disallowed_macros.clone();
store.register_late_pass(move |_| Box::new(disallowed_macros::DisallowedMacros::new(disallowed_macros.clone())));
let disallowed_methods = conf.disallowed_methods.clone();
store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
@ -875,36 +896,30 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|_| Box::<unused_async::UnusedAsync>::default());
let disallowed_types = conf.disallowed_types.clone();
store.register_late_pass(move |_| Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone())));
let import_renames = conf.enforced_import_renames.clone();
store.register_late_pass(move |_| {
Box::new(missing_enforced_import_rename::ImportRename::new(
import_renames.clone(),
enforced_import_renames.clone(),
))
});
let scripts = conf.allowed_scripts.clone();
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(allowed_scripts)));
store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings));
store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors));
store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator));
store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert));
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
store.register_late_pass(move |_| {
Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
enable_raw_pointer_heuristic_for_send,
))
});
let accept_comment_above_statement = conf.accept_comment_above_statement;
let accept_comment_above_attributes = conf.accept_comment_above_attributes;
store.register_late_pass(move |_| {
Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(
accept_comment_above_statement,
accept_comment_above_attributes,
))
});
let allow_mixed_uninlined = conf.allow_mixed_uninlined_format_args;
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined)));
store
.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args)));
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
@ -914,11 +929,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(msrv())));
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
let allow_dbg_in_tests = conf.allow_dbg_in_tests;
store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
let allow_print_in_tests = conf.allow_print_in_tests;
store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests)));
let cargo_ignore_publish = conf.cargo_ignore_publish;
store.register_late_pass(move |_| {
Box::new(cargo::Cargo {
ignore_publish: cargo_ignore_publish,
@ -929,7 +941,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
store.register_early_pass(|| Box::new(pub_use::PubUse));
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
let max_include_file_size = conf.max_include_file_size;
store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace));
store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
@ -942,7 +953,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
@ -959,8 +969,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr));
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv())));
let semicolon_inside_block_ignore_singleline = conf.semicolon_inside_block_ignore_singleline;
let semicolon_outside_block_ignore_multiline = conf.semicolon_outside_block_ignore_multiline;
store.register_late_pass(move |_| {
Box::new(semicolon_block::SemicolonBlock::new(
semicolon_inside_block_ignore_singleline,
@ -983,7 +991,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
let unnecessary_box_size = conf.unnecessary_box_size;
store.register_late_pass(move |_| {
Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
avoid_breaking_exported_api,
@ -993,8 +1000,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
let excessive_nesting_threshold = conf.excessive_nesting_threshold;
store.register_early_pass(move || {
Box::new(excessive_nesting::ExcessiveNesting {
excessive_nesting_threshold,
@ -1010,15 +1015,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf));
let allowed_idents_below_min_chars = conf.allowed_idents_below_min_chars.clone();
let min_ident_chars_threshold = conf.min_ident_chars_threshold;
store.register_late_pass(move |_| {
Box::new(min_ident_chars::MinIdentChars {
allowed_idents_below_min_chars: allowed_idents_below_min_chars.clone(),
min_ident_chars_threshold,
})
});
let stack_size_threshold = conf.stack_size_threshold;
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
store.register_late_pass(move |_| {
@ -1033,10 +1035,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
})
});
let needless_raw_string_hashes_allow_one = conf.allow_one_hash_in_raw_strings;
store.register_early_pass(move || {
Box::new(raw_strings::RawStrings {
needless_raw_string_hashes_allow_one,
allow_one_hash_in_raw_strings,
})
});
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
@ -1045,8 +1046,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes));
store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError));
let absolute_paths_max_segments = conf.absolute_paths_max_segments;
let absolute_paths_allowed_crates = conf.absolute_paths_allowed_crates.clone();
store.register_late_pass(move |_| {
Box::new(absolute_paths::AbsolutePaths {
absolute_paths_max_segments,
@ -1066,6 +1065,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -20,8 +20,8 @@ use rustc_middle::hir::nested_filter as middle_nested_filter;
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -310,20 +310,17 @@ fn elision_suggestions(
// elision doesn't work for explicit self types, see rust-lang/rust#69064
fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
if_chain! {
if let Some(ident) = ident;
if ident.name == kw::SelfLower;
if !func.implicit_self.has_implicit_self();
if let Some(ident) = ident
&& ident.name == kw::SelfLower
&& !func.implicit_self.has_implicit_self()
&& let Some(self_ty) = func.inputs.first()
{
let mut visitor = RefVisitor::new(cx);
visitor.visit_ty(self_ty);
if let Some(self_ty) = func.inputs.first();
then {
let mut visitor = RefVisitor::new(cx);
visitor.visit_ty(self_ty);
!visitor.all_lts().is_empty()
} else {
false
}
!visitor.all_lts().is_empty()
} else {
false
}
}

View File

@ -4,7 +4,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token;
use rustc_errors::Applicability;
@ -255,56 +254,48 @@ impl LiteralDigitGrouping {
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
if_chain! {
if let Some(src) = snippet_opt(cx, span);
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
then {
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
return;
}
if let Some(src) = snippet_opt(cx, span)
&& let Ok(lit_kind) = LitKind::from_token_lit(lit)
&& let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
{
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
return;
}
if Self::is_literal_uuid_formatted(&num_lit) {
return;
}
if Self::is_literal_uuid_formatted(&num_lit) {
return;
}
let result = (|| {
let result = (|| {
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
if let Some(fraction) = num_lit.fraction {
let fractional_group_size =
Self::get_group_size(fraction.rsplit('_'), num_lit.radix, self.lint_fraction_readability)?;
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
if let Some(fraction) = num_lit.fraction {
let fractional_group_size = Self::get_group_size(
fraction.rsplit('_'),
num_lit.radix,
self.lint_fraction_readability)?;
let consistent = Self::parts_consistent(integral_group_size,
fractional_group_size,
num_lit.integer.len(),
fraction.len());
if !consistent {
return Err(WarningType::InconsistentDigitGrouping);
};
}
Ok(())
})();
if let Err(warning_type) = result {
let should_warn = match warning_type {
| WarningType::UnreadableLiteral
| WarningType::InconsistentDigitGrouping
| WarningType::UnusualByteGroupings
| WarningType::LargeDigitGroups => {
!span.from_expansion()
}
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
true
}
let consistent = Self::parts_consistent(
integral_group_size,
fractional_group_size,
num_lit.integer.len(),
fraction.len(),
);
if !consistent {
return Err(WarningType::InconsistentDigitGrouping);
};
if should_warn {
warning_type.display(num_lit.format(), cx, span);
}
}
Ok(())
})();
if let Err(warning_type) = result {
let should_warn = match warning_type {
WarningType::UnreadableLiteral
| WarningType::InconsistentDigitGrouping
| WarningType::UnusualByteGroupings
| WarningType::LargeDigitGroups => !span.from_expansion(),
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
};
if should_warn {
warning_type.display(num_lit.format(), cx, span);
}
}
}
@ -478,20 +469,18 @@ impl DecimalLiteralRepresentation {
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
// Lint integral literals.
if_chain! {
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let LitKind::Int(val, _) = lit_kind;
if let Some(src) = snippet_opt(cx, span);
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
if num_lit.radix == Radix::Decimal;
if val >= u128::from(self.threshold);
then {
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span);
});
}
if let Ok(lit_kind) = LitKind::from_token_lit(lit)
&& let LitKind::Int(val, _) = lit_kind
&& let Some(src) = snippet_opt(cx, span)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
&& num_lit.radix == Radix::Decimal
&& val >= u128::from(self.threshold)
{
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span);
});
}
}

View File

@ -2,7 +2,6 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_enclosing_block, is_integer_const};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr};
use rustc_hir::{Expr, Pat};
@ -30,59 +29,57 @@ pub(super) fn check<'tcx>(
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
walk_block(&mut initialize_visitor, block);
if_chain! {
if let Some((name, ty, initializer)) = initialize_visitor.get_result();
if is_integer_const(cx, initializer, 0);
then {
let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi());
if let Some((name, ty, initializer)) = initialize_visitor.get_result()
&& is_integer_const(cx, initializer, 0)
{
let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi());
let int_name = match ty.map(Ty::kind) {
// usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => {
span_lint_and_sugg(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
"consider using",
format!(
"for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
return;
}
Some(ty::Int(int_ty)) => int_ty.name_str(),
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
_ => return,
};
let int_name = match ty.map(Ty::kind) {
// usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => {
span_lint_and_sugg(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
"consider using",
format!(
"for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
return;
},
Some(ty::Int(int_ty)) => int_ty.name_str(),
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
_ => return,
};
span_lint_and_then(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
|diag| {
diag.span_suggestion(
span,
"consider using",
format!(
"for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
span_lint_and_then(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
|diag| {
diag.span_suggestion(
span,
"consider using",
format!(
"for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
diag.note(format!(
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
));
},
);
}
diag.note(format!(
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
));
},
);
}
}
}

View File

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::lang_items::LangItem;
@ -23,77 +22,79 @@ pub(super) fn check<'tcx>(
let inner_expr = peel_blocks_with_stmt(body);
// Check for the specific case that the result is returned and optimize suggestion for that (more
// cases can be added later)
if_chain! {
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
if let Some(binding_id) = get_binding(pat);
if let ExprKind::Block(block, _) = then.kind;
if let [stmt] = block.stmts;
if let StmtKind::Semi(semi) = stmt.kind;
if let ExprKind::Ret(Some(ret_value)) = semi.kind;
if let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind;
if is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome);
if path_res(cx, inner_ret) == Res::Local(binding_id);
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
then {
let mut applicability = Applicability::MachineApplicable;
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// Checks if `pat` is a single reference to a binding (`&x`)
let is_ref_to_binding =
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet.push_str(
&format!(
".map(|{}| {})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
)[..],
);
}
let ty = cx.typeck_results().expr_ty(inner_ret);
if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
snippet.push_str(
&format!(
".find(|{}{}| {})",
"&".repeat(1 + usize::from(is_ref_to_binding)),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
if is_ref_to_binding {
snippet.push_str(".copied()");
}
} else {
applicability = Applicability::MaybeIncorrect;
snippet.push_str(
&format!(
".find(|{}| {})",
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
}
// Extends to `last_stmt` to include semicolon in case of `return None;`
let lint_span = span.to(last_stmt.span).to(last_ret.span);
span_lint_and_then(
cx,
MANUAL_FIND,
lint_span,
"manual implementation of `Iterator::find`",
|diag| {
if applicability == Applicability::MaybeIncorrect {
diag.note("you may need to dereference some variables");
}
diag.span_suggestion(
lint_span,
"replace with an iterator",
snippet,
applicability,
);
},
if let Some(higher::If {
cond,
then,
r#else: None,
}) = higher::If::hir(inner_expr)
&& let Some(binding_id) = get_binding(pat)
&& let ExprKind::Block(block, _) = then.kind
&& let [stmt] = block.stmts
&& let StmtKind::Semi(semi) = stmt.kind
&& let ExprKind::Ret(Some(ret_value)) = semi.kind
&& let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind
&& is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome)
&& path_res(cx, inner_ret) == Res::Local(binding_id)
&& let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
{
let mut applicability = Applicability::MachineApplicable;
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// Checks if `pat` is a single reference to a binding (`&x`)
let is_ref_to_binding =
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet.push_str(
&format!(
".map(|{}| {})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
)[..],
);
}
let ty = cx.typeck_results().expr_ty(inner_ret);
if cx
.tcx
.lang_items()
.copy_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
snippet.push_str(
&format!(
".find(|{}{}| {})",
"&".repeat(1 + usize::from(is_ref_to_binding)),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
if is_ref_to_binding {
snippet.push_str(".copied()");
}
} else {
applicability = Applicability::MaybeIncorrect;
snippet.push_str(
&format!(
".find(|{}| {})",
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
}
// Extends to `last_stmt` to include semicolon in case of `return None;`
let lint_span = span.to(last_stmt.span).to(last_ret.span);
span_lint_and_then(
cx,
MANUAL_FIND,
lint_span,
"manual implementation of `Iterator::find`",
|diag| {
if applicability == Applicability::MaybeIncorrect {
diag.note("you may need to dereference some variables");
}
diag.span_suggestion(lint_span, "replace with an iterator", snippet, applicability);
},
);
}
}
@ -124,34 +125,30 @@ fn last_stmt_and_ret<'tcx>(
if let Some(ret) = block.expr {
return Some((last_stmt, ret));
}
if_chain! {
if let [.., snd_last, _] = block.stmts;
if let StmtKind::Semi(last_expr) = last_stmt.kind;
if let ExprKind::Ret(Some(ret)) = last_expr.kind;
then {
return Some((snd_last, ret));
}
if let [.., snd_last, _] = block.stmts
&& let StmtKind::Semi(last_expr) = last_stmt.kind
&& let ExprKind::Ret(Some(ret)) = last_expr.kind
{
return Some((snd_last, ret));
}
}
None
}
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
if_chain! {
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next()
// This should be the loop
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
// This should be the function body
if let Some((_, Node::Block(block))) = parent_iter.next();
if let Some((last_stmt, last_ret)) = extract(block);
if last_stmt.hir_id == node_hir;
if is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone);
if let Some((_, Node::Expr(_block))) = parent_iter.next();
&& let Some((_, Node::Block(block))) = parent_iter.next()
&& let Some((last_stmt, last_ret)) = extract(block)
&& last_stmt.hir_id == node_hir
&& is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone)
&& let Some((_, Node::Expr(_block))) = parent_iter.next()
// This includes the function header
if let Some((_, func)) = parent_iter.next();
if func.fn_kind().is_some();
then {
Some((block.stmts.last().unwrap(), last_ret))
} else {
None
}
&& let Some((_, func)) = parent_iter.next()
&& func.fn_kind().is_some()
{
Some((block.stmts.last().unwrap(), last_ret))
} else {
None
}
}

View File

@ -3,7 +3,6 @@ use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, Pat, PatKind};
@ -21,66 +20,51 @@ pub(super) fn check<'tcx>(
span: Span,
) {
let inner_expr = peel_blocks_with_stmt(body);
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr)
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
if path_to_local_id(let_expr, pat_hir_id);
&& let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind
&& path_to_local_id(let_expr, pat_hir_id)
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id);
if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id);
let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
if some_ctor || ok_ctor;
&& let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
&& let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id)
&& let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id)
&& (some_ctor || ok_ctor)
// Ensure expr in `if let` is not used afterwards
if !is_local_used(cx, if_then, pat_hir_id);
then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message
let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
&& !is_local_used(cx, if_then, pat_hir_id)
{
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message
let msg =
format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
// Prepare the help message
let mut applicability = Applicability::MaybeIncorrect;
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
ty::Ref(_, inner, _) => match inner.kind() {
ty::Ref(..) => ".copied()",
_ => ""
}
_ => ""
};
// Prepare the help message
let mut applicability = Applicability::MaybeIncorrect;
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
ty::Ref(_, inner, _) => match inner.kind() {
ty::Ref(..) => ".copied()",
_ => "",
},
_ => "",
};
let sugg = format!("{arg_snippet}{copied}.flatten()");
let sugg = format!("{arg_snippet}{copied}.flatten()");
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
// to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..."
} else {
"...and remove the `if let` statement in the for loop"
};
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that
// case, it will be shown in the extra `help` message at the end, which is why the first
// `help_msg` needs to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..."
} else {
"...and remove the `if let` statement in the for loop"
};
span_lint_and_then(
cx,
MANUAL_FLATTEN,
span,
&msg,
|diag| {
diag.span_suggestion(
arg.span,
"try",
sugg,
applicability,
);
diag.span_help(
inner_expr.span,
help_msg,
);
}
);
}
span_lint_and_then(cx, MANUAL_FLATTEN, span, &msg, |diag| {
diag.span_suggestion(arg.span, "try", sugg, applicability);
diag.span_help(inner_expr.span, help_msg);
});
}
}

View File

@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::intravisit::walk_block;
@ -59,22 +58,31 @@ pub(super) fn check<'tcx>(
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if_chain! {
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind;
if let ExprKind::Index(base_right, idx_right, _) = rhs.kind;
if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left));
if get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some();
if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
&& let ExprKind::Index(base_right, idx_right, _) = rhs.kind
&& let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left))
&& get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some()
&& let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts)
&& let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts)
// Source and destination must be different
if path_to_local(base_left) != path_to_local(base_right);
then {
Some((ty, IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left },
IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }))
} else {
None
}
&& path_to_local(base_left) != path_to_local(base_right)
{
Some((
ty,
IndexExpr {
base: base_left,
idx: start_left,
idx_offset: offset_left,
},
IndexExpr {
base: base_right,
idx: start_right,
idx_offset: offset_right,
},
))
} else {
None
}
})
})
@ -118,23 +126,19 @@ fn build_manual_memcpy_suggestion<'tcx>(
}
let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
if_chain! {
if let ExprKind::MethodCall(method, recv, [], _) = end.kind;
if method.ident.name == sym::len;
if path_to_local(recv) == path_to_local(base);
then {
if sugg.to_string() == end_str {
sugg::EMPTY.into()
} else {
sugg
}
if let ExprKind::MethodCall(method, recv, [], _) = end.kind
&& method.ident.name == sym::len
&& path_to_local(recv) == path_to_local(base)
{
if sugg.to_string() == end_str {
sugg::EMPTY.into()
} else {
match limits {
ast::RangeLimits::Closed => {
sugg + &sugg::ONE.into()
},
ast::RangeLimits::HalfOpen => sugg,
}
sugg
}
} else {
match limits {
ast::RangeLimits::Closed => sugg + &sugg::ONE.into(),
ast::RangeLimits::HalfOpen => sugg,
}
}
};
@ -174,7 +178,9 @@ fn build_manual_memcpy_suggestion<'tcx>(
let dst_base_str = snippet(cx, dst.base.span, "???");
let src_base_str = snippet(cx, src.base.span, "???");
let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY {
let dst = if (dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY)
|| is_array_length_equal_to_range(cx, start, end, dst.base)
{
dst_base_str
} else {
format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into()
@ -186,11 +192,13 @@ fn build_manual_memcpy_suggestion<'tcx>(
"clone_from_slice"
};
format!(
"{dst}.{method_str}(&{src_base_str}[{}..{}]);",
src_offset.maybe_par(),
src_limit.maybe_par()
)
let src = if is_array_length_equal_to_range(cx, start, end, src.base) {
src_base_str
} else {
format!("{src_base_str}[{}..{}]", src_offset.maybe_par(), src_limit.maybe_par()).into()
};
format!("{dst}.{method_str}(&{src});")
}
/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
@ -331,10 +339,12 @@ fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Opti
}
fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if_chain! {
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind;
if method.ident.name == sym::clone;
then { arg } else { expr }
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind
&& method.ident.name == sym::clone
{
arg
} else {
expr
}
}
@ -446,3 +456,34 @@ fn get_loop_counters<'a, 'tcx>(
.into()
})
}
fn is_array_length_equal_to_range(cx: &LateContext<'_>, start: &Expr<'_>, end: &Expr<'_>, arr: &Expr<'_>) -> bool {
fn extract_lit_value(expr: &Expr<'_>) -> Option<u128> {
if let ExprKind::Lit(lit) = expr.kind
&& let ast::LitKind::Int(value, _) = lit.node
{
Some(value)
} else {
None
}
}
let arr_ty = cx.typeck_results().expr_ty(arr).peel_refs();
if let ty::Array(_, s) = arr_ty.kind() {
let size: u128 = if let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) {
size.into()
} else {
return false;
};
let range = match (extract_lit_value(start), extract_lit_value(end)) {
(Some(start_value), Some(end_value)) => end_value - start_value,
_ => return false,
};
size == range
} else {
false
}
}

View File

@ -31,26 +31,30 @@ fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
if let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind;
if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
if let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind();
if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
then {
span_lint_and_sugg(
cx,
MISSING_SPIN_LOOP,
body.span,
"busy-waiting loop should at least have a spin loop hint",
"try",
(if is_no_std_crate(cx) {
"{ core::hint::spin_loop() }"
} else {
"{ std::hint::spin_loop() }"
}).into(),
Applicability::MachineApplicable
);
}
if let ExprKind::Block(
Block {
stmts: [], expr: None, ..
},
_,
) = body.kind
&& let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind
&& [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name)
&& let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind()
&& cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did())
{
span_lint_and_sugg(
cx,
MISSING_SPIN_LOOP,
body.span,
"busy-waiting loop should at least have a spin loop hint",
"try",
(if is_no_std_crate(cx) {
"{ core::hint::spin_loop() }"
} else {
"{ std::hint::spin_loop() }"
})
.into(),
Applicability::MachineApplicable,
);
}
}

View File

@ -1,7 +1,6 @@
use super::MUT_RANGE_BOUND;
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::{get_enclosing_block, higher, path_to_local};
use if_chain::if_chain;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
@ -12,19 +11,17 @@ use rustc_middle::ty;
use rustc_span::Span;
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
if_chain! {
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg);
let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
if mut_id_start.is_some() || mut_id_end.is_some();
then {
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high);
}
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg)
&& let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end))
&& (mut_id_start.is_some() || mut_id_end.is_some())
{
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high);
}
}
@ -42,13 +39,11 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
}
fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
if_chain! {
if let Some(hir_id) = path_to_local(bound);
if let Node::Pat(pat) = cx.tcx.hir().get(hir_id);
if let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind;
then {
return Some(hir_id);
}
if let Some(hir_id) = path_to_local(bound)
&& let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
&& let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind
{
return Some(hir_id);
}
None
}

View File

@ -4,7 +4,6 @@ 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, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{DefKind, Res};
@ -187,15 +186,13 @@ pub(super) fn check<'tcx>(
}
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
if_chain! {
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind;
if method.ident.name == sym::len;
if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind;
if path.segments.len() == 1;
if path.segments[0].ident.name == var;
then {
return true;
}
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind
&& method.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind
&& path.segments.len() == 1
&& path.segments[0].ident.name == var
{
return true;
}
false
@ -207,17 +204,15 @@ fn is_end_eq_array_len<'tcx>(
limits: ast::RangeLimits,
indexed_ty: Ty<'tcx>,
) -> bool {
if_chain! {
if let ExprKind::Lit(lit) = end.kind;
if let ast::LitKind::Int(end_int, _) = lit.node;
if let ty::Array(_, arr_len_const) = indexed_ty.kind();
if let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env);
then {
return match limits {
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
};
}
if let ExprKind::Lit(lit) = end.kind
&& let ast::LitKind::Int(end_int, _) = lit.node
&& let ty::Array(_, arr_len_const) = indexed_ty.kind()
&& let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env)
{
return match limits {
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
};
}
false
@ -248,51 +243,49 @@ struct VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Path(ref seqpath) = seqexpr.kind
// the indexed container is referenced by a name
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, seqvar) = *seqpath;
if seqvar.segments.len() == 1;
if is_local_used(self.cx, idx, self.var);
then {
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
}
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let extent = self
.cx
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
.unwrap();
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
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, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
},
_ => (),
}
&& let QPath::Resolved(None, seqvar) = *seqpath
&& seqvar.segments.len() == 1
&& is_local_used(self.cx, idx, self.var)
{
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
}
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let extent = self
.cx
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
.unwrap();
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
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, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
},
_ => (),
}
}
true
@ -301,42 +294,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind
// a range index op
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
if let Some(trait_id) = self
&& 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;
}
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id))
&& ((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)))
&& !self.check(args_1, args_0, expr)
{
return;
}
if_chain! {
if let ExprKind::Index(seqexpr, idx, _) = expr.kind
// an index op
if let ExprKind::Index(seqexpr, idx, _) = expr.kind;
if !self.check(idx, seqexpr, expr);
then {
return;
}
&& !self.check(idx, seqexpr, expr)
{
return;
}
if_chain! {
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
// directly using a variable
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
if let Res::Local(local_id) = path.res;
then {
if local_id == self.var {
self.nonindex = true;
} else {
// not the correct variable, but still a variable
self.referenced.insert(path.segments[0].ident.name);
}
&& let Res::Local(local_id) = path.res
{
if local_id == self.var {
self.nonindex = true;
} else {
// not the correct variable, but still a variable
self.referenced.insert(path.segments[0].ident.name);
}
}

View File

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -44,54 +43,50 @@ pub(super) fn check<'tcx>(
// Determine whether it is safe to lint the body
let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
walk_expr(&mut same_item_push_visitor, body);
if_chain! {
if same_item_push_visitor.should_lint();
if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push;
let vec_ty = cx.typeck_results().expr_ty(vec);
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
if cx
if same_item_push_visitor.should_lint()
&& let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push
&& let vec_ty = cx.typeck_results().expr_ty(vec)
&& let ty = vec_ty.walk().nth(1).unwrap().expect_ty()
&& cx
.tcx
.lang_items()
.clone_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]));
then {
// Make sure that the push does not involve possibly mutating values
match pushed_item.kind {
ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id);
if_chain! {
if let Node::Pat(pat) = node;
if let PatKind::Binding(bind_ann, ..) = pat.kind;
if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut));
let parent_node = cx.tcx.hir().parent_id(hir_id);
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
if let Some(init) = parent_let_expr.init;
then {
match init.kind {
// immutable bindings that are initialized with literal
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
// immutable bindings that are initialized with constant
ExprKind::Path(ref path) => {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
emit_lint(cx, vec, pushed_item, ctxt);
}
}
_ => {},
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
// Make sure that the push does not involve possibly mutating values
match pushed_item.kind {
ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id);
if let Node::Pat(pat) = node
&& let PatKind::Binding(bind_ann, ..) = pat.kind
&& !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut))
&& let parent_node = cx.tcx.hir().parent_id(hir_id)
&& let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node)
&& let Some(init) = parent_let_expr.init
{
match init.kind {
// immutable bindings that are initialized with literal
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
// immutable bindings that are initialized with constant
ExprKind::Path(ref path) => {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
emit_lint(cx, vec, pushed_item, ctxt);
}
}
},
_ => {},
}
},
// constant
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
},
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
}
},
// constant
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
},
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
}
}
@ -118,16 +113,14 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
}
fn should_lint(&self) -> bool {
if_chain! {
if !self.non_deterministic_expr;
if !self.multiple_pushes;
if let Some((vec, _, _)) = self.vec_push;
if let Some(hir_id) = path_to_local(vec);
then {
!self.used_locals.contains(&hir_id)
} else {
false
}
if !self.non_deterministic_expr
&& !self.multiple_pushes
&& let Some((vec, _, _)) = self.vec_push
&& let Some(hir_id) = path_to_local(vec)
{
!self.used_locals.contains(&hir_id)
} else {
false
}
}
}
@ -180,18 +173,16 @@ fn get_vec_push<'tcx>(
cx: &LateContext<'tcx>,
stmt: &'tcx Stmt<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
if_chain! {
if let StmtKind::Semi(semi_stmt) = &stmt.kind
// Extract method being called
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
&& let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind
// Figure out the parameters for the method call
if let Some(pushed_item) = args.first();
&& let Some(pushed_item) = args.first()
// Check that the method being called is push() on a Vec
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
if path.ident.name.as_str() == "push";
then {
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()))
}
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec)
&& path.ident.name.as_str() == "push"
{
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()));
}
None
}

View File

@ -2,7 +2,6 @@ use super::SINGLE_ELEMENT_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, snippet_with_applicability};
use clippy_utils::visitors::contains_break_or_continue;
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
use rustc_ast::Mutability;
use rustc_errors::Applicability;
@ -66,36 +65,36 @@ pub(super) fn check<'tcx>(
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
_ => return,
};
if_chain! {
if let ExprKind::Block(block, _) = body.kind;
if !block.stmts.is_empty();
if !contains_break_or_continue(body);
then {
let mut applicability = Applicability::MachineApplicable;
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
block_str.remove(0);
block_str.pop();
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
if let ExprKind::Block(block, _) = body.kind
&& !block.stmts.is_empty()
&& !contains_break_or_continue(body)
{
let mut applicability = Applicability::MachineApplicable;
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
block_str.remove(0);
block_str.pop();
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty() && (
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty()
&& (
// Precedence of internal expression is less than or equal to precedence of `&expr`.
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
) {
arg_snip = format!("({arg_snip})").into();
}
span_lint_and_sugg(
cx,
SINGLE_ELEMENT_LOOP,
expr.span,
"for loop over a single element",
"try",
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
applicability,
)
{
arg_snip = format!("({arg_snip})").into();
}
span_lint_and_sugg(
cx,
SINGLE_ELEMENT_LOOP,
expr.span,
"for loop over a single element",
"try",
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
applicability,
);
}
}

View File

@ -9,7 +9,7 @@ use rustc_middle::ty;
/// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
let PatKind::Tuple(tuple, _) = pat.kind else {
let PatKind::Tuple([index, elem], _) = pat.kind else {
return;
};
@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
let ty = cx.typeck_results().expr_ty(arg);
if !pat_is_wild(cx, &tuple[0].kind, body) {
if !pat_is_wild(cx, &index.kind, body) {
return;
}
@ -53,7 +53,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
diag,
"remove the `.enumerate()` call",
vec![
(pat.span, snippet(cx, tuple[1].span, "..").into_owned()),
(pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()),
],
);

View File

@ -1,6 +1,5 @@
use clippy_utils::ty::{has_iter_method, implements_trait};
use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
@ -145,20 +144,18 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
fn visit_local(&mut self, l: &'tcx Local<'_>) {
// Look for declarations of the variable
if_chain! {
if l.pat.hir_id == self.var_id;
if let PatKind::Binding(.., ident, _) = l.pat.kind;
then {
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
if l.pat.hir_id == self.var_id
&& let PatKind::Binding(.., ident, _) = l.pat.kind
{
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized {
initializer: init,
ty,
name: ident.name,
}
})
}
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized {
initializer: init,
ty,
name: ident.name,
}
});
}
walk_local(self, l);

View File

@ -2,7 +2,6 @@ use super::WHILE_IMMUTABLE_CONDITION;
use clippy_utils::consts::constant;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::usage::mutated_variables;
use if_chain::if_chain;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -95,20 +94,18 @@ struct VarCollectorVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Path(ref qpath) = ex.kind;
if let QPath::Resolved(None, _) = *qpath;
then {
match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static(_), def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},
_ => {},
}
if let ExprKind::Path(ref qpath) = ex.kind
&& let QPath::Resolved(None, _) = *qpath
{
match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static(_), def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},
_ => {},
}
}
}

View File

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::is_res_used;
use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable, is_res_lang_ctor, is_trait_method};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -15,59 +14,53 @@ use rustc_span::symbol::sym;
use rustc_span::Symbol;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr)
// check for `Some(..)` pattern
if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind;
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
&& let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
// check for call to `Iterator::next`
if let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind;
if method_name.ident.name == sym::next;
if is_trait_method(cx, let_expr, sym::Iterator);
if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr);
&& let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind
&& method_name.ident.name == sym::next
&& is_trait_method(cx, let_expr, sym::Iterator)
&& let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr)
// get the loop containing the match expression
if !uses_iter(cx, &iter_expr_struct, if_then);
then {
(let_expr, iter_expr_struct, iter_expr, some_pat, expr)
} else {
return;
}
};
let mut applicability = Applicability::MachineApplicable;
let loop_var = if let Some(some_pat) = some_pat.first() {
if is_refutable(cx, some_pat) {
// Refutable patterns don't work with for loops.
return;
}
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
} else {
"_".into()
};
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
&& !uses_iter(cx, &iter_expr_struct, if_then)
{
".by_ref()"
} else {
""
};
let mut applicability = Applicability::MachineApplicable;
let loop_var = if let Some(some_pat) = some_pat.first() {
if is_refutable(cx, some_pat) {
// Refutable patterns don't work with for loops.
return;
}
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
} else {
"_".into()
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(scrutinee_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("for {loop_var} in {iterator}{by_ref}"),
applicability,
);
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
{
".by_ref()"
} else {
""
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("for {loop_var} in {iterator}{by_ref}"),
applicability,
);
}
}
#[derive(Debug)]

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use hir::def::{DefKind, Res};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
@ -89,30 +88,26 @@ impl MacroUseImports {
impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! {
if cx.sess().opts.edition >= Edition::Edition2018;
if let hir::ItemKind::Use(path, _kind) = &item.kind;
let hir_id = item.hir_id();
let attrs = cx.tcx.hir().attrs(hir_id);
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
if let Some(id) = path.res.iter().find_map(|res| match res {
if cx.sess().opts.edition >= Edition::Edition2018
&& let hir::ItemKind::Use(path, _kind) = &item.kind
&& let hir_id = item.hir_id()
&& let attrs = cx.tcx.hir().attrs(hir_id)
&& let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use))
&& let Some(id) = path.res.iter().find_map(|res| match res {
Res::Def(DefKind::Mod, id) => Some(id),
_ => None,
});
if !id.is_local();
then {
for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span, hir_id));
}
}
} else {
if item.span.from_expansion() {
self.push_unique_macro_pat_ty(cx, item.span);
})
&& !id.is_local()
{
for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span, hir_id));
}
}
} else if item.span.from_expansion() {
self.push_unique_macro_pat_ty(cx, item.span);
}
}
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet;
use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -43,21 +42,19 @@ impl LateLintPass<'_> for MainRecursion {
return;
}
if_chain! {
if let ExprKind::Call(func, _) = &expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind;
if let Some(def_id) = path.res.opt_def_id();
if is_entrypoint_fn(cx, def_id);
then {
span_lint_and_help(
cx,
MAIN_RECURSION,
func.span,
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
None,
"consider using another function for this recursion"
)
}
if let ExprKind::Call(func, _) = &expr.kind
&& let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind
&& let Some(def_id) = path.res.opt_def_id()
&& is_entrypoint_fn(cx, def_id)
{
span_lint_and_help(
cx,
MAIN_RECURSION,
func.span,
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
None,
"consider using another function for this recursion",
);
}
}
}

View File

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
@ -47,61 +46,57 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
span: Span,
def_id: LocalDefId,
) {
if_chain! {
if let Some(header) = kind.header();
if !header.asyncness.is_async();
if let Some(header) = kind.header()
&& !header.asyncness.is_async()
// Check that this function returns `impl Future`
if let FnRetTy::Return(ret_ty) = decl.output;
if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty);
if let Some(output) = future_output_ty(trait_ref);
if captures_all_lifetimes(decl.inputs, &output_lifetimes);
&& let FnRetTy::Return(ret_ty) = decl.output
&& let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty)
&& let Some(output) = future_output_ty(trait_ref)
&& captures_all_lifetimes(decl.inputs, &output_lifetimes)
// Check that the body of the function consists of one async block
if let ExprKind::Block(block, _) = body.value.kind;
if block.stmts.is_empty();
if let Some(closure_body) = desugared_async_block(cx, block);
if let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
cx.tcx.hir().get_by_def_id(def_id);
then {
let header_span = span.with_hi(ret_ty.span.hi());
&& let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(closure_body) = desugared_async_block(cx, block)
&& let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
cx.tcx.hir().get_by_def_id(def_id)
{
let header_span = span.with_hi(ret_ty.span.hi());
span_lint_and_then(
cx,
MANUAL_ASYNC_FN,
header_span,
"this function can be simplified using the `async fn` syntax",
|diag| {
if_chain! {
if let Some(vis_snip) = snippet_opt(cx, *vis_span);
if let Some(header_snip) = snippet_opt(cx, header_span);
if let Some(ret_pos) = position_before_rarrow(&header_snip);
if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
then {
let header_snip = if vis_snip.is_empty() {
format!("async {}", &header_snip[..ret_pos])
} else {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
span_lint_and_then(
cx,
MANUAL_ASYNC_FN,
header_span,
"this function can be simplified using the `async fn` syntax",
|diag| {
if let Some(vis_snip) = snippet_opt(cx, *vis_span)
&& let Some(header_snip) = snippet_opt(cx, header_span)
&& let Some(ret_pos) = position_before_rarrow(&header_snip)
&& let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
{
let header_snip = if vis_snip.is_empty() {
format!("async {}", &header_snip[..ret_pos])
} else {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable
);
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable,
);
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip,
Applicability::MachineApplicable
);
}
}
},
);
}
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip,
Applicability::MachineApplicable,
);
}
},
);
}
}
}
@ -110,48 +105,44 @@ fn future_trait_ref<'tcx>(
cx: &LateContext<'tcx>,
ty: &'tcx Ty<'tcx>,
) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
if_chain! {
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind;
let item = cx.tcx.hir().item(item_id);
if let ItemKind::OpaqueTy(opaque) = &item.kind;
if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind
&& let item = cx.tcx.hir().item(item_id)
&& let ItemKind::OpaqueTy(opaque) = &item.kind
&& let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let GenericBound::Trait(poly, _) = bound {
Some(&poly.trait_ref)
} else {
None
}
});
if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
then {
let output_lifetimes = bounds
.iter()
.filter_map(|bound| {
if let GenericArg::Lifetime(lt) = bound {
Some(lt.res)
} else {
None
}
})
.collect();
})
&& trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
{
let output_lifetimes = bounds
.iter()
.filter_map(|bound| {
if let GenericArg::Lifetime(lt) = bound {
Some(lt.res)
} else {
None
}
})
.collect();
return Some((trait_ref, output_lifetimes));
}
return Some((trait_ref, output_lifetimes));
}
None
}
fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
if_chain! {
if let Some(segment) = trait_ref.path.segments.last();
if let Some(args) = segment.args;
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;
then {
return Some(output);
}
if let Some(segment) = trait_ref.path.segments.last()
&& let Some(args) = segment.args
&& args.bindings.len() == 1
&& let binding = &args.bindings[0]
&& binding.ident.name == sym::Output
&& let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind
{
return Some(output);
}
None
@ -181,17 +172,15 @@ 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 Expr {
if let Some(block_expr) = block.expr
&& let Expr {
kind: ExprKind::Closure(&Closure { body, .. }),
..
} = block_expr;
let closure_body = cx.tcx.hir().body(body);
if closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block));
then {
return Some(closure_body);
}
} = block_expr
&& let closure_body = cx.tcx.hir().body(body)
&& closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block))
{
return Some(closure_body);
}
None

View File

@ -53,32 +53,30 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
return;
}
if_chain! {
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind;
if let BinOpKind::Mul = &bin_op.node;
if !in_external_macro(cx.sess(), expr.span);
let ctxt = expr.span.ctxt();
if left_expr.span.ctxt() == ctxt;
if right_expr.span.ctxt() == ctxt;
if let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr);
if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_));
if let ExprKind::Lit(lit) = &other_expr.kind;
if let LitKind::Int(8, _) = lit.node;
then {
let mut app = Applicability::MachineApplicable;
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
&& let BinOpKind::Mul = &bin_op.node
&& !in_external_macro(cx.sess(), expr.span)
&& let ctxt = expr.span.ctxt()
&& left_expr.span.ctxt() == ctxt
&& right_expr.span.ctxt() == ctxt
&& let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr)
&& matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_))
&& let ExprKind::Lit(lit) = &other_expr.kind
&& let LitKind::Int(8, _) = lit.node
{
let mut app = Applicability::MachineApplicable;
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
span_lint_and_sugg(
cx,
MANUAL_BITS,
expr.span,
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
"consider using",
sugg,
app,
);
}
span_lint_and_sugg(
cx,
MANUAL_BITS,
expr.span,
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
"consider using",
sugg,
app,
);
}
}
@ -98,22 +96,22 @@ fn get_one_size_of_ty<'tcx>(
}
fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>)> {
if_chain! {
if let ExprKind::Call(count_func, _func_args) = expr.kind;
if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
if let QPath::Resolved(_, count_func_path) = count_func_qpath;
if let Some(segment_zero) = count_func_path.segments.first();
if let Some(args) = segment_zero.args;
if let Some(GenericArg::Type(real_ty)) = args.args.first();
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);
then {
cx.typeck_results().node_args(count_func.hir_id).types().next().map(|resolved_ty| (*real_ty, resolved_ty))
} else {
None
}
if let ExprKind::Call(count_func, _func_args) = expr.kind
&& let ExprKind::Path(ref count_func_qpath) = count_func.kind
&& let QPath::Resolved(_, count_func_path) = count_func_qpath
&& let Some(segment_zero) = count_func_path.segments.first()
&& let Some(args) = segment_zero.args
&& let Some(GenericArg::Type(real_ty)) = args.args.first()
&& let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
{
cx.typeck_results()
.node_args(count_func.hir_id)
.types()
.next()
.map(|resolved_ty| (*real_ty, resolved_ty))
} else {
None
}
}

View File

@ -5,18 +5,15 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, Visitable};
use clippy_utils::{is_lint_allowed, pat_and_expr_can_be_question_mark, peel_blocks};
use clippy_utils::{is_lint_allowed, is_never_expr, pat_and_expr_can_be_question_mark, peel_blocks};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::declare_tool_lint;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use std::ops::ControlFlow;
use std::slice;
declare_clippy_lint! {
@ -51,7 +48,7 @@ declare_clippy_lint! {
}
impl<'tcx> QuestionMark {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
return;
}
@ -67,7 +64,7 @@ impl<'tcx> QuestionMark {
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => {
if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
&& let Some(if_else) = if_else
&& expr_diverges(cx, if_else)
&& is_never_expr(cx, if_else).is_some()
&& let qm_allowed = is_lint_allowed(cx, QUESTION_MARK, stmt.hir_id)
&& (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
{
@ -91,10 +88,9 @@ impl<'tcx> QuestionMark {
return;
}
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
let diverging_arm_opt = arms
.iter()
.enumerate()
.find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
let diverging_arm_opt = arms.iter().enumerate().find(|(_, arm)| {
is_never_expr(cx, arm.body).is_some() && pat_allowed_for_else(cx, arm.pat, check_types)
});
let Some((idx, diverging_arm)) = diverging_arm_opt else {
return;
};
@ -272,104 +268,6 @@ fn replace_in_pattern(
sn_pat.into_owned()
}
/// Check whether an expression is divergent. May give false negatives.
fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
res: ControlFlow<(), Descend>,
}
impl<'tcx> Visitor<'tcx> for V<'_, '_> {
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
return ty.is_never();
}
false
}
if self.res.is_break() {
return;
}
// We can't just call is_never on expr and be done, because the type system
// sometimes coerces the ! type to something different before we can get
// our hands on it. So instead, we do a manual search. We do fall back to
// is_never in some places when there is no better alternative.
self.res = match e.kind {
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
ExprKind::Call(call, _) => {
if is_never(self.cx, e) || is_never(self.cx, call) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::Yes)
}
},
ExprKind::MethodCall(..) => {
if is_never(self.cx, e) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::Yes)
}
},
ExprKind::If(if_expr, if_then, if_else) => {
let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
let diverges =
expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
if diverges {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::No)
}
},
ExprKind::Match(match_expr, match_arms, _) => {
let diverges = expr_diverges(self.cx, match_expr)
|| match_arms.iter().all(|arm| {
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
guard_diverges || expr_diverges(self.cx, arm.body)
});
if diverges {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::No)
}
},
// Don't continue into loops or labeled blocks, as they are breakable,
// and we'd have to start checking labels.
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
// Default: descend
_ => ControlFlow::Continue(Descend::Yes),
};
if let ControlFlow::Continue(Descend::Yes) = self.res {
walk_expr(self, e);
}
}
fn visit_local(&mut self, local: &'tcx Local<'_>) {
// Don't visit the else block of a let/else statement as it will not make
// the statement divergent even though the else block is divergent.
if let Some(init) = local.init {
self.visit_expr(init);
}
}
// Avoid unnecessary `walk_*` calls.
fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {}
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
// Avoid monomorphising all `visit_*` functions.
fn visit_nested_item(&mut self, _: ItemId) {}
}
let mut v = V {
cx,
res: ControlFlow::Continue(Descend::Yes),
};
expr.visit(&mut v);
v.res.is_break()
}
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
// Check whether the pattern contains any bindings, as the
// binding might potentially be used in the body.

View File

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -71,55 +70,61 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
return;
}
if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
if let ExprKind::Path(target_path) = &target_arg.kind;
then {
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
StripKind::Prefix
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
StripKind::Suffix
} else {
return;
};
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {
return;
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
&& let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
&& let ExprKind::Path(target_path) = &target_arg.kind
{
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
StripKind::Prefix
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
StripKind::Suffix
} else {
return;
};
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {
return;
};
if let Res::Local(hir_id) = target_res
&& let Some(used_mutably) = mutated_variables(then, cx)
&& used_mutably.contains(&hir_id)
{
return;
}
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
if !strippings.is_empty() {
let kind_word = match strip_kind {
StripKind::Prefix => "prefix",
StripKind::Suffix => "suffix",
};
if_chain! {
if let Res::Local(hir_id) = target_res;
if let Some(used_mutably) = mutated_variables(then, cx);
if used_mutably.contains(&hir_id);
then {
return;
}
}
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
if !strippings.is_empty() {
let kind_word = match strip_kind {
StripKind::Prefix => "prefix",
StripKind::Suffix => "suffix",
};
let test_span = expr.span.until(then.span);
span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
let test_span = expr.span.until(then.span);
span_lint_and_then(
cx,
MANUAL_STRIP,
strippings[0],
&format!("stripping a {kind_word} manually"),
|diag| {
diag.span_note(test_span, format!("the {kind_word} was tested here"));
multispan_sugg(
diag,
&format!("try using the `strip_{kind_word}` method"),
vec![(test_span,
format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
snippet(cx, target_arg.span, ".."),
snippet(cx, pattern.span, "..")))]
.into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
vec![(
test_span,
format!(
"if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
snippet(cx, target_arg.span, ".."),
snippet(cx, pattern.span, "..")
),
)]
.into_iter()
.chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
);
});
}
},
);
}
}
}
@ -129,15 +134,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if match_def_path(cx, method_def_id, &paths::STR_LEN);
then {
Some(arg)
} else {
None
}
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& match_def_path(cx, method_def_id, &paths::STR_LEN)
{
Some(arg)
} else {
None
}
}
@ -201,36 +204,38 @@ fn find_stripping<'tcx>(
impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
if_chain! {
if is_ref_str(self.cx, ex);
let unref = peel_ref(ex);
if let ExprKind::Index(indexed, index, _) = &unref.kind;
if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
if let ExprKind::Path(path) = &indexed.kind;
if self.cx.qpath_res(path, ex.hir_id) == self.target;
then {
match (self.strip_kind, start, end) {
(StripKind::Prefix, Some(start), None) => {
if eq_pattern_length(self.cx, self.pattern, start) {
self.results.push(ex.span);
return;
}
},
(StripKind::Suffix, None, Some(end)) => {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
if let Some(left_arg) = len_arg(self.cx, left);
if let ExprKind::Path(left_path) = &left_arg.kind;
if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
if eq_pattern_length(self.cx, self.pattern, right);
then {
self.results.push(ex.span);
return;
}
}
},
_ => {}
}
if is_ref_str(self.cx, ex)
&& let unref = peel_ref(ex)
&& let ExprKind::Index(indexed, index, _) = &unref.kind
&& let Some(higher::Range { start, end, .. }) = higher::Range::hir(index)
&& let ExprKind::Path(path) = &indexed.kind
&& self.cx.qpath_res(path, ex.hir_id) == self.target
{
match (self.strip_kind, start, end) {
(StripKind::Prefix, Some(start), None) => {
if eq_pattern_length(self.cx, self.pattern, start) {
self.results.push(ex.span);
return;
}
},
(StripKind::Suffix, None, Some(end)) => {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
left,
right,
) = end.kind
&& let Some(left_arg) = len_arg(self.cx, left)
&& let ExprKind::Path(left_path) = &left_arg.kind
&& self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
&& eq_pattern_length(self.cx, self.pattern, right)
{
self.results.push(ex.span);
return;
}
},
_ => {},
}
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{iter_input_pats, method_chain_args};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -163,16 +162,14 @@ fn unit_closure<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
if_chain! {
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind;
let body = cx.tcx.hir().body(body);
let body_expr = &body.value;
if fn_decl.inputs.len() == 1;
if is_unit_expression(cx, body_expr);
if let Some(binding) = iter_input_pats(fn_decl, body).next();
then {
return Some((binding, body_expr));
}
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind
&& let body = cx.tcx.hir().body(body)
&& let body_expr = &body.value
&& fn_decl.inputs.len() == 1
&& is_unit_expression(cx, body_expr)
&& let Some(binding) = iter_input_pats(fn_decl, body).next()
{
return Some((binding, body_expr));
}
None
}

View File

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, is_res_lang_ctor};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, PatKind};
use rustc_lint::{LateContext, LateLintPass};
@ -56,33 +55,31 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
return;
};
if_chain! {
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation
if ok_path.ident.as_str() == "ok";
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
let ctxt = expr.span.ctxt();
if let_expr.span.ctxt() == ctxt;
if let_pat.span.ctxt() == ctxt;
then {
let mut applicability = Applicability::MachineApplicable;
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!(
"{ifwhile} let Ok({some_expr_string}) = {}",
trimmed_ok.trim().trim_end_matches('.'),
);
span_lint_and_sugg(
cx,
MATCH_RESULT_OK,
expr.span.with_hi(let_expr.span.hi()),
"matching on `Some` with `ok()` is redundant",
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
sugg,
applicability,
);
}
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result<T,E>.ok(, _)
&& let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation
&& ok_path.ident.as_str() == "ok"
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
&& let ctxt = expr.span.ctxt()
&& let_expr.span.ctxt() == ctxt
&& let_pat.span.ctxt() == ctxt
{
let mut applicability = Applicability::MachineApplicable;
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!(
"{ifwhile} let Ok({some_expr_string}) = {}",
trimmed_ok.trim().trim_end_matches('.'),
);
span_lint_and_sugg(
cx,
MATCH_RESULT_OK,
expr.span.with_hi(let_expr.span.hi()),
"matching on `Some` with `ok()` is redundant",
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
sugg,
applicability,
);
}
}
}

View File

@ -5,7 +5,6 @@ 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,
};
use if_chain::if_chain;
use rustc_errors::MultiSpan;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
@ -40,76 +39,73 @@ fn check_arm<'tcx>(
outer_else_body: Option<&'tcx Expr<'tcx>>,
) {
let inner_expr = peel_blocks_with_stmt(outer_then_body);
if_chain! {
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr)
&& let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none())
// if there are more than two arms, collapsing would be non-trivial
if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
// one of the arms must be "wild-like"
if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
then {
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
Some((scrutinee, then.pat, Some(els.body)))
} else {
None
}
&& let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a))
{
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
Some((scrutinee, then.pat, Some(els.body)))
} else {
None
},
};
if outer_pat.span.eq_ctxt(inner_scrutinee.span);
}
&& outer_pat.span.eq_ctxt(inner_scrutinee.span)
// match expression must be a local binding
// match <local> { .. }
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
if !pat_contains_or(inner_then_pat);
&& let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee))
&& !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), is_innermost_parent_pat_struct)
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id);
&& 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) {
&& match (outer_else_body, inner_else_body) {
(None, None) => true,
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
};
}
// the binding must not be used in the if guard
if outer_guard.map_or(
&& outer_guard.map_or(
true,
|(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
);
)
// ...or anywhere in the inner expression
if match inner {
&& match inner {
IfLetOrMatch::IfLet(_, _, body, els) => {
!is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
},
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
};
then {
let msg = format!(
"this `{}` can be collapsed into the outer `{}`",
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,
inner_expr.span,
&msg,
|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, format!("with this pattern{replace_msg}"));
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
}
{
let msg = format!(
"this `{}` can be collapsed into the outer `{}`",
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, inner_expr.span, &msg, |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, format!("with this pattern{replace_msg}"));
diag.span_help(
help_span,
"the outer pattern can be modified to include the inner pattern",
);
});
}
}

View File

@ -8,38 +8,35 @@ use rustc_lint::LateContext;
use super::INFALLIBLE_DESTRUCTURING_MATCH;
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
if_chain! {
if !local.span.from_expansion();
if let Some(expr) = local.init;
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
if arms.len() == 1 && arms[0].guard.is_none();
if let PatKind::TupleStruct(
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
if args.len() == 1;
if let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind;
let body = peel_blocks(arms[0].body);
if path_to_local_id(body, arg);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
INFALLIBLE_DESTRUCTURING_MATCH,
local.span,
"you seem to be trying to use `match` to destructure a single infallible pattern. \
Consider using `let`",
"try",
format!(
"let {}({}{}) = {};",
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
if binding.0 == ByRef::Yes { "ref " } else { "" },
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
snippet_with_applicability(cx, target.span, "..", &mut applicability),
),
applicability,
);
return true;
}
if !local.span.from_expansion()
&& let Some(expr) = local.init
&& let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind
&& arms.len() == 1
&& arms[0].guard.is_none()
&& let PatKind::TupleStruct(QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind
&& args.len() == 1
&& let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind
&& let body = peel_blocks(arms[0].body)
&& path_to_local_id(body, arg)
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
INFALLIBLE_DESTRUCTURING_MATCH,
local.span,
"you seem to be trying to use `match` to destructure a single infallible pattern. \
Consider using `let`",
"try",
format!(
"let {}({}{}) = {};",
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
if binding.0 == ByRef::Yes { "ref " } else { "" },
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
snippet_with_applicability(cx, target.span, "..", &mut applicability),
),
applicability,
);
return true;
}
false
}

View File

@ -21,19 +21,19 @@ fn get_cond_expr<'tcx>(
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 is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr); // 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: is_none_expr(cx, then_expr) // if the `then_expr` resolves to `None`, need to negate the cond
})
}
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr)
&& let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind
&& let PatKind::Binding(_, target, ..) = pat.kind
&& (is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr))
// check that one expr resolves to `Some(x)`, the other to `None`
{
return Some(SomeExpr {
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
needs_unsafe_block: contains_unsafe_block(cx, expr),
needs_negated: is_none_expr(cx, then_expr), /* if the `then_expr` resolves to `None`, need to negate the
* cond */
});
};
None
}

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