1
Fork 0

Merge commit 'edb720b199' 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 ## 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 ## 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) [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_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_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_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_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_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 [`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 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_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] [package]
name = "clippy" name = "clippy"
version = "0.1.75" version = "0.1.76"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" 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. nothing more has to be done.
When declaring a new lint by hand and `cargo dev update_lints` is used, the lint 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`: `clippy_lints/src/lib.rs`:
```rust,ignore ```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 required Rust feature. If multiple features are required, just use the one with
a lower MSRV. 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. This can be accessed later as `msrvs::STR_STRIP_PREFIX`, for example.
```rust ```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 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 ```rust
define_Conf! { 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 ## 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 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: 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 ```rust,ignore
/// Lint: LINT_NAME. /// 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. 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 [`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`]: 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 [`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. we'd use `cargo dev update_lints` command afterwards.
When a lint is manually declared, we might need to register the lint pass 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 ```rust
store.register_late_pass(|_| Box::new(foo_functions::FooFunctions)); store.register_late_pass(|_| Box::new(foo_functions::FooFunctions));

View file

@ -1 +1,7 @@
avoid-breaking-exported-api = false 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] [package]
name = "clippy_config" name = "clippy_config"
version = "0.1.75" version = "0.1.76"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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::de::{self, Deserializer, Visitor};
use serde::{ser, Deserialize, Serialize}; use serde::{ser, Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Rename { 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 { pub enum MatchLintBehaviour {
AllTypes, AllTypes,
WellKnownTypes, WellKnownTypes,
Never, Never,
} }
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct MacroMatcher { pub struct MacroMatcher {
pub name: String, 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 { impl<'de> Deserialize<'de> for MacroMatcher {
fn deserialize<D>(deser: D) -> Result<Self, D::Error> fn deserialize<D>(deser: D) -> Result<Self, D::Error>
where where
@ -83,7 +69,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
V: de::MapAccess<'de>, V: de::MapAccess<'de>,
{ {
let mut name = None; let mut name = None;
let mut brace: Option<String> = None; let mut brace: Option<char> = None;
while let Some(key) = map.next_key()? { while let Some(key) = map.next_key()? {
match key { match key {
Field::Name => { Field::Name => {
@ -104,7 +90,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?; let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
Ok(MacroMatcher { Ok(MacroMatcher {
name, name,
braces: [("(", ")"), ("{", "}"), ("[", "]")] braces: [('(', ')'), ('{', '}'), ('[', ']')]
.into_iter() .into_iter()
.find(|b| b.0 == brace) .find(|b| b.0 == brace)
.map(|(o, c)| (o.to_owned(), c.to_owned())) .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}); extract_msrv_attr!({context_import});
}} }}
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed. // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs` // TODO: Update msrv config comment in `clippy_config/src/conf.rs`
"# "#
) )
} else { } else {

View file

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

View file

@ -52,24 +52,22 @@ declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
impl LateLintPass<'_> for AllowAttribute { impl LateLintPass<'_> for AllowAttribute {
// Separate each crate's features. // Separate each crate's features.
fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) { fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) {
if_chain! { if !in_external_macro(cx.sess(), attr.span)
if !in_external_macro(cx.sess(), attr.span); && cx.tcx.features().lint_reasons
if cx.tcx.features().lint_reasons; && let AttrStyle::Outer = attr.style
if let AttrStyle::Outer = attr.style; && let Some(ident) = attr.ident()
if let Some(ident) = attr.ident(); && ident.name == rustc_span::symbol::sym::allow
if ident.name == rustc_span::symbol::sym::allow; && !is_from_proc_macro(cx, &attr)
if !is_from_proc_macro(cx, &attr); {
then { span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, ALLOW_ATTRIBUTES,
ALLOW_ATTRIBUTES, ident.span,
ident.span, "#[allow] attribute found",
"#[allow] attribute found", "replace it with",
"replace it with", "expect".into(),
"expect".into(), Applicability::MachineApplicable,
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`. /// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
/// ///
/// ### Why is this bad? /// ### 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` /// either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
/// ///
/// ### Example /// ### Example
@ -34,7 +36,7 @@ declare_clippy_lint! {
#[clippy::version = "1.72.0"] #[clippy::version = "1.72.0"]
pub ARC_WITH_NON_SEND_SYNC, pub ARC_WITH_NON_SEND_SYNC,
suspicious, 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]); declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]);
@ -61,19 +63,25 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync {
cx, cx,
ARC_WITH_NON_SEND_SYNC, ARC_WITH_NON_SEND_SYNC,
expr.span, expr.span,
"usage of an `Arc` that is not `Send` or `Sync`", "usage of an `Arc` that is not `Send` and `Sync`",
|diag| { |diag| {
with_forced_trimmed_paths!({ with_forced_trimmed_paths!({
diag.note(format!("`Arc<{arg_ty}>` is not `Send` and `Sync` as:"));
if !is_send { 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 { 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::is_from_proc_macro;
use clippy_utils::macros::{is_panic, macro_backtrace}; 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 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::token::{Token, TokenKind};
use rustc_ast::tokenstream::TokenTree; use rustc_ast::tokenstream::TokenTree;
use rustc_ast::{ use rustc_ast::{
@ -20,7 +19,7 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use rustc_span::{sym, DUMMY_SP, Span}; use rustc_span::{sym, Span, DUMMY_SP};
use semver::Version; use semver::Version;
static UNIX_SYSTEMS: &[&str] = &[ static UNIX_SYSTEMS: &[&str] = &[
@ -371,7 +370,7 @@ declare_clippy_lint! {
/// let _ = 1 / random(); /// let _ = 1 / random();
/// } /// }
/// ``` /// ```
#[clippy::version = "1.73.0"] #[clippy::version = "1.74.0"]
pub SHOULD_PANIC_WITHOUT_EXPECT, pub SHOULD_PANIC_WITHOUT_EXPECT,
pedantic, pedantic,
"ensures that all `should_panic` attributes specify its expected panic message" "ensures that all `should_panic` attributes specify its expected panic message"
@ -470,13 +469,11 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return; return;
} }
for item in items { for item in items {
if_chain! { if let NestedMetaItem::MetaItem(mi) = &item
if let NestedMetaItem::MetaItem(mi) = &item; && let MetaItemKind::NameValue(lit) = &mi.kind
if let MetaItemKind::NameValue(lit) = &mi.kind; && mi.has_name(sym::since)
if mi.has_name(sym::since); {
then { check_semver(cx, item.span(), lit);
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. /// Returns the lint name if it is clippy lint.
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> { fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
if_chain! { if let Some(meta_item) = lint.meta_item()
if let Some(meta_item) = lint.meta_item(); && meta_item.path.segments.len() > 1
if meta_item.path.segments.len() > 1; && let tool_name = meta_item.path.segments[0].ident
if let tool_name = meta_item.path.segments[0].ident; && tool_name.name == sym::clippy
if tool_name.name == sym::clippy; {
then { let lint_name = meta_item.path.segments.last().unwrap().ident.name;
let lint_name = meta_item.path.segments.last().unwrap().ident.name; return Some(lint_name);
return Some(lint_name);
}
} }
None 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) { 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 // check cfg_attr
if attr.has_name(sym::cfg_attr); && attr.has_name(sym::cfg_attr)
if let Some(items) = attr.meta_item_list(); && let Some(items) = attr.meta_item_list()
if items.len() == 2; && items.len() == 2
// check for `rustfmt` // check for `rustfmt`
if let Some(feature_item) = items[0].meta_item(); && let Some(feature_item) = items[0].meta_item()
if feature_item.has_name(sym::rustfmt); && feature_item.has_name(sym::rustfmt)
// check for `rustfmt_skip` and `rustfmt::skip` // check for `rustfmt_skip` and `rustfmt::skip`
if let Some(skip_item) = &items[1].meta_item(); && let Some(skip_item) = &items[1].meta_item()
if skip_item.has_name(sym!(rustfmt_skip)) && (skip_item.has_name(sym!(rustfmt_skip))
|| skip_item || skip_item
.path .path
.segments .segments
@ -875,21 +869,20 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
.expect("empty path in attribute") .expect("empty path in attribute")
.ident .ident
.name .name
== sym::skip; == sym::skip)
// Only lint outer attributes, because custom inner attributes are unstable // Only lint outer attributes, because custom inner attributes are unstable
// Tracking issue: https://github.com/rust-lang/rust/issues/54726 // Tracking issue: https://github.com/rust-lang/rust/issues/54726
if attr.style == AttrStyle::Outer; && attr.style == AttrStyle::Outer
then { {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
DEPRECATED_CFG_ATTR, DEPRECATED_CFG_ATTR,
attr.span, attr.span,
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
"use", "use",
"#[rustfmt::skip]".to_string(), "#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
}
} }
} }
@ -989,12 +982,10 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched.extend(find_mismatched_target_os(list)); mismatched.extend(find_mismatched_target_os(list));
}, },
MetaItemKind::Word => { MetaItemKind::Word => {
if_chain! { if let Some(ident) = meta.ident()
if let Some(ident) = meta.ident(); && let Some(os) = find_os(ident.name.as_str())
if let Some(os) = find_os(ident.name.as_str()); {
then { mismatched.push((os, ident.span));
mismatched.push((os, ident.span));
}
} }
}, },
MetaItemKind::NameValue(..) => {}, MetaItemKind::NameValue(..) => {},
@ -1005,30 +996,28 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched mismatched
} }
if_chain! { if attr.has_name(sym::cfg)
if attr.has_name(sym::cfg); && let Some(list) = attr.meta_item_list()
if let Some(list) = attr.meta_item_list(); && let mismatched = find_mismatched_target_os(&list)
let mismatched = find_mismatched_target_os(&list); && !mismatched.is_empty()
if !mismatched.is_empty(); {
then { let mess = "operating system used in target family position";
let mess = "operating system used in target family position";
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| { span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case // Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems // we have more than one mismatch for unix-like systems
let mut unix_suggested = false; let mut unix_suggested = false;
for (os, span) in mismatched { for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\""); let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
if !unix_suggested && is_unix(os) { if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?"); diag.help("did you mean `unix`?");
unix_suggested = true; 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::visitors::{for_each_expr, Descend};
use clippy_utils::{get_parent_expr, higher}; use clippy_utils::{get_parent_expr, higher};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind}; use rustc_hir::{BlockCheckMode, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -114,15 +113,13 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
let _: Option<!> = for_each_expr(cond, |e| { let _: Option<!> = for_each_expr(cond, |e| {
if let ExprKind::Closure(closure) = e.kind { if let ExprKind::Closure(closure) = e.kind {
// do not lint if the closure is called using an iterator (see #1141) // 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 Some(parent) = get_parent_expr(cx, e); && let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind; && let caller = cx.typeck_results().expr_ty(self_arg)
let caller = cx.typeck_results().expr_ty(self_arg); && let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator); && implements_trait(cx, caller, iter_id, &[])
if implements_trait(cx, caller, iter_id, &[]); {
then { return ControlFlow::Continue(Descend::No);
return ControlFlow::Continue(Descend::No);
}
} }
let body = cx.tcx.hir().body(closure.body); 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::eq_expr_value;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; 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)); return Ok(Bool::Term(n as u8));
} }
if_chain! { if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind; && implements_ord(self.cx, e_lhs)
if implements_ord(self.cx, e_lhs); && let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind; && negate(e_binop.node) == Some(expr_binop.node)
if negate(e_binop.node) == Some(expr_binop.node); && eq_expr_value(self.cx, e_lhs, expr_lhs)
if eq_expr_value(self.cx, e_lhs, expr_lhs); && eq_expr_value(self.cx, e_rhs, expr_rhs)
if eq_expr_value(self.cx, e_rhs, expr_rhs); {
then { #[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_truncation)] return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
}
} }
} }
let n = self.terminals.len(); let n = self.terminals.len();

View file

@ -49,69 +49,62 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
if_chain! { if !e.span.from_expansion()
if !e.span.from_expansion(); && let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind; && !addrof_target.span.from_expansion()
if !addrof_target.span.from_expansion(); && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
if let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind; && !deref_target.span.from_expansion()
if !deref_target.span.from_expansion(); && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) ); && let ref_ty = cx.typeck_results().expr_ty(deref_target)
let ref_ty = cx.typeck_results().expr_ty(deref_target); && let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind()
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind(); && !is_from_proc_macro(cx, e)
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, ..))
if let Some(parent_expr) = get_parent_expr(cx, e){ && !is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id)
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..)) && {
!is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id) { return;
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;
}
} }
span_lint_and_then( // modification to `&mut &*x` is different from `&mut x`
cx, if matches!(
BORROW_DEREF_REF, deref_target.kind,
e.span, ExprKind::Path(..) | ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, ..)
"deref on an immutable reference", ) && matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _))
|diag| { {
diag.span_suggestion( return;
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
);
}
);
} }
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 cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use itertools::Itertools; use itertools::Itertools;
use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::def_id::LOCAL_CRATE;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -15,31 +14,33 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
let mut packages = metadata.packages.clone(); let mut packages = metadata.packages.clone();
packages.sort_by(|a, b| a.name.cmp(&b.name)); packages.sort_by(|a, b| a.name.cmp(&b.name));
if_chain! { if let Some(resolve) = &metadata.resolve
if let Some(resolve) = &metadata.resolve; && let Some(local_id) = packages.iter().find_map(|p| {
if let Some(local_id) = packages if p.name == local_name.as_str() {
.iter() Some(&p.id)
.find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None }); } else {
then { None
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { }
let group: Vec<&Package> = group.collect(); })
{
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
let group: Vec<&Package> = group.collect();
if group.len() <= 1 { if group.len() <= 1 {
continue; continue;
} }
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { 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(); let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
versions.sort(); versions.sort();
let versions = versions.iter().join(", "); let versions = versions.iter().join(", ");
span_lint( span_lint(
cx, cx,
MULTIPLE_CRATE_VERSIONS, MULTIPLE_CRATE_VERSIONS,
DUMMY_SP, DUMMY_SP,
&format!("multiple versions for dependency `{name}`: {versions}"), &format!("multiple versions for dependency `{name}`: {versions}"),
); );
}
} }
} }
} }

View file

@ -1,6 +1,5 @@
use cargo_metadata::Metadata; use cargo_metadata::Metadata;
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
@ -9,19 +8,17 @@ use super::WILDCARD_DEPENDENCIES;
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
for dep in &metadata.packages[0].dependencies { for dep in &metadata.packages[0].dependencies {
// VersionReq::any() does not work // VersionReq::any() does not work
if_chain! { if let Ok(wildcard_ver) = semver::VersionReq::parse("*")
if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); && let Some(ref source) = dep.source
if let Some(ref source) = dep.source; && !source.starts_with("git")
if !source.starts_with("git"); && dep.req == wildcard_ver
if dep.req == wildcard_ver; {
then { span_lint(
span_lint( cx,
cx, WILDCARD_DEPENDENCIES,
WILDCARD_DEPENDENCIES, DUMMY_SP,
DUMMY_SP, &format!("wildcard dependency for `{}`", dep.name),
&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_hir::Expr;
use rustc_lint::{LateContext, LintContext}; use rustc_lint::LateContext;
use rustc_middle::ty::Ty; use rustc_middle::ty::Ty;
use super::{utils, CAST_POSSIBLE_WRAP}; 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 { if let EmitState::LintOnPtrSize(16) = should_lint {
diag diag
.note("`usize` and `isize` may be as small as 16 bits on some platforms") .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") .note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
} else { };
diag
}
}); });
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@ use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{for_each_expr, Visitable}; 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 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_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -25,40 +24,40 @@ pub(super) fn check<'tcx>(
) -> bool { ) -> bool {
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default(); 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 // check both mutability and type are the same
if cast_from.kind() == cast_to.kind(); && cast_from.kind() == cast_to.kind()
if let ExprKind::Cast(_, cast_to_hir) = expr.kind; && let ExprKind::Cast(_, cast_to_hir) = expr.kind
// Ignore casts to e.g. type aliases and infer types // Ignore casts to e.g. type aliases and infer types
// - p as pointer_alias // - p as pointer_alias
// - p as _ // - p as _
if let TyKind::Ptr(to_pointee) = cast_to_hir.kind; && let TyKind::Ptr(to_pointee) = cast_to_hir.kind
then { {
match to_pointee.ty.kind { match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g. // Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias) // - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant) // - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => { TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) { if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false; return false;
} }
}, },
// Ignore `p as *const _` // Ignore `p as *const _`
TyKind::Infer => return false, 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,
);
} }
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 // skip cast of local that is a type alias
@ -86,14 +85,12 @@ pub(super) fn check<'tcx>(
} }
// skip cast to non-primitive type // skip cast to non-primitive type
if_chain! { if let ExprKind::Cast(_, cast_to) = expr.kind
if let ExprKind::Cast(_, cast_to) = expr.kind; && let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind; && let Res::PrimTy(_) = path.res
if let Res::PrimTy(_) = path.res; {
then {} } else {
else { return false;
return false;
}
} }
// skip cast of fn call that returns type alias // 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) { if let Some(lit) = get_numeric_literal(cast_expr) {
let literal_str = &cast_str; let literal_str = &cast_str;
if_chain! { if let LitKind::Int(n, _) = lit.node
if let LitKind::Int(n, _) = lit.node; && let Some(src) = snippet_opt(cx, cast_expr.span)
if let Some(src) = snippet_opt(cx, cast_expr.span); && cast_to.is_floating_point()
if cast_to.is_floating_point(); && let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); && let from_nbits = 128 - n.leading_zeros()
let from_nbits = 128 - n.leading_zeros(); && let to_nbits = fp_ty_mantissa_nbits(cast_to)
let to_nbits = fp_ty_mantissa_nbits(cast_to); && from_nbits != 0
if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); && to_nbits != 0
then { && from_nbits <= to_nbits
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to); && num_lit.is_decimal()
return true {
} lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
} }
match lit.node { 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::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq}; use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -55,20 +54,17 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
return; return;
} }
let result = if_chain! { let result = if !in_constant(cx, item.hir_id)
if !in_constant(cx, item.hir_id); && !in_external_macro(cx.sess(), item.span)
if !in_external_macro(cx.sess(), item.span); && let ExprKind::Binary(op, left, right) = &item.kind
if let ExprKind::Binary(op, left, right) = &item.kind; {
match op.node {
then { BinOpKind::Ge | BinOpKind::Le => single_check(item),
match op.node { BinOpKind::And => double_check(cx, left, right),
BinOpKind::Ge | BinOpKind::Le => single_check(item), _ => None,
BinOpKind::And => double_check(cx, left, right),
_ => None,
}
} else {
None
} }
} else {
None
}; };
if let Some(cv) = result { if let Some(cv) = result {
@ -193,16 +189,13 @@ impl ConversionType {
/// Check for `expr <= (to_type::MAX as from_type)` /// Check for `expr <= (to_type::MAX as from_type)`
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> { 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 ExprKind::Binary(ref op, left, right) = &expr.kind; && let Some((candidate, check)) = normalize_le_ge(op, left, right)
if let Some((candidate, check)) = normalize_le_ge(op, left, right); && let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX")
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX"); {
Conversion::try_new(candidate, from, to)
then { } else {
Conversion::try_new(candidate, from, to) None
} else {
None
}
} }
} }
@ -243,33 +236,27 @@ fn get_types_from_cast<'a>(
) -> Option<(&'a str, &'a str)> { ) -> Option<(&'a str, &'a str)> {
// `to_type::max_value() as from_type` // `to_type::max_value() as from_type`
// or `to_type::MAX 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 // to_type::max_value(), from_type
if let ExprKind::Cast(limit, from_type) = &expr.kind; && let TyKind::Path(ref from_type_path) = &from_type.kind
if let TyKind::Path(ref from_type_path) = &from_type.kind; && let Some(from_sym) = int_ty_to_sym(from_type_path)
if let Some(from_sym) = int_ty_to_sym(from_type_path); {
Some((limit, from_sym))
then { } else {
Some((limit, from_sym)) None
} else {
None
}
}; };
// `from_type::from(to_type::max_value())` // `from_type::from(to_type::max_value())`
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { 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()` // `from_type::from, to_type::max_value()`
if let ExprKind::Call(from_func, [limit]) = &expr.kind;
// `from_type::from` // `from_type::from`
if let ExprKind::Path(ref path) = &from_func.kind; && let ExprKind::Path(ref path) = &from_func.kind
if let Some(from_sym) = get_implementing_type(path, INTS, "from"); && let Some(from_sym) = get_implementing_type(path, INTS, "from")
{
then { Some((limit, from_sym))
Some((limit, from_sym)) } else {
} else { None
None
}
} }
}); });
@ -298,31 +285,27 @@ fn get_types_from_cast<'a>(
/// Gets the type which implements the called function /// Gets the type which implements the called function
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { 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 let QPath::TypeRelative(ty, path) = &path; && path.ident.name.as_str() == function
if path.ident.name.as_str() == function; && let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind; && let [int] = tp.segments
if let [int] = tp.segments; {
then { let name = int.ident.name.as_str();
let name = int.ident.name.as_str(); candidates.iter().find(|c| &name == *c).copied()
candidates.iter().find(|c| &name == *c).copied() } else {
} else { None
None
}
} }
} }
/// Gets the type as a string, if it is a supported integer /// Gets the type as a string, if it is a supported integer
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! { if let QPath::Resolved(_, path) = *path
if let QPath::Resolved(_, path) = *path; && let [ty] = path.segments
if let [ty] = path.segments; {
then { let name = ty.ident.name.as_str();
let name = ty.ident.name.as_str(); INTS.iter().find(|c| &name == *c).copied()
INTS.iter().find(|c| &name == *c).copied() } else {
} else { None
None
}
} }
} }

View file

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

View file

@ -117,7 +117,7 @@ declare_clippy_lint! {
/// ``` /// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub IF_SAME_THEN_ELSE, pub IF_SAME_THEN_ELSE,
correctness, style,
"`if` with the same `then` and `else` blocks" "`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_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym; use rustc_span::sym;
use if_chain::if_chain;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for types that implement `Copy` as well as /// 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 { impl<'tcx> LateLintPass<'tcx> for CopyIterator {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! { if let ItemKind::Impl(Impl {
if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref),
of_trait: Some(ref trait_ref), ..
.. }) = item.kind
}) = item.kind; && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); && is_copy(cx, ty)
if is_copy(cx, ty); && let Some(trait_id) = trait_ref.trait_def_id()
if let Some(trait_id) = trait_ref.trait_def_id(); && cx.tcx.is_diagnostic_item(sym::Iterator, trait_id)
if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id); {
then { span_lint_and_note(
span_lint_and_note( cx,
cx, COPY_ITERATOR,
COPY_ITERATOR, item.span,
item.span, "you are implementing `Iterator` on a `Copy` type",
"you are implementing `Iterator` on a `Copy` type", None,
None, "consider implementing `IntoIterator` instead",
"consider implementing `IntoIterator` instead", );
);
}
} }
} }
} }

View file

@ -53,35 +53,31 @@ declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
impl EarlyLintPass for CrateInMacroDef { impl EarlyLintPass for CrateInMacroDef {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! { if item.attrs.iter().any(is_macro_export)
if item.attrs.iter().any(is_macro_export); && let ItemKind::MacroDef(macro_def) = &item.kind
if let ItemKind::MacroDef(macro_def) = &item.kind; && let tts = macro_def.body.tokens.clone()
let tts = macro_def.body.tokens.clone(); && let Some(span) = contains_unhygienic_crate_reference(&tts)
if let Some(span) = contains_unhygienic_crate_reference(&tts); {
then { span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, CRATE_IN_MACRO_DEF,
CRATE_IN_MACRO_DEF, span,
span, "`crate` references the macro call's crate",
"`crate` references the macro call's crate", "to reference the macro definition's crate, use",
"to reference the macro definition's crate, use", String::from("$crate"),
String::from("$crate"), Applicability::MachineApplicable,
Applicability::MachineApplicable, );
);
}
} }
} }
} }
fn is_macro_export(attr: &Attribute) -> bool { fn is_macro_export(attr: &Attribute) -> bool {
if_chain! { if let AttrKind::Normal(normal) = &attr.kind
if let AttrKind::Normal(normal) = &attr.kind; && let [segment] = normal.item.path.segments.as_slice()
if let [segment] = normal.item.path.segments.as_slice(); {
then { segment.ident.name == sym::macro_export
segment.ident.name == sym::macro_export } else {
} else { false
false
}
} }
} }
@ -89,14 +85,12 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
let mut prev_is_dollar = false; let mut prev_is_dollar = false;
let mut cursor = tts.trees(); let mut cursor = tts.trees();
while let Some(curr) = cursor.next() { while let Some(curr) = cursor.next() {
if_chain! { if !prev_is_dollar
if !prev_is_dollar; && let Some(span) = is_crate_keyword(curr)
if let Some(span) = is_crate_keyword(curr); && let Some(next) = cursor.look_ahead(0)
if let Some(next) = cursor.look_ahead(0); && is_token(next, &TokenKind::ModSep)
if is_token(next, &TokenKind::ModSep); {
then { return Some(span);
return Some(span);
}
} }
if let TokenTree::Delimited(_, _, tts) = &curr { if let TokenTree::Delimited(_, _, tts) = &curr {
let span = contains_unhygienic_crate_reference(tts); 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> { fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
if_chain! { if let TokenTree::Token(
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt; Token {
if symbol.as_str() == "crate"; kind: TokenKind::Ident(symbol, _),
then { Some(*span) } else { None } 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::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -33,22 +32,20 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir { impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if let ExprKind::Call(func, [arg, ..]) = expr.kind
if let ExprKind::Call(func, [arg, ..]) = expr.kind; && let ExprKind::Path(ref path) = func.kind
if let ExprKind::Path(ref path) = func.kind; && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
if 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)
if cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id); {
then { span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, CREATE_DIR,
CREATE_DIR, expr.span,
expr.span, "calling `std::fs::create_dir` where there may be a better way",
"calling `std::fs::create_dir` where there may be a better way", "consider calling `std::fs::create_dir_all` instead",
"consider calling `std::fs::create_dir_all` instead", format!("create_dir_all({})", snippet(cx, arg.span, "..")),
format!("create_dir_all({})", snippet(cx, arg.span, "..")), Applicability::MaybeIncorrect,
Applicability::MaybeIncorrect, );
)
}
} }
} }
} }

View file

@ -6,7 +6,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node}; use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, BytePos, Pos, Span}; use rustc_span::sym;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -31,31 +31,6 @@ declare_clippy_lint! {
"`dbg!` macro is intended as a debugging tool" "`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)] #[derive(Copy, Clone)]
pub struct DbgMacro { pub struct DbgMacro {
allow_dbg_in_tests: bool, allow_dbg_in_tests: bool,
@ -88,10 +63,10 @@ impl LateLintPass<'_> for DbgMacro {
ExprKind::Block(..) => { ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions, // If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement. // remove the whole statement.
if let Some(Node::Stmt(stmt)) = cx.tcx.hir().find_parent(expr.hir_id) if let Some(Node::Stmt(_)) = cx.tcx.hir().find_parent(expr.hir_id)
&& let Some(span) = span_including_semi(cx, stmt.span.source_callsite()) && 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 { } else {
(macro_call.span, String::from("()")) (macro_call.span, String::from("()"))
} }

View file

@ -10,8 +10,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO, crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
#[cfg(feature = "internal")] #[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, crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO, 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_PANICS_DOC_INFO,
crate::doc::MISSING_SAFETY_DOC_INFO, crate::doc::MISSING_SAFETY_DOC_INFO,
crate::doc::NEEDLESS_DOCTEST_MAIN_INFO, crate::doc::NEEDLESS_DOCTEST_MAIN_INFO,
crate::doc::SUSPICIOUS_DOC_COMMENTS_INFO,
crate::doc::UNNECESSARY_SAFETY_DOC_INFO, crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
crate::double_parens::DOUBLE_PARENS_INFO, crate::double_parens::DOUBLE_PARENS_INFO,
crate::drop_forget_ref::DROP_NON_DROP_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_statements::ITEMS_AFTER_STATEMENTS_INFO,
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO, crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_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::INTO_ITER_WITHOUT_ITER_INFO,
crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO, crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO,
crate::large_const_arrays::LARGE_CONST_ARRAYS_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::STR_TO_STRING_INFO,
crate::strings::TRIM_SPLIT_WHITESPACE_INFO, crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_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_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO, crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_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::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy}; 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 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_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; 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 { impl<'tcx> LateLintPass<'tcx> for Default {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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` // Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span); && !self.reassigned_linted.contains(&expr.span)
if let ExprKind::Call(path, ..) = expr.kind; && let ExprKind::Call(path, ..) = expr.kind
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); && !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
if let ExprKind::Path(ref qpath) = path.kind; && let ExprKind::Path(ref qpath) = path.kind
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); && 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); && cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
if !is_update_syntax_base(cx, expr); && !is_update_syntax_base(cx, expr)
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type. // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath; && let QPath::Resolved(None, _path) = qpath
let expr_ty = cx.typeck_results().expr_ty(expr); && let expr_ty = cx.typeck_results().expr_ty(expr)
if let ty::Adt(def, ..) = expr_ty.kind(); && let ty::Adt(def, ..) = expr_ty.kind()
if !is_from_proc_macro(cx, expr); && !is_from_proc_macro(cx, expr)
then { {
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did()))); let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
DEFAULT_TRAIT_ACCESS, DEFAULT_TRAIT_ACCESS,
expr.span, expr.span,
&format!("calling `{replacement}` is more clear than this expression"), &format!("calling `{replacement}` is more clear than this expression"),
"try", "try",
replacement, replacement,
Applicability::Unspecified, // First resolve the TODO above 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 // 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 // `default` method of the `Default` trait, and store statement index in current block being
// checked and the name of the bound variable // 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 // only take `let ...` statements
if let StmtKind::Local(local) = stmt.kind; && let Some(expr) = local.init
if let Some(expr) = local.init; && !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); && !expr.span.from_expansion()
if !expr.span.from_expansion();
// only take bindings to identifiers // 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()` // only when assigning `... = Default::default()`
if is_expr_default(expr, cx); && is_expr_default(expr, cx)
let binding_type = cx.typeck_results().node_type(binding_id); && let binding_type = cx.typeck_results().node_type(binding_id)
if let Some(adt) = binding_type.ty_adt_def(); && let Some(adt) = binding_type.ty_adt_def()
if adt.is_struct(); && adt.is_struct()
let variant = adt.non_enum_variant(); && let variant = adt.non_enum_variant()
if adt.did().is_local() || !variant.is_field_list_non_exhaustive(); && (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
let module_did = cx.tcx.parent_module(stmt.hir_id); && let module_did = cx.tcx.parent_module(stmt.hir_id)
if variant && variant
.fields .fields
.iter() .iter()
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx)); .all(|field| field.vis.is_accessible_from(module_did, cx.tcx))
let all_fields_are_copy = variant && let all_fields_are_copy = variant
.fields .fields
.iter() .iter()
.all(|field| { .all(|field| {
is_copy(cx, cx.tcx.type_of(field.did).instantiate_identity()) is_copy(cx, cx.tcx.type_of(field.did).instantiate_identity())
}); })
if !has_drop(cx, binding_type) || all_fields_are_copy; && (!has_drop(cx, binding_type) || all_fields_are_copy)
then { {
(local, variant, ident.name, binding_type, expr.span) (local, variant, ident.name, binding_type, expr.span)
} else { } else {
continue; continue;
}
}; };
let init_ctxt = local.span.ctxt(); let init_ctxt = local.span.ctxt();
@ -216,21 +211,19 @@ impl<'tcx> LateLintPass<'tcx> for Default {
.join(", "); .join(", ");
// give correct suggestion if generics are involved (see #6944) // give correct suggestion if generics are involved (see #6944)
let binding_type = if_chain! { let binding_type = if let ty::Adt(adt_def, args) = binding_type.kind()
if let ty::Adt(adt_def, args) = binding_type.kind(); && !args.is_empty()
if !args.is_empty(); {
then { let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let adt_def_ty_name = cx.tcx.item_name(adt_def.did()); let generic_args = args.iter().collect::<Vec<_>>();
let generic_args = args.iter().collect::<Vec<_>>(); let tys_str = generic_args
let tys_str = generic_args .iter()
.iter() .map(ToString::to_string)
.map(ToString::to_string) .collect::<Vec<_>>()
.collect::<Vec<_>>() .join(", ");
.join(", "); format!("{adt_def_ty_name}::<{}>", &tys_str)
format!("{adt_def_ty_name}::<{}>", &tys_str) } else {
} else { binding_type.to_string()
binding_type.to_string()
}
}; };
let sugg = if ext_with_default { 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. /// 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 { 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::Call(fn_expr, _) = &expr.kind; && let ExprKind::Path(qpath) = &fn_expr.kind
if let ExprKind::Path(qpath) = &fn_expr.kind; && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id); {
then { // right hand side of assignment is `Default::default`
// right hand side of assignment is `Default::default` cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
cx.tcx.is_diagnostic_item(sym::default_fn, def_id) } else {
} else { false
false
}
} }
} }
/// Returns the reassigned field and the assigning expression (right-hand side of assign). /// 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>)> { 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 // only take assignments
if let StmtKind::Semi(later_expr) = this.kind; && let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind
if 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 // only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement // the same binding as the previous statement
if let ExprKind::Field(binding, field_ident) = assign_lhs.kind; && let ExprKind::Field(binding, field_ident) = assign_lhs.kind
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind; && let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind
if let Some(second_binding_name) = path.segments.last(); && let Some(second_binding_name) = path.segments.last()
if second_binding_name.ident.name == binding_name; && second_binding_name.ident.name == binding_name
then { {
Some((field_ident, assign_rhs)) Some((field_ident, assign_rhs))
} else { } else {
None None
}
} }
} }
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }` /// 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 { 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 Some(parent) = get_parent_expr(cx, expr); && let ExprKind::Struct(_, _, Some(base)) = parent.kind
if let ExprKind::Struct(_, _, Some(base)) = parent.kind; {
then { base.hir_id == expr.hir_id
base.hir_id == expr.hir_id } else {
} else { false
false
}
} }
} }

View file

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

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_node, numeric_literal}; use clippy_utils::{get_parent_node, numeric_literal};
use if_chain::if_chain;
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor}; 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. /// 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) { 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 !in_external_macro(self.cx.sess(), lit.span); && matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)))
if matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false))); && matches!(
if matches!(lit.node, lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
then { )
let (suffix, is_float) = match lit_ty.kind() { {
ty::Int(IntTy::I32) => ("i32", false), let (suffix, is_float) = match lit_ty.kind() {
ty::Float(FloatTy::F64) => ("f64", true), ty::Int(IntTy::I32) => ("i32", false),
// Default numeric fallback never results in other types. ty::Float(FloatTy::F64) => ("f64", true),
_ => return, // Default numeric fallback never results in other types.
}; _ => return,
};
let src = if let Some(src) = snippet_opt(self.cx, lit.span) { let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src src
} else { } else {
match lit.node { match lit.node {
LitKind::Int(src, _) => format!("{src}"), LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"), LitKind::Float(src, _) => format!("{src}"),
_ => return, _ => 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 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) => { ExprKind::Struct(_, fields, base) => {
let ty = self.cx.typeck_results().expr_ty(expr); let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! { if let Some(adt_def) = ty.ty_adt_def()
if let Some(adt_def) = ty.ty_adt_def(); && adt_def.is_struct()
if adt_def.is_struct(); && let Some(variant) = adt_def.variants().iter().next()
if let Some(variant) = adt_def.variants().iter().next(); {
then { let fields_def = &variant.fields;
let fields_def = &variant.fields;
// Push field type then visit each field expr. // Push field type then visit each field expr.
for field in *fields { for field in *fields {
let bound = let bound = fields_def.iter().find_map(|f_def| {
fields_def if f_def.ident(self.cx.tcx) == field.ident {
.iter() Some(self.cx.tcx.type_of(f_def.did).instantiate_identity())
.find_map(|f_def| { } else {
if f_def.ident(self.cx.tcx) == field.ident None
{ Some(self.cx.tcx.type_of(f_def.did).instantiate_identity()) } }
else { None } });
}); self.ty_bounds.push(bound.into());
self.ty_bounds.push(bound.into()); self.visit_expr(field.expr);
self.visit_expr(field.expr); self.ty_bounds.pop();
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;
} }
// 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::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; 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::{ use clippy_utils::{
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, 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_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -170,9 +171,7 @@ pub struct Dereferencing<'tcx> {
#[derive(Debug)] #[derive(Debug)]
struct StateData<'tcx> { struct StateData<'tcx> {
/// Span of the top level expression first_expr: &'tcx Expr<'tcx>,
span: Span,
hir_id: HirId,
adjusted_ty: Ty<'tcx>, adjusted_ty: Ty<'tcx>,
} }
@ -198,6 +197,7 @@ enum State {
}, },
ExplicitDerefField { ExplicitDerefField {
name: Symbol, name: Symbol,
derefs_manually_drop: bool,
}, },
Reborrow { Reborrow {
mutability: Mutability, mutability: Mutability,
@ -242,7 +242,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
// Stop processing sub expressions when a macro call is seen // Stop processing sub expressions when a macro call is seen
if expr.span.from_expansion() { if expr.span.from_expansion() {
if let Some((state, data)) = self.state.take() { if let Some((state, data)) = self.state.take() {
report(cx, expr, state, data); report(cx, expr, state, data, cx.typeck_results());
} }
return; 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 { let Some((kind, sub_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
// The whole chain of reference operations has been seen // The whole chain of reference operations has been seen
if let Some((state, data)) = self.state.take() { if let Some((state, data)) = self.state.take() {
report(cx, expr, state, data); report(cx, expr, state, data, typeck);
} }
return; return;
}; };
@ -272,14 +272,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
(Some(use_cx), RefOp::Deref) => { (Some(use_cx), RefOp::Deref) => {
let sub_ty = typeck.expr_ty(sub_expr); let sub_ty = typeck.expr_ty(sub_expr);
if let ExprUseNode::FieldAccess(name) = use_cx.node 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) && !ty_contains_field(sub_ty, name.name)
{ {
self.state = Some(( self.state = Some((
State::ExplicitDerefField { name: name.name }, State::ExplicitDerefField {
name: name.name,
derefs_manually_drop: is_manually_drop(sub_ty),
},
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -293,8 +295,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.state = Some(( self.state = Some((
State::ExplicitDeref { mutability: None }, State::ExplicitDeref { mutability: None },
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -313,8 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
mutbl, mutbl,
}, },
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -342,8 +342,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()) TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
}); });
let can_auto_borrow = match use_cx.node { let can_auto_borrow = match use_cx.node {
ExprUseNode::Callee => true, ExprUseNode::FieldAccess(_)
ExprUseNode::FieldAccess(_) => adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()), 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 => { ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
// Check for calls to trait methods where the trait is implemented // Check for calls to trait methods where the trait is implemented
// on a reference. // on a reference.
@ -357,11 +367,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
.tcx .tcx
.erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target)) .erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target))
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
&& let args = cx && let args =
.typeck_results() typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default()
.node_args_opt(hir_id)
.map(|args| &args[1..])
.unwrap_or_default()
&& let impl_ty = && let impl_ty =
if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder().inputs()[0] if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder().inputs()[0]
.is_ref() .is_ref()
@ -436,14 +443,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
count: deref_count - required_refs, count: deref_count - required_refs,
msg, msg,
stability, stability,
for_field_access: match use_cx.node { for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node
ExprUseNode::FieldAccess(name) => Some(name.name), && !use_cx.moved_before_use
_ => None, {
Some(name.name)
} else {
None
}, },
}), }),
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), 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(( self.state = Some((
State::Borrow { mutability }, State::Borrow { mutability },
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), 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)) => { (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => {
let adjusted_ty = data.adjusted_ty; let adjusted_ty = data.adjusted_ty;
let stability = state.stability; let stability = state.stability;
report(cx, expr, State::DerefedBorrow(state), data); report(cx, expr, State::DerefedBorrow(state), data, typeck);
if stability.is_deref_stable() { if stability.is_deref_stable() {
self.state = Some(( self.state = Some((
State::Borrow { mutability }, State::Borrow { mutability },
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -517,15 +524,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
let adjusted_ty = data.adjusted_ty; let adjusted_ty = data.adjusted_ty;
let stability = state.stability; let stability = state.stability;
let for_field_access = state.for_field_access; 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 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(( self.state = Some((
State::ExplicitDerefField { name }, State::ExplicitDerefField {
name,
derefs_manually_drop: is_manually_drop(sub_expr_ty),
},
StateData { StateData {
span: expr.span, first_expr: expr,
hir_id: expr.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -535,8 +545,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.state = Some(( self.state = Some((
State::ExplicitDeref { mutability: None }, State::ExplicitDeref { mutability: None },
StateData { StateData {
span: parent.span, first_expr: parent,
hir_id: parent.hir_id,
adjusted_ty, adjusted_ty,
}, },
)); ));
@ -566,13 +575,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => { (state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
self.state = state; 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; return;
} }
if_chain! { if !pat.span.from_expansion()
if !pat.span.from_expansion(); && let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind()
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
// only lint immutable refs, because borrowed `&mut T` cannot be moved out // only lint immutable refs, because borrowed `&mut T` cannot be moved out
if let ty::Ref(_, _, Mutability::Not) = *tam.kind(); && let ty::Ref(_, _, Mutability::Not) = *tam.kind()
then { {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0; 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.current_body = self.current_body.or(cx.enclosing_body);
self.ref_locals.insert( self.ref_locals.insert(
id, id,
Some(RefPat { Some(RefPat {
always_deref: true, always_deref: true,
spans: vec![pat.span], spans: vec![pat.span],
app, app,
replacements: vec![(pat.span, snip.into())], replacements: vec![(pat.span, snip.into())],
hir_id: pat.hir_id, 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 // Checks whether the type for a deref call actually changed the type, not just the mutability of
// the reference. // the reference.
fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { 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)] #[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 { match state {
State::DerefMethod { State::DerefMethod {
ty_changed_count, ty_changed_count,
@ -906,8 +942,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
mutbl, mutbl,
} => { } => {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); let (expr_str, _expr_is_macro_call) =
let ty = cx.typeck_results().expr_ty(expr); 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 (_, ref_count) = peel_mid_ty_refs(ty);
let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { 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 // 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( span_lint_and_sugg(
cx, cx,
EXPLICIT_DEREF_METHODS, EXPLICIT_DEREF_METHODS,
data.span, data.first_expr.span,
match mutbl { match mutbl {
Mutability::Not => "explicit `deref` method call", Mutability::Not => "explicit `deref` method call",
Mutability::Mut => "explicit `deref_mut` 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) => { State::DerefedBorrow(state) => {
let mut app = Applicability::MachineApplicable; 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) =
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| { snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.hir_id) { span_lint_hir_and_then(
Some(Node::Expr(e)) => match e.kind { cx,
ExprKind::Call(callee, _) if callee.hir_id != data.hir_id => (0, false), NEEDLESS_BORROW,
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))), data.first_expr.hir_id,
_ => (e.precedence().order(), false), data.first_expr.span,
}, state.msg,
_ => (0, false), |diag| {
}; let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) {
let sugg = if !snip_is_macro Some(Node::Expr(e)) => match e.kind {
&& (calls_field || expr.precedence().order() < precedence) ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
&& !has_enclosing_paren(&snip) ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
{ _ => (e.precedence().order(), false),
format!("({snip})") },
} else { _ => (0, false),
snip.into() };
}; let sugg = if !snip_is_macro
diag.span_suggestion(data.span, "change this to", sugg, app); && (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 } => { State::ExplicitDeref { mutability } => {
if matches!( 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 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 { let prefix = match mutability {
Mutability::Not => "&", Mutability::Not => "&",
@ -1009,53 +1054,61 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
EXPLICIT_AUTO_DEREF, EXPLICIT_AUTO_DEREF,
data.hir_id, data.first_expr.hir_id,
data.span, data.first_expr.span,
"deref which would be done by auto-deref", "deref which would be done by auto-deref",
|diag| { |diag| {
let mut app = Applicability::MachineApplicable; 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 = let sugg =
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) { if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
format!("{prefix}({snip})") format!("{prefix}({snip})")
} else { } else {
format!("{prefix}{snip}") format!("{prefix}{snip}")
}; };
diag.span_suggestion(data.span, "try", sugg, app); diag.span_suggestion(data.first_expr.span, "try", sugg, app);
}, },
); );
}, },
State::ExplicitDerefField { .. } => { State::ExplicitDerefField {
if matches!( derefs_manually_drop, ..
expr.kind, } => {
ExprKind::Block(..) let (snip_span, needs_parens) = if matches!(expr.kind, ExprKind::Field(..))
| ExprKind::ConstBlock(_) && (derefs_manually_drop
| ExprKind::If(..) || adjust_derefs_manually_drop(
| ExprKind::Loop(..) typeck.expr_adjustments(data.first_expr),
| ExprKind::Match(..) typeck.expr_ty(data.first_expr),
) && data.adjusted_ty.is_sized(cx.tcx, cx.param_env) )) {
{ // `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
// Rustc bug: auto deref doesn't work on block expression when targeting sized types. // field expressions when the base type is a union and the parent
return; // expression is also a field access.
} //
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
if let ExprKind::Field(parent_expr, _) = expr.kind // deref through `ManuallyDrop<_>` will not compile.
&& let ty::Adt(adt, _) = cx.typeck_results().expr_ty(parent_expr).kind() let parent_id = cx.tcx.hir().parent_id(expr.hir_id);
&& adt.is_union() if parent_id == data.first_expr.hir_id {
{ return;
// Auto deref does not apply on union field }
return; (cx.tcx.hir().get(parent_id).expect_expr().span, true)
} } else {
(expr.span, false)
};
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
EXPLICIT_AUTO_DEREF, EXPLICIT_AUTO_DEREF,
data.hir_id, data.first_expr.hir_id,
data.span, data.first_expr.span,
"deref which would be done by auto-deref", "deref which would be done by auto-deref",
|diag| { |diag| {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; let snip = snippet_with_context(cx, snip_span, data.first_expr.span.ctxt(), "..", &mut app).0;
diag.span_suggestion(data.span, "try", snip.into_owned(), app); 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<'_>) { 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 ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind; && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res; && let variant_id = cx.tcx.parent(id)
if let variant_id = cx.tcx.parent(id); && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id)
if let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id); && variant_def.fields.is_empty()
if variant_def.fields.is_empty(); && !variant_def.is_field_list_non_exhaustive()
if !variant_def.is_field_list_non_exhaustive(); {
let enum_span = cx.tcx.def_span(adt_def.did());
then { let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
let enum_span = cx.tcx.def_span(adt_def.did()); let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_enum = indent_of(cx, enum_span).unwrap_or(0); let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
let variant_span = cx.tcx.def_span(variant_def.def_id); span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| {
let indent_variant = indent_of(cx, variant_span).unwrap_or(0); diag.span_suggestion_hidden(
span_lint_and_then(
cx,
DERIVABLE_IMPLS,
item.span, item.span,
"this `impl` can be derived", "remove the manual implementation...",
|diag| { String::new(),
diag.span_suggestion_hidden( Applicability::MachineApplicable,
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
);
}
); );
} 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 { impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! { if let ItemKind::Impl(Impl {
if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref),
of_trait: Some(ref trait_ref), items: [child],
items: [child], self_ty,
self_ty, ..
.. }) = item.kind
}) = item.kind; && !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived); && !item.span.from_expansion()
if !item.span.from_expansion(); && let Some(def_id) = trait_ref.trait_def_id()
if let Some(def_id) = trait_ref.trait_def_id(); && cx.tcx.is_diagnostic_item(sym::Default, def_id)
if cx.tcx.is_diagnostic_item(sym::Default, def_id); && let impl_item_hir = child.id.hir_id()
if let impl_item_hir = child.id.hir_id(); && let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir); && let ImplItemKind::Fn(_, b) = &impl_item.kind
if let ImplItemKind::Fn(_, b) = &impl_item.kind; && let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
if 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()
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind(); && let attrs = cx.tcx.hir().attrs(item.hir_id())
if let attrs = cx.tcx.hir().attrs(item.hir_id()); && !attrs.iter().any(|attr| attr.doc_str().is_some())
if !attrs.iter().any(|attr| attr.doc_str().is_some()); && cx.tcx.hir().attrs(impl_item_hir).is_empty()
if cx.tcx.hir().attrs(impl_item_hir).is_empty(); {
if adt_def.is_struct() {
then { check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
if adt_def.is_struct() { } else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b)); check_enum(cx, item, func_expr, adt_def);
} 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::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::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{is_lint_allowed, match_def_path, paths}; use clippy_utils::{is_lint_allowed, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
@ -232,42 +231,37 @@ fn check_hash_peq<'tcx>(
ty: Ty<'tcx>, ty: Ty<'tcx>,
hash_is_automatically_derived: bool, hash_is_automatically_derived: bool,
) { ) {
if_chain! { if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); && let Some(def_id) = trait_ref.trait_def_id()
if let Some(def_id) = trait_ref.trait_def_id(); && cx.tcx.is_diagnostic_item(sym::Hash, def_id)
if cx.tcx.is_diagnostic_item(sym::Hash, def_id); {
then { // Look for the PartialEq implementations for `ty`
// Look for the PartialEq implementations for `ty` cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
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);
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
if !hash_is_automatically_derived || peq_is_automatically_derived { if !hash_is_automatically_derived || peq_is_automatically_derived {
return; 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` // Only care about `impl PartialEq<Foo> for Foo`
// For `impl PartialEq<B> for A, input_types is [A, B] // For `impl PartialEq<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty { if trait_ref.instantiate_identity().args.type_at(1) == ty {
span_lint_and_then( span_lint_and_then(
cx, cx,
DERIVED_HASH_WITH_MANUAL_EQ, DERIVED_HASH_WITH_MANUAL_EQ,
span, span,
"you are deriving `Hash` but have implemented `PartialEq` explicitly", "you are deriving `Hash` but have implemented `PartialEq` explicitly",
|diag| { |diag| {
if let Some(local_def_id) = impl_id.as_local() { 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); let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note( diag.span_note(cx.tcx.hir().span(hir_id), "`PartialEq` implemented here");
cx.tcx.hir().span(hir_id),
"`PartialEq` implemented here"
);
}
} }
); },
} );
}); }
} });
} }
} }
@ -279,49 +273,38 @@ fn check_ord_partial_ord<'tcx>(
ty: Ty<'tcx>, ty: Ty<'tcx>,
ord_is_automatically_derived: bool, ord_is_automatically_derived: bool,
) { ) {
if_chain! { if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
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()
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait(); && let Some(def_id) = &trait_ref.trait_def_id()
if let Some(def_id) = &trait_ref.trait_def_id(); && *def_id == ord_trait_def_id
if *def_id == ord_trait_def_id; {
then { // Look for the PartialOrd implementations for `ty`
// Look for the PartialOrd implementations for `ty` cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
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);
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 { if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return; 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` // Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B] // For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty { if trait_ref.instantiate_identity().args.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived { let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`" "you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else { } else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly" "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
}; };
span_lint_and_then( span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
cx, if let Some(local_def_id) = impl_id.as_local() {
DERIVE_ORD_XOR_PARTIAL_ORD, let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
span, diag.span_note(cx.tcx.hir().span(hir_id), "`PartialOrd` implemented here");
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 visitor.has_unsafe
} }
if_chain! { if let Some(trait_def_id) = trait_ref.trait_def_id()
if let Some(trait_def_id) = trait_ref.trait_def_id(); && match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE); && let ty::Adt(def, _) = ty.kind()
if let ty::Adt(def, _) = ty.kind(); && let Some(local_def_id) = def.did().as_local()
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)
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)
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id); && cx
if cx.tcx.inherent_impls(def.did()) .tcx
.inherent_impls(def.did())
.iter() .iter()
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
.any(|imp| has_unsafe(cx, imp)); .any(|imp| has_unsafe(cx, imp))
then { {
span_lint_and_help( span_lint_and_help(
cx, cx,
UNSAFE_DERIVE_DESERIALIZE, UNSAFE_DERIVE_DESERIALIZE,
item.span, item.span,
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
None, None,
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html" "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
); );
}
} }
} }
@ -431,12 +414,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
return; return;
} }
if_chain! { if let Some(header) = kind.header()
if let Some(header) = kind.header(); && header.unsafety == Unsafety::Unsafe
if header.unsafety == Unsafety::Unsafe; {
then { self.has_unsafe = true;
self.has_unsafe = true;
}
} }
walk_fn(self, kind, decl, body_id, id); 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. /// 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>) { 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 let ty::Adt(adt, args) = ty.kind(); && cx.tcx.visibility(adt.did()).is_public()
if cx.tcx.visibility(adt.did()).is_public(); && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq); && let Some(def_id) = trait_ref.trait_def_id()
if let Some(def_id) = trait_ref.trait_def_id(); && cx.tcx.is_diagnostic_item(sym::PartialEq, 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)
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 !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 all of our fields implement `Eq`, we can implement `Eq` too
if adt && adt
.all_fields() .all_fields()
.map(|f| f.ty(cx.tcx, args)) .map(|f| f.ty(cx.tcx, args))
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])); .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]))
then { {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
DERIVE_PARTIAL_EQ_WITHOUT_EQ, DERIVE_PARTIAL_EQ_WITHOUT_EQ,
span.ctxt().outer_expn_data().call_site, span.ctxt().outer_expn_data().call_site,
"you are deriving `PartialEq` and can implement `Eq`", "you are deriving `PartialEq` and can implement `Eq`",
"consider deriving `Eq` as well", "consider deriving `Eq` as well",
"PartialEq, Eq".to_string(), "PartialEq, Eq".to_string(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
) );
}
} }
} }

View file

@ -31,9 +31,9 @@ pub struct DisallowedNames {
} }
impl DisallowedNames { impl DisallowedNames {
pub fn new(disallow: FxHashSet<String>) -> Self { pub fn new(disallowed_names: &[String]) -> Self {
Self { Self {
disallow, disallow: disallowed_names.iter().cloned().collect(),
test_modules_deep: 0, 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::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty}; use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
use if_chain::if_chain;
use pulldown_cmark::Event::{ use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
}; };
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph}; use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options}; use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
use rustc_ast::ast::{Async, Attribute, Fn, FnRetTy, ItemKind}; 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::fx::FxHashSet;
use rustc_data_structures::sync::Lrc; use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::EmitterWriter; use rustc_errors::emitter::EmitterWriter;
@ -30,8 +31,8 @@ use rustc_resolve::rustdoc::{
use rustc_session::parse::ParseSess; use rustc_session::parse::ParseSess;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
use rustc_span::{sym, BytePos, FileName, Pos, Span};
use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::source_map::{FilePathMapping, SourceMap};
use rustc_span::{sym, BytePos, FileName, Pos, Span};
use std::ops::Range; use std::ops::Range;
use std::{io, thread}; use std::{io, thread};
use url::Url; use url::Url;
@ -261,6 +262,53 @@ declare_clippy_lint! {
"`pub fn` or `pub trait` with `# Safety` docs" "`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)] #[expect(clippy::module_name_repetitions)]
#[derive(Clone)] #[derive(Clone)]
pub struct DocMarkdown { pub struct DocMarkdown {
@ -269,9 +317,9 @@ pub struct DocMarkdown {
} }
impl DocMarkdown { impl DocMarkdown {
pub fn new(valid_idents: FxHashSet<String>) -> Self { pub fn new(valid_idents: &[String]) -> Self {
Self { Self {
valid_idents, valid_idents: valid_idents.iter().cloned().collect(),
in_trait_impl: false, in_trait_impl: false,
} }
} }
@ -285,6 +333,7 @@ impl_lint_pass!(DocMarkdown => [
MISSING_PANICS_DOC, MISSING_PANICS_DOC,
NEEDLESS_DOCTEST_MAIN, NEEDLESS_DOCTEST_MAIN,
UNNECESSARY_SAFETY_DOC, UNNECESSARY_SAFETY_DOC,
SUSPICIOUS_DOC_COMMENTS
]); ]);
impl<'tcx> LateLintPass<'tcx> for DocMarkdown { impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
@ -428,25 +477,21 @@ fn lint_for_missing_headers(
span, span,
"docs for function returning `Result` missing `# Errors` section", "docs for function returning `Result` missing `# Errors` section",
); );
} else { } else if let Some(body_id) = body_id
if_chain! { && let Some(future) = cx.tcx.lang_items().future_trait()
if let Some(body_id) = body_id; && let typeck = cx.tcx.typeck_body(body_id)
if let Some(future) = cx.tcx.lang_items().future_trait(); && let body = cx.tcx.hir().body(body_id)
let typeck = cx.tcx.typeck_body(body_id); && let ret_ty = typeck.expr_ty(body.value)
let body = cx.tcx.hir().body(body_id); && implements_trait(cx, ret_ty, future, &[])
let ret_ty = typeck.expr_ty(body.value); && let ty::Coroutine(_, subs, _) = ret_ty.kind()
if implements_trait(cx, ret_ty, future, &[]); && is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
if let ty::Coroutine(_, subs, _) = ret_ty.kind(); {
if is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result); span_lint(
then { cx,
span_lint( MISSING_ERRORS_DOC,
cx, span,
MISSING_ERRORS_DOC, "docs for function returning `Result` missing `# Errors` section",
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; return None;
} }
check_almost_inner_doc(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true); let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
let mut doc = String::new(); let mut doc = String::new();
for fragment in &fragments { 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"]; const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
#[allow(clippy::too_many_lines)] // Only a big match statement #[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, || { rustc_span::create_session_globals_then(edition, || {
let filename = FileName::anon_source_code(&code); let filename = FileName::anon_source_code(&code);
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = let fallback_bundle =
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); 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 sess = ParseSess::with_span_handler(handler, sm);
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) { 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::diagnostics::span_lint_and_sugg;
use clippy_utils::peel_blocks; use clippy_utils::peel_blocks;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node}; use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -36,31 +35,30 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
impl LateLintPass<'_> for EmptyDrop { impl LateLintPass<'_> for EmptyDrop {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! { if let ItemKind::Impl(Impl {
if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref),
of_trait: Some(ref trait_ref), items: [child],
items: [child], ..
.. }) = item.kind
}) = item.kind; && trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait()
if trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait(); && let impl_item_hir = child.id.hir_id()
if let impl_item_hir = child.id.hir_id(); && let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir); && let ImplItemKind::Fn(_, b) = &impl_item.kind
if let ImplItemKind::Fn(_, b) = &impl_item.kind; && let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b); && let func_expr = peel_blocks(func_expr)
let func_expr = peel_blocks(func_expr); && let ExprKind::Block(block, _) = func_expr.kind
if let ExprKind::Block(block, _) = func_expr.kind; && block.stmts.is_empty()
if block.stmts.is_empty() && block.expr.is_none(); && block.expr.is_none()
then { {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
EMPTY_DROP, EMPTY_DROP,
item.span, item.span,
"empty drop implementation", "empty drop implementation",
"try removing this impl", "try removing this impl",
String::new(), String::new(),
Applicability::MaybeIncorrect Applicability::MaybeIncorrect,
); );
}
} }
} }
} }

View file

@ -114,27 +114,23 @@ impl LateLintPass<'_> for EndianBytes {
return; return;
} }
if_chain! { if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind; && args.is_empty()
if args.is_empty(); && let ty = cx.typeck_results().expr_ty(receiver)
let ty = cx.typeck_results().expr_ty(receiver); && ty.is_primitive_ty()
if ty.is_primitive_ty(); && maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty)
if maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty); {
then { return;
return;
}
} }
if_chain! { if let ExprKind::Call(function, ..) = expr.kind
if let ExprKind::Call(function, ..) = expr.kind; && let ExprKind::Path(qpath) = function.kind
if let ExprKind::Path(qpath) = function.kind; && let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id()
if 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()
if let Some(function_name) = cx.get_def_path(def_id).last(); && let ty = cx.typeck_results().expr_ty(expr)
let ty = cx.typeck_results().expr_ty(expr); && ty.is_primitive_ty()
if ty.is_primitive_ty(); {
then { maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, 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_middle::ty::{self, TraitRef, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_span::symbol::kw; use rustc_span::symbol::kw;
use rustc_span::Span;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
#[derive(Copy, Clone)] #[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. /// 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 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 { fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
matches!(from_region.kind(), RegionKind::ReBound(..)) matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..))
&& !matches!(to_region.kind(), RegionKind::ReBound(..))
} }
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { 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::diagnostics::span_lint_and_then;
use clippy_utils::source::indent_of; use clippy_utils::source::indent_of;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -71,40 +70,31 @@ declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
impl LateLintPass<'_> for ExhaustiveItems { impl LateLintPass<'_> for ExhaustiveItems {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! { if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind; && cx.effective_visibilities.is_exported(item.owner_id.def_id)
if cx.effective_visibilities.is_exported(item.owner_id.def_id); && let attrs = cx.tcx.hir().attrs(item.hir_id())
let attrs = cx.tcx.hir().attrs(item.hir_id()); && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)); {
then { let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
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()) {
if v.fields().iter().any(|f| { // skip structs with private fields
!cx.tcx.visibility(f.def_id).is_public() return;
}) { }
// skip structs with private fields (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
return; } else {
} (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive") };
} else { let suggestion_span = item.span.shrink_to_lo();
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive") let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
}; span_lint_and_then(cx, lint, item.span, msg, |diag| {
let suggestion_span = item.span.shrink_to_lo(); let sugg = format!("#[non_exhaustive]\n{indent}");
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); diag.span_suggestion(
span_lint_and_then( suggestion_span,
cx, "try adding #[non_exhaustive]",
lint, sugg,
item.span, Applicability::MaybeIncorrect,
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::diagnostics::span_lint;
use clippy_utils::is_entrypoint_fn; use clippy_utils::is_entrypoint_fn;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -42,19 +41,17 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit { impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { 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::Call(path_expr, _args) = e.kind; && let ExprKind::Path(ref path) = path_expr.kind
if let ExprKind::Path(ref path) = path_expr.kind; && let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
if 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)
if cx.tcx.is_diagnostic_item(sym::process_exit, def_id); && let parent = cx.tcx.hir().get_parent_item(e.hir_id).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 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 // If the next item up is a function we check if it is an entry point
// and only then emit a linter warning // and only then emit a linter warning
if !is_entrypoint_fn(cx, parent.to_def_id()); && !is_entrypoint_fn(cx, parent.to_def_id())
then { {
span_lint(cx, EXIT, e.span, "usage of `process::exit`"); 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::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, path_def_id}; use clippy_utils::{is_expn_of, path_def_id};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}; 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 /// If `kind` is a block that looks like `{ let result = $expr; result }` then
/// returns $expr. Otherwise returns `kind`. /// returns $expr. Otherwise returns `kind`.
fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) -> &'tcx ExprKind<'hir> { 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 ExprKind::Block(block, _label @ None) = kind; && let Block {
if let Block {
stmts: [Stmt { kind: StmtKind::Local(local), .. }], stmts: [Stmt { kind: StmtKind::Local(local), .. }],
expr: Some(expr_end_of_block), expr: Some(expr_end_of_block),
rules: BlockCheckMode::DefaultBlock, rules: BlockCheckMode::DefaultBlock,
.. ..
} = block; } = block
// Find id of the local that expr_end_of_block resolves to // 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; && let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind
if let Res::Local(expr_res) = expr_path.res; && let Res::Local(expr_res) = expr_path.res
if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res); && let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res)
// Find id of the local we found in the block // 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 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; && let Some(init) = local.init
then { {
return &init.kind; return &init.kind;
}
} }
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::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::method_chain_args; use clippy_utils::method_chain_args;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty; use rustc_middle::ty;
@ -53,13 +52,13 @@ declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
// check for `impl From<???> for ..` // check for `impl From<???> for ..`
if_chain! { if let hir::ItemKind::Impl(impl_) = &item.kind
if let hir::ItemKind::Impl(impl_) = &item.kind; && let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id)
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id); && cx
if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id); .tcx
then { .is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id)
lint_impl_body(cx, item.span, impl_.items); {
} 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 { for impl_item in impl_items {
if_chain! { if impl_item.ident.name == sym::from
if impl_item.ident.name == sym::from; && let ImplItemKind::Fn(_, body_id) = cx.tcx.hir().impl_item(impl_item.id).kind
if let ImplItemKind::Fn(_, body_id) = {
cx.tcx.hir().impl_item(impl_item.id).kind; // check the body for `begin_panic` or `unwrap`
then { let body = cx.tcx.hir().body(body_id);
// check the body for `begin_panic` or `unwrap` let mut fpu = FindPanicUnwrap {
let body = cx.tcx.hir().body(body_id); lcx: cx,
let mut fpu = FindPanicUnwrap { typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
lcx: cx, result: Vec::new(),
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id), };
result: Vec::new(), fpu.visit_expr(body.value);
};
fpu.visit_expr(body.value);
// if we've found one, lint // if we've found one, lint
if !fpu.result.is_empty() { if !fpu.result.is_empty() {
span_lint_and_then( span_lint_and_then(
cx, cx,
FALLIBLE_IMPL_FROM, FALLIBLE_IMPL_FROM,
impl_span, impl_span,
"consider implementing `TryFrom` instead", "consider implementing `TryFrom` instead",
move |diag| { move |diag| {
diag.help( diag.help(
"`From` is intended for infallible conversions only. \ "`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail"); Use `TryFrom` if there's a possibility for the conversion to fail",
diag.span_note(fpu.result, "potential failure(s)"); );
}); 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::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal; use clippy_utils::numeric_literal;
use if_chain::if_chain;
use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
@ -64,73 +63,70 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral { impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if_chain! { if let ty::Float(fty) = *ty.kind()
if let ty::Float(fty) = *ty.kind(); && let hir::ExprKind::Lit(lit) = expr.kind
if let hir::ExprKind::Lit(lit) = expr.kind; && let LitKind::Float(sym, lit_float_ty) = lit.node
if let LitKind::Float(sym, lit_float_ty) = lit.node; {
then { let sym_str = sym.as_str();
let sym_str = sym.as_str(); let formatter = FloatFormat::new(sym_str);
let formatter = FloatFormat::new(sym_str); // Try to bail out if the float is for sure fine.
// Try to bail out if the float is for sure fine. // If its within the 2 decimal digits of being out of precision we
// If its within the 2 decimal digits of being out of precision we // check if the parsed representation is the same as the string
// check if the parsed representation is the same as the string // since we'll need the truncated string anyway.
// since we'll need the truncated string anyway. let digits = count_digits(sym_str);
let digits = count_digits(sym_str); let max = max_digits(fty);
let max = max_digits(fty); let type_suffix = match lit_float_ty {
let type_suffix = match lit_float_ty { LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"), LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"), LitFloatType::Unsuffixed => None,
LitFloatType::Unsuffixed => None };
}; let (is_whole, is_inf, mut float_str) = match fty {
let (is_whole, is_inf, mut float_str) = match fty { FloatTy::F32 => {
FloatTy::F32 => { let value = sym_str.parse::<f32>().unwrap();
let value = sym_str.parse::<f32>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value)) (value.fract() == 0.0, value.is_infinite(), formatter.format(value))
}, },
FloatTy::F64 => { FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap(); 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 { if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
return; // 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
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') { // incorrectly interpreted as an integer so adding a `.0`
// Normalize the literal by stripping the fractional portion // suffix to prevent that.
if sym_str.split('.').next().unwrap() != float_str { if type_suffix.is_none() {
// If the type suffix is missing the suggestion would be float_str.push_str(".0");
// 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,
);
} }
} else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL,
expr.span, expr.span,
"float has excessive precision", "literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or truncating it to", "consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true), numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable, 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::{ use clippy_utils::{
eq_expr_value, get_parent_expr, higher, in_constant, is_no_std_crate, numeric_literal, peel_blocks, sugg, 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_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass}; 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; 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 // if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous // add a suffix so the suggestion is valid and unambiguous
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind(); && let ExprKind::Lit(lit) = &expr.kind
if let ExprKind::Lit(lit) = &expr.kind; && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node; {
then { let op = format!(
let op = format!( "{suggestion}{}{}",
"{suggestion}{}{}", // Check for float literals without numbers following the decimal
// Check for float literals without numbers following the decimal // separator such as `2.` and adds a trailing zero
// separator such as `2.` and adds a trailing zero if sym.as_str().ends_with('.') { "0" } else { "" },
if sym.as_str().ends_with('.') { float_ty.name_str()
"0" )
} else { .into();
""
},
float_ty.name_str()
).into();
suggestion = match suggestion { suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op), Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op) _ => Sugg::NonParen(op),
}; };
}
} }
suggestion.maybe_par() suggestion.maybe_par()
@ -359,35 +353,59 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
) = receiver.kind ) = receiver.kind
{ {
// check if expression of the form x * x + y * y // check if expression of the form x * x + y * y
if_chain! { if let ExprKind::Binary(
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind; Spanned {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind; node: BinOpKind::Mul, ..
if eq_expr_value(cx, lmul_lhs, lmul_rhs); },
if eq_expr_value(cx, rmul_lhs, rmul_rhs); lmul_lhs,
then { lmul_rhs,
return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, ".."))); ) = 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) // check if expression of the form x.powi(2) + y.powi(2)
if_chain! { if let ExprKind::MethodCall(
if let ExprKind::MethodCall( PathSegment {
PathSegment { ident: lmethod_name, .. }, ident: lmethod_name, ..
largs_0, [largs_1, ..], },
_ largs_0,
) = &add_lhs.kind; [largs_1, ..],
if let ExprKind::MethodCall( _,
PathSegment { ident: rmethod_name, .. }, ) = &add_lhs.kind
rargs_0, [rargs_1, ..], && let ExprKind::MethodCall(
_ PathSegment {
) = &add_rhs.kind; ident: rmethod_name, ..
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; },
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1); rargs_0,
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1); [rargs_1, ..],
if Int(2) == lvalue && Int(2) == rvalue; _,
then { ) = &add_rhs.kind
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, ".."))); && 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 // TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead // and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if let ExprKind::Binary(
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind; Spanned {
if cx.typeck_results().expr_ty(lhs).is_floating_point(); node: BinOpKind::Sub, ..
if let Some(value) = constant(cx, cx.typeck_results(), rhs); },
if F32(1.0) == value || F64(1.0) == value; lhs,
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind; rhs,
if cx.typeck_results().expr_ty(self_arg).is_floating_point(); ) = expr.kind
if path.ident.name.as_str() == "exp"; && cx.typeck_results().expr_ty(lhs).is_floating_point()
then { && let Some(value) = constant(cx, cx.typeck_results(), rhs)
span_lint_and_sugg( && (F32(1.0) == value || F64(1.0) == value)
cx, && let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
IMPRECISE_FLOPS, && cx.typeck_results().expr_ty(self_arg).is_floating_point()
expr.span, && path.ident.name.as_str() == "exp"
"(e.pow(x) - 1) can be computed more accurately", {
"consider using", span_lint_and_sugg(
format!( cx,
"{}.exp_m1()", IMPRECISE_FLOPS,
Sugg::hir(cx, self_arg, "..").maybe_par() expr.span,
), "(e.pow(x) - 1) can be computed more accurately",
Applicability::MachineApplicable, "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>)> { 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(
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind; Spanned {
if cx.typeck_results().expr_ty(lhs).is_floating_point(); node: BinOpKind::Mul, ..
if cx.typeck_results().expr_ty(rhs).is_floating_point(); },
then { lhs,
return Some((lhs, rhs)); 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 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<'_>) { fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if let Some(higher::If {
if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr); cond,
let if_body_expr = peel_blocks(then); then,
let else_body_expr = peel_blocks(r#else); r#else: Some(r#else),
if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr); }) = higher::If::hir(expr)
then { && let if_body_expr = peel_blocks(then)
let positive_abs_sugg = ( && let else_body_expr = peel_blocks(r#else)
"manual implementation of `abs` method", && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), {
); let positive_abs_sugg = (
let negative_abs_sugg = ( "manual implementation of `abs` method",
"manual implementation of negation of `abs` method", format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), );
); let negative_abs_sugg = (
let sugg = if is_testing_positive(cx, cond, body) { "manual implementation of negation of `abs` method",
if if_expr_positive { format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
positive_abs_sugg );
} else { let sugg = if is_testing_positive(cx, cond, body) {
negative_abs_sugg if if_expr_positive {
} positive_abs_sugg
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
} else { } else {
return; negative_abs_sugg
}; }
span_lint_and_sugg( } else if is_testing_negative(cx, cond, body) {
cx, if if_expr_positive {
SUBOPTIMAL_FLOPS, negative_abs_sugg
expr.span, } else {
sugg.0, positive_abs_sugg
"try", }
sugg.1, } else {
Applicability::MachineApplicable, 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 { fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if_chain! { if let ExprKind::MethodCall(
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind; PathSegment {
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind; ident: method_name_a, ..
then { },
return method_name_a.as_str() == method_name_b.as_str() && _,
args_a.len() == args_b.len() && args_a,
( _,
["ln", "log2", "log10"].contains(&method_name_a.as_str()) || ) = expr_a.kind
method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]) && 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 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<'_>) { fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
// check if expression of the form x.logN() / y.logN() // check if expression of the form x.logN() / y.logN()
if_chain! { if let ExprKind::Binary(
if let ExprKind::Binary( Spanned {
Spanned { node: BinOpKind::Div, ..
node: BinOpKind::Div, .. },
}, lhs,
lhs, rhs,
rhs, ) = &expr.kind
) = &expr.kind; && are_same_base_logs(cx, lhs, rhs)
if are_same_base_logs(cx, lhs, rhs); && let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind
if let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind; && let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind
if let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind; {
then { span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, SUBOPTIMAL_FLOPS,
SUBOPTIMAL_FLOPS, expr.span,
expr.span, "log base can be expressed more clearly",
"log base can be expressed more clearly", "consider using",
"consider using", format!(
format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),), "{}.log({})",
Applicability::MachineApplicable, Sugg::hir(cx, largs_self, "..").maybe_par(),
); Sugg::hir(cx, rargs_self, ".."),
} ),
Applicability::MachineApplicable,
);
} }
} }
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if let ExprKind::Binary(
if let ExprKind::Binary( Spanned {
Spanned { node: BinOpKind::Div, ..
node: BinOpKind::Div, .. },
}, div_lhs,
div_lhs, div_rhs,
div_rhs, ) = &expr.kind
) = &expr.kind; && let ExprKind::Binary(
if let ExprKind::Binary(
Spanned { Spanned {
node: BinOpKind::Mul, .. node: BinOpKind::Mul, ..
}, },
mul_lhs, mul_lhs,
mul_rhs, mul_rhs,
) = &div_lhs.kind; ) = &div_lhs.kind
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs); && let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs)
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs); && let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs)
then { {
// TODO: also check for constant values near PI/180 or 180/PI // TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
(F32(180_f32) == lvalue || F64(180_f64) == lvalue) && (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 value.as_str().ends_with('.') {
if_chain! { proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
if let ExprKind::Lit(literal) = mul_lhs.kind; } else {
if let ast::LitKind::Float(ref value, float_type) = literal.node; proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
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, ".."));
}
}
} }
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::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item}; use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools; use itertools::Itertools;
use rustc_ast::{ use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, 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<'_>) { fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
if_chain! { if !value.span.from_expansion()
if !value.span.from_expansion(); && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
if let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind; && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
if 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)
if is_diag_trait_item(cx, method_def_id, sym::ToString); && let receiver_ty = cx.typeck_results().expr_ty(receiver)
let receiver_ty = cx.typeck_results().expr_ty(receiver); && let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display); && let (n_needed_derefs, target) =
let (n_needed_derefs, target) = count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter()); && implements_trait(cx, target, display_trait_id, &[])
if implements_trait(cx, target, display_trait_id, &[]); && let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait(); && let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); {
then { let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]); if n_needed_derefs == 0 && !needs_ref {
if n_needed_derefs == 0 && !needs_ref { span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, TO_STRING_IN_FORMAT_ARGS,
TO_STRING_IN_FORMAT_ARGS, to_string_span.with_lo(receiver.span.hi()),
to_string_span.with_lo(receiver.span.hi()), &format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
&format!( "remove this",
"`to_string` applied to a type that implements `Display` in `{name}!` args" String::new(),
), Applicability::MachineApplicable,
"remove this", );
String::new(), } else {
Applicability::MachineApplicable, span_lint_and_sugg(
); cx,
} else { TO_STRING_IN_FORMAT_ARGS,
span_lint_and_sugg( value.span,
cx, &format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
TO_STRING_IN_FORMAT_ARGS, "use this",
value.span, format!(
&format!( "{}{:*>n_needed_derefs$}{receiver_snippet}",
"`to_string` applied to a type that implements `Display` in `{name}!` args" if needs_ref { "&" } else { "" },
), ""
"use this", ),
format!( Applicability::MachineApplicable,
"{}{:*>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::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::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 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_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; 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<'_>) { 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 // 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() ? // 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 // Is the method a part of the ToString trait? (i.e. not to_string() implemented
// separately) // separately)
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); && 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); && is_diag_trait_item(cx, expr_def_id, sym::ToString)
// Is the method is called on self // Is the method is called on self
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind; && let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
if let [segment] = path.segments; && let [segment] = path.segments
if segment.ident.name == kw::SelfLower; && segment.ident.name == kw::SelfLower
then { {
span_lint( span_lint(
cx, cx,
RECURSIVE_FORMAT_IMPL, RECURSIVE_FORMAT_IMPL,
expr.span, expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", "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) { 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(macro_call) = root_macro_call_first_node(cx, expr); && let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id); {
then { let replacement = match name {
let replacement = match name { sym::print_macro | sym::eprint_macro => "write",
sym::print_macro | sym::eprint_macro => "write", sym::println_macro | sym::eprintln_macro => "writeln",
sym::println_macro | sym::eprintln_macro => "writeln", _ => return,
_ => return, };
};
let name = name.as_str().strip_suffix("_macro").unwrap(); let name = name.as_str().strip_suffix("_macro").unwrap();
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
PRINT_IN_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL,
macro_call.span, macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name), &format!("use of `{name}!` in `{}` impl", impl_trait.name),
"replace with", "replace with",
if let Some(formatter_name) = impl_trait.formatter_name { if let Some(formatter_name) = impl_trait.formatter_name {
format!("{replacement}!({formatter_name}, ..)") format!("{replacement}!({formatter_name}, ..)")
} else { } else {
format!("{replacement}!(..)") format!("{replacement}!(..)")
}, },
Applicability::HasPlaceholders, Applicability::HasPlaceholders,
); );
}
} }
} }
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> { fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if_chain! { if impl_item.ident.name == sym::fmt
if impl_item.ident.name == sym::fmt; && let ImplItemKind::Fn(_, body_id) = impl_item.kind
if let ImplItemKind::Fn(_, body_id) = impl_item.kind; && let Some(Impl {
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id()); of_trait: Some(trait_ref),
if let Some(did) = trait_ref.trait_def_id(); ..
if let Some(name) = cx.tcx.get_diagnostic_name(did); }) = get_parent_as_impl(cx.tcx, impl_item.hir_id())
if matches!(name, sym::Debug | sym::Display); && let Some(did) = trait_ref.trait_def_id()
then { && let Some(name) = cx.tcx.get_diagnostic_name(did)
let body = cx.tcx.hir().body(body_id); && matches!(name, sym::Debug | sym::Display)
let formatter_name = body.params.get(1) {
.and_then(|param| param.pat.simple_ident()) let body = cx.tcx.hir().body(body_id);
.map(|ident| ident.name); let formatter_name = body
.params
.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
Some(FormatTraitNames { Some(FormatTraitNames { name, formatter_name })
name, } else {
formatter_name, None
})
} else {
None
}
} }
} }

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
use clippy_utils::is_span_if; use clippy_utils::is_span_if;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro; 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. /// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) { fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! { if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind; && !lhs.span.from_expansion() && !rhs.span.from_expansion()
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
// span between BinOp LHS and RHS // 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 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 // from UnOp operator to UnOp operand
let unop_operand_span = rhs.span.until(un_rhs.span); && let unop_operand_span = rhs.span.until(un_rhs.span)
if let Some(binop_snippet) = snippet_opt(cx, binop_span); && let Some(binop_snippet) = snippet_opt(cx, binop_span)
if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span); && let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span)
let binop_str = BinOpKind::to_string(&binop.node); && let binop_str = BinOpKind::to_string(&binop.node)
// no space after BinOp operator and space after UnOp operator // no space after BinOp operator and space after UnOp operator
if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' '); && binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ')
then { {
let unop_str = UnOp::to_string(op); let unop_str = UnOp::to_string(op);
let eqop_span = lhs.span.between(un_rhs.span); let eqop_span = lhs.span.between(un_rhs.span);
span_lint_and_help( span_lint_and_help(
cx, cx,
SUSPICIOUS_UNARY_OP_FORMATTING, SUSPICIOUS_UNARY_OP_FORMATTING,
eqop_span, eqop_span,
&format!( &format!(
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \ "by not having a space between `{binop_str}` and `{unop_str}` it looks like \
`{binop_str}{unop_str}` is a single operator" `{binop_str}{unop_str}` is a single operator"
), ),
None, None,
&format!( &format!("put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"),
"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`. /// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
fn check_else(cx: &EarlyContext<'_>, expr: &Expr) { fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! { if let ExprKind::If(_, then, Some(else_)) = &expr.kind
if let ExprKind::If(_, then, Some(else_)) = &expr.kind; && (is_block(else_) || is_if(else_))
if is_block(else_) || is_if(else_); && !then.span.from_expansion() && !else_.span.from_expansion()
if !then.span.from_expansion() && !else_.span.from_expansion(); && !in_external_macro(cx.sess(), expr.span)
if !in_external_macro(cx.sess(), expr.span);
// workaround for rust-lang/rust#43081 // 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 // this will be a span from the closing } of the “then” block (excluding) to
// the “if” of the “else if” block (excluding) // 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 // the snippet should look like " else \n " with maybe comments anywhere
// its bad when there is a \n after the “else” // its bad when there is a \n after the “else”
if let Some(else_snippet) = snippet_opt(cx, else_span); && let Some(else_snippet) = snippet_opt(cx, else_span)
if let Some((pre_else, post_else)) = else_snippet.split_once("else"); && let Some((pre_else, post_else)) = else_snippet.split_once("else")
if let Some((_, post_else_post_eol)) = post_else.split_once('\n'); && let Some((_, post_else_post_eol)) = post_else.split_once('\n')
{
then { // Allow allman style braces `} \n else \n {`
// Allow allman style braces `} \n else \n {` if is_block(else_)
if_chain! { && let Some((_, pre_else_post_eol)) = pre_else.split_once('\n')
if is_block(else_); // Exactly one eol before and after the else
if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n'); && !pre_else_post_eol.contains('\n')
// Exactly one eol before and after the else && !post_else_post_eol.contains('\n')
if !pre_else_post_eol.contains('\n'); {
if !post_else_post_eol.contains('\n'); return;
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}`",
),
);
} }
// 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) { fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Array(ref array) = expr.kind { if let ExprKind::Array(ref array) = expr.kind {
for element in array { for element in array {
if_chain! { if let ExprKind::Binary(ref op, ref lhs, _) = element.kind
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind; && has_unary_equivalent(op.node)
if has_unary_equivalent(op.node) && lhs.span.eq_ctxt(op.span); && lhs.span.eq_ctxt(op.span)
let space_span = lhs.span.between(op.span); && let space_span = lhs.span.between(op.span)
if let Some(space_snippet) = snippet_opt(cx, space_span); && let Some(space_snippet) = snippet_opt(cx, space_span)
let lint_span = lhs.span.with_lo(lhs.span.hi()); && let lint_span = lhs.span.with_lo(lhs.span.hi())
if space_snippet.contains('\n'); && space_snippet.contains('\n')
if indentation(cx, op.span) <= indentation(cx, lhs.span); && indentation(cx, op.span) <= indentation(cx, lhs.span)
then { {
span_lint_and_note( span_lint_and_note(
cx, cx,
POSSIBLE_MISSING_COMMA, POSSIBLE_MISSING_COMMA,
lint_span, lint_span,
"possibly missing a comma here", "possibly missing a comma here",
None, None,
"to remove this lint, add a comma or write the expr in a single line", "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) { fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
if_chain! { if !first.span.from_expansion() && !second.span.from_expansion()
if !first.span.from_expansion() && !second.span.from_expansion(); && matches!(first.kind, ExprKind::If(..))
if matches!(first.kind, ExprKind::If(..)); && (is_block(second) || is_if(second))
if is_block(second) || is_if(second);
// Proc-macros can give weird spans. Make sure this is actually an `if`. // 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 line break between the two expressions, don't lint.
// If there is a non-whitespace character, this span came from a proc-macro. // If there is a non-whitespace character, this span came from a proc-macro.
let else_span = first.span.between(second.span); && let else_span = first.span.between(second.span)
if let Some(else_snippet) = snippet_opt(cx, else_span); && let Some(else_snippet) = snippet_opt(cx, else_span)
if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace()); && !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace())
then { {
let (looks_like, next_thing) = if is_if(second) { let (looks_like, next_thing) = if is_if(second) {
("an `else if`", "the second `if`") ("an `else if`", "the second `if`")
} else { } else {
("an `else {..}`", "the next block") ("an `else {..}`", "the next block")
}; };
span_lint_and_note( span_lint_and_note(
cx, cx,
SUSPICIOUS_ELSE_FORMATTING, SUSPICIOUS_ELSE_FORMATTING,
else_span, else_span,
&format!("this looks like {looks_like} but the `else` is missing"), &format!("this looks like {looks_like} but the `else` is missing"),
None, None,
&format!( &format!("to remove this lint, add the missing `else` or add a new line before {next_thing}",),
"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::is_integer_literal;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind}; use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -46,52 +45,41 @@ declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) { 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::Call(maybe_path, [src, radix]) = &exp.kind; && let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
// check if the first part of the path is some integer primitive // check if the first part of the path is some integer primitive
if let TyKind::Path(ty_qpath) = &ty.kind; && let TyKind::Path(ty_qpath) = &ty.kind
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id); && let ty_res = cx.qpath_res(ty_qpath, ty.hir_id)
if let def::Res::PrimTy(prim_ty) = ty_res; && let def::Res::PrimTy(prim_ty) = ty_res
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)); && matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_))
// check if the second part of the path indeed calls the associated // check if the second part of the path indeed calls the associated
// function `from_str_radix` // 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` // 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 sugg =
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind { Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
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( span_lint_and_sugg(
cx, cx,
expr, FROM_STR_RADIX_10,
"<string>", exp.span,
&mut Applicability::MachineApplicable "this call to `from_str_radix` can be replaced with a call to `str::parse`",
).maybe_par(); "try",
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
span_lint_and_sugg( Applicability::MaybeIncorrect,
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::intravisit::FnKind;
use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind}; use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::symbol::Ident;
use rustc_span::{BytePos, Span};
use super::IMPL_TRAIT_IN_PARAMS; use super::IMPL_TRAIT_IN_PARAMS;
fn report( fn report(cx: &LateContext<'_>, param: &GenericParam<'_>, generics: &Generics<'_>) {
cx: &LateContext<'_>,
param: &GenericParam<'_>,
ident: &Ident,
generics: &Generics<'_>,
first_param_span: Span,
) {
// No generics with nested generics, and no generics like FnMut(x) // No generics with nested generics, and no generics like FnMut(x)
span_lint_and_then( span_lint_and_then(
cx, cx,
@ -35,12 +27,7 @@ fn report(
); );
} else { } else {
diag.span_suggestion_with_style( diag.span_suggestion_with_style(
Span::new( generics.span,
first_param_span.lo() - rustc_span::BytePos(1),
ident.span.hi(),
ident.span.ctxt(),
ident.span.parent(),
),
"add a type parameter", "add a type parameter",
format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]), format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders, 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) { pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
if_chain! { if let FnKind::ItemFn(_, generics, _) = kind
if let FnKind::ItemFn(ident, generics, _) = kind; && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public(); && !is_in_test_function(cx.tcx, hir_id)
if !is_in_test_function(cx.tcx, hir_id); {
then { for param in generics.params {
for param in generics.params { if param.is_impl_trait() {
if param.is_impl_trait() { report(cx, param, generics);
report(cx, param, ident, generics, body.params[0].span); };
};
}
} }
} }
} }
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
if_chain! { if let ImplItemKind::Fn(_, body_id) = impl_item.kind
if let ImplItemKind::Fn(_, body_id) = impl_item.kind; && let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id())
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id()); && let hir::ItemKind::Impl(impl_) = item.kind
if let hir::ItemKind::Impl(impl_) = item.kind; && let hir::Impl { of_trait, .. } = *impl_
if let hir::Impl { of_trait, .. } = *impl_; && of_trait.is_none()
if of_trait.is_none(); && let body = cx.tcx.hir().body(body_id)
let body = cx.tcx.hir().body(body_id); && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public(); && !is_in_test_function(cx.tcx, impl_item.hir_id())
if !is_in_test_function(cx.tcx, impl_item.hir_id()); {
then { for param in impl_item.generics.params {
for param in impl_item.generics.params { if param.is_impl_trait() {
if param.is_impl_trait() { report(cx, param, impl_item.generics);
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
}
} }
} }
} }
} }
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) { pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
if_chain! { if !avoid_breaking_exported_api
if !avoid_breaking_exported_api; && let TraitItemKind::Fn(_, _) = trait_item.kind
if let TraitItemKind::Fn(_, _) = trait_item.kind; && let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id())
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
// ^^ (Will always be a trait) // ^^ (Will always be a trait)
if !item.vis_span.is_empty(); // Is public && !item.vis_span.is_empty() // Is public
if !is_in_test_function(cx.tcx, trait_item.hir_id()); && !is_in_test_function(cx.tcx, trait_item.hir_id())
then { {
for param in trait_item.generics.params { for param in trait_item.generics.params {
if param.is_impl_trait() { 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.generics);
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
}
} }
} }
} }

View file

@ -43,15 +43,13 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
// Body must be &(mut) <self_data>.name // Body must be &(mut) <self_data>.name
// self_data is not necessarily self, to also lint sub-getters, etc… // self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if_chain! { let block_expr = if let ExprKind::Block(block, _) = body.value.kind
if let ExprKind::Block(block,_) = body.value.kind; && block.stmts.is_empty()
if block.stmts.is_empty(); && let Some(block_expr) = block.expr
if let Some(block_expr) = block.expr; {
then { block_expr
block_expr } else {
} else { return;
return;
}
}; };
let expr_span = block_expr.span; let expr_span = block_expr.span;
@ -61,14 +59,12 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
} else { } else {
block_expr block_expr
}; };
let (self_data, used_ident) = if_chain! { let (self_data, used_ident) = if let ExprKind::Field(self_data, ident) = expr.kind
if let ExprKind::Field(self_data, ident) = expr.kind; && ident.name.as_str() != name
if ident.name.as_str() != name; {
then { (self_data, ident)
(self_data, ident) } else {
} else { return;
return;
}
}; };
let mut used_field = None; 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) { 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 Adt(adt, subst) = err_ty.kind(); && let Some(local_def_id) = err_ty
if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local(); .ty_adt_def()
if let Some(hir::Node::Item(item)) = cx .expect("already checked this is adt")
.tcx .did()
.hir() .as_local()
.find_by_def_id(local_def_id); && 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; && let hir::ItemKind::Enum(ref def, _) = item.kind
then { {
let variants_size = AdtVariantInfo::new(cx, *adt, subst); let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if let Some((first_variant, variants)) = variants_size.split_first() if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold && first_variant.size >= large_err_threshold
{ {
span_lint_and_then( span_lint_and_then(
cx, cx,
RESULT_LARGE_ERR, RESULT_LARGE_ERR,
hir_ty_span, hir_ty_span,
"the `Err`-variant returned from this function is very large", "the `Err`-variant returned from this function is very large",
|diag| { |diag| {
diag.span_label( diag.span_label(
def.variants[first_variant.ind].span, def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size), format!("the largest variant contains at least {} bytes", variants_size[0].size),
); );
for variant in variants { for variant in variants {
if variant.size >= large_err_threshold { if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind]; let variant_def = &def.variants[variant.ind];
diag.span_label( diag.span_label(
variant_def.span, variant_def.span,
format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size), 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 { } else {
let ty_size = approx_ty_size(cx, err_ty); let ty_size = approx_ty_size(cx, err_ty);
if ty_size >= large_err_threshold { if ty_size >= large_err_threshold {
span_lint_and_then( span_lint_and_then(
cx, cx,
RESULT_LARGE_ERR, RESULT_LARGE_ERR,
hir_ty_span, hir_ty_span,
"the `Err`-variant returned from this function is very large", "the `Err`-variant returned from this function is very large",
|diag: &mut Diagnostic| { |diag: &mut Diagnostic| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes")); 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}>`")); 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::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, SpanlessEq}; use clippy_utils::{higher, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Diagnostic; use rustc_errors::Diagnostic;
use rustc_hir::intravisit::{self as visit, Visitor}; use rustc_hir::intravisit::{self as visit, Visitor};
use rustc_hir::{Expr, ExprKind}; 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>> { 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 let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind; && path.ident.as_str() == "lock"
if path.ident.as_str() == "lock"; && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); && is_type_diagnostic_item(cx, ty, sym::Mutex)
if is_type_diagnostic_item(cx, ty, sym::Mutex); {
then { Some(self_arg)
Some(self_arg) } else {
} else { None
None
}
} }
} }

View file

@ -10,10 +10,8 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{Ty, TypeckResults}; use rustc_middle::ty::{Ty, TypeckResults};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::Span;
use if_chain::if_chain;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt}; 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<'_>) { fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if_chain! { if let ExprKind::Call(fun, args) = e.kind
if let ExprKind::Call(fun, args) = e.kind; && let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind; && let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; && let Some(ty_did) = ty_path.res.opt_def_id()
if let Some(ty_did) = ty_path.res.opt_def_id(); {
then { if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) { return;
return; }
}
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) { if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new { if method.ident.name == sym::new {
self.suggestions self.suggestions.insert(e.span, "HashMap::default()".to_string());
.insert(e.span, "HashMap::default()".to_string()); } else if method.ident.name == sym!(with_capacity) {
} else if method.ident.name == sym!(with_capacity) { self.suggestions.insert(
self.suggestions.insert( e.span,
e.span, format!(
format!( "HashMap::with_capacity_and_hasher({}, Default::default())",
"HashMap::with_capacity_and_hasher({}, Default::default())", snippet(self.cx, args[0].span, "capacity"),
snippet(self.cx, args[0].span, "capacity"), ),
), );
); }
} } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) { if method.ident.name == sym::new {
if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashSet::default()".to_string());
self.suggestions } else if method.ident.name == sym!(with_capacity) {
.insert(e.span, "HashSet::default()".to_string()); self.suggestions.insert(
} else if method.ident.name == sym!(with_capacity) { e.span,
self.suggestions.insert( format!(
e.span, "HashSet::with_capacity_and_hasher({}, Default::default())",
format!( snippet(self.cx, args[0].span, "capacity"),
"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::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr; use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind}; use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind}; 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 { impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { 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::If(cond, then, None) = expr.kind; && let ExprKind::DropTemps(expr1) = cond.kind
if let ExprKind::DropTemps(expr1) = cond.kind; && let Some((c, op_node, l)) = get_const(cx, expr1)
if let Some((c, op_node, l)) = get_const(cx, expr1); && let BinOpKind::Ne | BinOpKind::Lt = op_node
if let BinOpKind::Ne | BinOpKind::Lt = op_node; && let ExprKind::Block(block, None) = then.kind
if let ExprKind::Block(block, None) = then.kind; && let Block {
if let Block {
stmts: stmts:
[Stmt [
{ kind: StmtKind::Expr(ex) | StmtKind::Semi(ex), .. }], Stmt {
expr: None, ..} | kind: StmtKind::Expr(ex) | StmtKind::Semi(ex),
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); expr: None,
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);
} }
| 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::diagnostics::span_lint_and_sugg;
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq}; use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
@ -46,83 +45,76 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if expr.span.from_expansion() { if expr.span.from_expansion() {
return; 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 // 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 < // 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 // 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 // Extracting out the variable name
if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind; && let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
then { // Handle symmetric conditions in the if statement
// Handle symmetric conditions in the if statement let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
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 {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node { (cond_left, cond_right)
(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;
}
} else { } else {
return; return;
}; }
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
// Check if the variable in the condition statement is an integer if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
if !cx.typeck_results().expr_ty(cond_var).is_integral() { (cond_right, cond_left)
} else {
return; return;
} }
} else {
return;
};
// Get the variable name // Check if the variable in the condition statement is an integer
let var_name = ares_path.segments[0].ident.name.as_str(); if !cx.typeck_results().expr_ty(cond_var).is_integral() {
match cond_num_val.kind { return;
ExprKind::Lit(cond_lit) => { }
// Check if the constant is zero
if let LitKind::Int(0, _) = cond_lit.node { // Get the variable name
if cx.typeck_results().expr_ty(cond_left).is_signed() { let var_name = ares_path.segments[0].ident.name.as_str();
} else { match cond_num_val.kind {
print_lint_and_sugg(cx, var_name, expr); 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() {
ExprKind::Path(QPath::TypeRelative(_, name)) => { } else {
if_chain! { print_lint_and_sugg(cx, var_name, expr);
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 ExprKind::Path(QPath::TypeRelative(_, name)) => {
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral(); if name.ident.as_str() == "MIN"
then { && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
print_lint_and_sugg(cx, var_name, expr) && 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()
}, {
ExprKind::Call(func, []) => { print_lint_and_sugg(cx, var_name, expr);
if_chain! { }
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind; },
if name.ident.as_str() == "min_value"; ExprKind::Call(func, []) => {
if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id); if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
if let Some(impl_id) = cx.tcx.impl_of_method(func_id); && name.ident.as_str() == "min_value"
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral(); && let Some(impl_id) = cx.tcx.impl_of_method(func_id)
then { && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
print_lint_and_sugg(cx, var_name, expr) && 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) (BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
}, },
ExprKind::Assign(target, value, _) => { ExprKind::Assign(target, value, _) => {
if_chain! { if let ExprKind::Binary(ref op1, left1, right1) = value.kind
if let ExprKind::Binary(ref op1, left1, right1) = value.kind; && BinOpKind::Sub == op1.node
if BinOpKind::Sub == op1.node; && SpanlessEq::new(cx).eq_expr(left1, target)
&& is_integer_literal(right1, 1)
if SpanlessEq::new(cx).eq_expr(left1, target); {
Some(target)
if is_integer_literal(right1, 1); } else {
then { None
Some(target)
} else {
None
}
} }
}, },
_ => None, _ => None,

View file

@ -43,7 +43,7 @@ declare_clippy_lint! {
/// Box::new(123) /// Box::new(123)
/// } /// }
/// ``` /// ```
#[clippy::version = "1.73.0"] #[clippy::version = "1.74.0"]
pub IMPLIED_BOUNDS_IN_IMPLS, pub IMPLIED_BOUNDS_IN_IMPLS,
nursery, nursery,
"specifying bounds that are implied by other bounds in `impl Trait` type" "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::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind}; use rustc_hir::{self as hir, ExprKind};
@ -66,54 +65,53 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! { if !expr.span.from_expansion()
if !expr.span.from_expansion(); && let ExprKind::Struct(qpath, fields, base) = expr.kind
if let ExprKind::Struct(qpath, fields, base) = expr.kind; && let ty = cx.typeck_results().expr_ty(expr)
let ty = cx.typeck_results().expr_ty(expr); && let Some(adt_def) = ty.ty_adt_def()
if let Some(adt_def) = ty.ty_adt_def(); && adt_def.is_struct()
if adt_def.is_struct(); && let Some(variant) = adt_def.variants().iter().next()
if let Some(variant) = adt_def.variants().iter().next(); && fields.iter().all(|f| f.is_shorthand)
if fields.iter().all(|f| f.is_shorthand); {
then { let mut def_order_map = FxHashMap::default();
let mut def_order_map = FxHashMap::default(); for (idx, field) in variant.fields.iter().enumerate() {
for (idx, field) in variant.fields.iter().enumerate() { def_order_map.insert(field.name, idx);
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 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::higher::IfLet;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local}; 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_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
@ -70,20 +69,17 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice { impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { 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 !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some(); && let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr); && !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id); && self.msrv.meets(msrvs::SLICE_PATTERNS)
if self.msrv.meets(msrvs::SLICE_PATTERNS); && let found_slices = find_slice_values(cx, let_pat)
&& !found_slices.is_empty()
let found_slices = find_slice_values(cx, let_pat); && let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then)
if !found_slices.is_empty(); && !filtered_slices.is_empty()
let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then); {
if !filtered_slices.is_empty(); for slice in filtered_slices.values() {
then { lint_slice(cx, slice);
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, max_suggested_slice,
} = *self; } = *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 // 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 // Checking for slice indexing
let parent_id = map.parent_id(expr.hir_id); && let parent_id = map.parent_id(expr.hir_id)
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id); && let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id)
if let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind; && let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr); && let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr)
if let Ok(index_value) = index_value.try_into(); && let Ok(index_value) = index_value.try_into()
if index_value < max_suggested_slice; && index_value < max_suggested_slice
// Make sure that this slice index is read only // Make sure that this slice index is read only
let maybe_addrof_id = map.parent_id(parent_id); && let maybe_addrof_id = map.parent_id(parent_id)
if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id); && 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; && 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))); use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
return; return;
}
} }
// The slice was used for something other than indexing // The slice was used for something other than indexing

View file

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

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_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use rustc_span::Span;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### 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 clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind}; use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -47,43 +46,40 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! { if !item.span.from_expansion()
if !item.span.from_expansion(); && let ItemKind::Const(_, generics, _) = &item.kind
if let ItemKind::Const(_, generics, _) = &item.kind;
// Since static items may not have generics, skip generic const items. // 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 // 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. // doesn't account for empty where-clauses that only consist of keyword `where` IINM.
if generics.params.is_empty() && !generics.has_where_clause_predicates; && generics.params.is_empty() && !generics.has_where_clause_predicates
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
if let ty::Array(element_type, cst) = ty.kind(); && let ty::Array(element_type, cst) = ty.kind()
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind(); && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx); && 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()); && 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); && 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 hi_pos = item.ident.span.lo() - BytePos::from_usize(1); let sugg_span = Span::new(
let sugg_span = Span::new( hi_pos - BytePos::from_usize("const".len()),
hi_pos - BytePos::from_usize("const".len()), hi_pos,
hi_pos, item.span.ctxt(),
item.span.ctxt(), item.span.parent(),
item.span.parent(), );
); span_lint_and_then(
span_lint_and_then( cx,
cx, LARGE_CONST_ARRAYS,
LARGE_CONST_ARRAYS, item.span,
item.span, "large array defined as const",
"large array defined as const", |diag| {
|diag| { diag.span_suggestion(
diag.span_suggestion( sugg_span,
sugg_span, "make this a static item",
"make this a static item", "static",
"static", Applicability::MachineApplicable,
Applicability::MachineApplicable, );
); },
} );
);
}
} }
} }
} }

View file

@ -50,37 +50,35 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
impl LateLintPass<'_> for LargeIncludeFile { impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if_chain! { if let Some(macro_call) = root_macro_call_first_node(cx, expr)
if let Some(macro_call) = root_macro_call_first_node(cx, expr); && !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id)
if !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id); && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_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))
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id); && let ExprKind::Lit(lit) = &expr.kind
if let ExprKind::Lit(lit) = &expr.kind; {
then { let len = match &lit.node {
let len = match &lit.node { // include_bytes
// include_bytes LitKind::ByteStr(bstr, _) => bstr.len(),
LitKind::ByteStr(bstr, _) => bstr.len(), // include_str
// include_str LitKind::Str(sym, _) => sym.as_str().len(),
LitKind::Str(sym, _) => sym.as_str().len(), _ => return,
_ => return, };
};
if len as u64 <= self.max_file_size { if len as u64 <= self.max_file_size {
return; 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
),
);
} }
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::source::snippet_with_context;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators}; 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_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
@ -15,9 +14,9 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{Span, Symbol};
use rustc_span::source_map::Spanned; use rustc_span::source_map::Spanned;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### 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<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if_chain! { if item.ident.name == sym::len
if item.ident.name == sym::len; && let ImplItemKind::Fn(sig, _) = &item.kind
if let ImplItemKind::Fn(sig, _) = &item.kind; && sig.decl.implicit_self.has_implicit_self()
if sig.decl.implicit_self.has_implicit_self(); && sig.decl.inputs.len() == 1
if sig.decl.inputs.len() == 1; && cx.effective_visibilities.is_exported(item.owner_id.def_id)
if cx.effective_visibilities.is_exported(item.owner_id.def_id); && matches!(sig.decl.output, FnRetTy::Return(_))
if matches!(sig.decl.output, FnRetTy::Return(_)); && let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()); && imp.of_trait.is_none()
if imp.of_trait.is_none(); && let TyKind::Path(ty_path) = &imp.self_ty.kind
if 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()
if 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()
if let Some(local_id) = ty_id.as_local(); && let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id)
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)
if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); && let Some(output) =
if let Some(output) = parse_len_output( parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
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"),
then { Some(Node::Item(x)) => match x.kind {
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { ItemKind::Struct(..) => (x.ident.name, "struct"),
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), ItemKind::Enum(..) => (x.ident.name, "enum"),
Some(Node::Item(x)) => match x.kind { ItemKind::Union(..) => (x.ident.name, "union"),
ItemKind::Struct(..) => (x.ident.name, "struct"), _ => (x.ident.name, "type"),
ItemKind::Enum(..) => (x.ident.name, "enum"), },
ItemKind::Union(..) => (x.ident.name, "union"), _ => return,
_ => (x.ident.name, "type"), };
} check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
_ => 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::path_to_local_id;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used; use clippy_utils::visitors::is_local_used;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::{BindingAnnotation, Mutability}; 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<'_>) { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let mut it = block.stmts.iter().peekable(); let mut it = block.stmts.iter().peekable();
while let Some(stmt) = it.next() { while let Some(stmt) = it.next() {
if_chain! { if let Some(expr) = it.peek()
if let Some(expr) = it.peek(); && let hir::StmtKind::Local(local) = stmt.kind
if let hir::StmtKind::Local(local) = stmt.kind; && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; && let hir::StmtKind::Expr(if_) = expr.kind
if let hir::StmtKind::Expr(if_) = expr.kind; && let hir::ExprKind::If(
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind; hir::Expr {
if !is_local_used(cx, *cond, canonical_id); kind: hir::ExprKind::DropTemps(cond),
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,
then { else_,
let span = stmt.span.to(if_.span); ) = 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( let has_interior_mutability = !cx
cx.tcx, .typeck_results()
cx.param_env, .node_type(canonical_id)
); .is_freeze(cx.tcx, cx.param_env);
if has_interior_mutability { return; } if has_interior_mutability {
return;
}
let (default_multi_stmts, default) = if let Some(else_) = else_ { let (default_multi_stmts, default) = if let Some(else_) = else_ {
if let hir::ExprKind::Block(else_, _) = else_.kind { if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) { if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default) (else_.stmts.len() > 1, default)
} else if let Some(default) = local.init { } else if let Some(default) = local.init {
(true, default) (true, default)
} else {
continue;
}
} else { } else {
continue; continue;
} }
} else if let Some(default) = local.init {
(false, default)
} else { } else {
continue; continue;
}; }
} else if let Some(default) = local.init {
(false, default)
} else {
continue;
};
let mutability = match mode { let mutability = match mode {
BindingAnnotation(_, Mutability::Mut) => "<mut> ", BindingAnnotation(_, Mutability::Mut) => "<mut> ",
_ => "", _ => "",
}; };
// FIXME: this should not suggest `mut` if we can detect that the variable is not // FIXME: this should not suggest `mut` if we can detect that the variable is not
// use mutably after the `if` // use mutably after the `if`
let sug = format!( let sug = format!(
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", "let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
name=ident.name, name=ident.name,
cond=snippet(cx, cond.span, "_"), cond=snippet(cx, cond.span, "_"),
then=if then.stmts.len() > 1 { " ..;" } else { "" }, then=if then.stmts.len() > 1 { " ..;" } else { "" },
else=if default_multi_stmts { " ..;" } else { "" }, else=if default_multi_stmts { " ..;" } else { "" },
value=snippet(cx, value.span, "<value>"), value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"), default=snippet(cx, default.span, "<default>"),
); );
span_lint_and_then(cx, span_lint_and_then(
USELESS_LET_IF_SEQ, cx,
span, USELESS_LET_IF_SEQ,
"`if _ { .. } else { .. }` is an expression", span,
|diag| { "`if _ { .. } else { .. }` is an expression",
diag.span_suggestion( |diag| {
span, diag.span_suggestion(
"it is more idiomatic to write", span,
sug, "it is more idiomatic to write",
Applicability::HasPlaceholders, sug,
); Applicability::HasPlaceholders,
if !mutability.is_empty() { );
diag.note("you might not need `mut` at all"); if !mutability.is_empty() {
} diag.note("you might not need `mut` at all");
}); }
} },
);
} }
} }
} }
@ -141,20 +149,23 @@ fn check_assign<'tcx>(
decl: hir::HirId, decl: hir::HirId,
block: &'tcx hir::Block<'_>, block: &'tcx hir::Block<'_>,
) -> Option<&'tcx hir::Expr<'tcx>> { ) -> Option<&'tcx hir::Expr<'tcx>> {
if_chain! { if block.expr.is_none()
if block.expr.is_none(); && let Some(expr) = block.stmts.iter().last()
if let Some(expr) = block.stmts.iter().last(); && let hir::StmtKind::Semi(expr) = expr.kind
if let hir::StmtKind::Semi(expr) = expr.kind; && let hir::ExprKind::Assign(var, value, _) = expr.kind
if let hir::ExprKind::Assign(var, value, _) = expr.kind; && path_to_local_id(var, decl)
if path_to_local_id(var, decl); {
then { if block
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) { .stmts
None .iter()
} else { .take(block.stmts.len() - 1)
Some(value) .any(|stmt| is_local_used(cx, stmt, decl))
} {
} else {
None None
} else {
Some(value)
} }
} else {
None
} }
} }

View file

@ -27,27 +27,25 @@ declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped { impl LateLintPass<'_> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
if_chain! { if !in_external_macro(cx.tcx.sess, local.span)
if !in_external_macro(cx.tcx.sess, local.span); && let Some(ty) = local.ty // Ensure that it has a type defined
if let Some(ty) = local.ty; // Ensure that it has a type defined && let TyKind::Infer = &ty.kind // that type is '_'
if let TyKind::Infer = &ty.kind; // that type is '_' && local.span.eq_ctxt(ty.span)
if local.span.eq_ctxt(ty.span); {
then { // NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized, // this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty` if snippet(cx, ty.span, "_").trim() != "_" {
if snippet(cx, ty.span, "_").trim() != "_" { return;
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"
)
} }
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_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId}; use rustc_lint::{Lint, LintId};
use rustc_session::Session;
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
pub mod deprecated_lints; pub mod deprecated_lints;
@ -165,6 +164,7 @@ mod item_name_repetitions;
mod items_after_statements; mod items_after_statements;
mod items_after_test_module; mod items_after_test_module;
mod iter_not_returning_iterator; mod iter_not_returning_iterator;
mod iter_over_hash_type;
mod iter_without_into_iter; mod iter_without_into_iter;
mod large_const_arrays; mod large_const_arrays;
mod large_enum_variant; mod large_enum_variant;
@ -308,7 +308,6 @@ mod slow_vector_initialization;
mod std_instead_of_core; mod std_instead_of_core;
mod strings; mod strings;
mod strlen_on_c_strings; mod strlen_on_c_strings;
mod suspicious_doc_comments;
mod suspicious_operation_groupings; mod suspicious_operation_groupings;
mod suspicious_trait_impl; mod suspicious_trait_impl;
mod suspicious_xor_used_as_pow; mod suspicious_xor_used_as_pow;
@ -492,11 +491,83 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
groups.register(store); 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`. /// Used in `./src/driver.rs`.
#[expect(clippy::too_many_lines)] #[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_removed_non_tool_lints(store);
register_categories(store); register_categories(store);
@ -521,7 +592,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| { store.register_late_pass(|_| {
Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new()) 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::new(utils::internal_lints::invalid_paths::InvalidPaths));
store.register_late_pass(|_| { store.register_late_pass(|_| {
Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default() 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 |_| { store.register_late_pass(move |_| {
Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new( Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(
arithmetic_side_effects_allowed 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_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::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author)); 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 |_| { store.register_late_pass(move |_| {
Box::new(await_holding_invalid::AwaitHolding::new( Box::new(await_holding_invalid::AwaitHolding::new(
await_holding_invalid_types.clone(), await_holding_invalid_types.clone(),
)) ))
}); });
store.register_late_pass(|_| Box::new(serde_api::SerdeApi)); 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 |_| { store.register_late_pass(move |_| {
Box::new(types::Types::new( Box::new(types::Types::new(
vec_box_size_threshold, 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(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); 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()))); 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 |_| { store.register_late_pass(move |_| {
Box::new(methods::Methods::new( Box::new(methods::Methods::new(
avoid_breaking_exported_api, 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()))); 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_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_non_exhaustive::ManualNonExhaustiveEnum::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::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_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(size_of_in_element_count::SizeOfInElementCount));
store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod)); 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 |_| { store.register_late_pass(move |_| {
Box::new(index_refutable_slice::IndexRefutableSlice::new( Box::new(index_refutable_slice::IndexRefutableSlice::new(
max_suggested_slice_pattern_length, 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::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes)); 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(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::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes)); 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(no_effect::NoEffect));
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment)); store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv()))); store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(cognitive_complexity::CognitiveComplexity::new( Box::new(cognitive_complexity::CognitiveComplexity::new(
cognitive_complexity_threshold, 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(escape::BoxedLocal { too_large_for_stack }));
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(vec::UselessVec { 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(empty_enum::EmptyEnum));
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons)); store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
store.register_late_pass(|_| Box::<regex::Regex>::default()); 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(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(copy_iterator::CopyIterator));
store.register_late_pass(|_| Box::new(format::UselessFormat)); store.register_late_pass(|_| Box::new(format::UselessFormat));
store.register_late_pass(|_| Box::new(swap::Swap)); store.register_late_pass(|_| Box::new(swap::Swap));
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional)); store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default()); 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)));
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 |_| { store.register_late_pass(move |_| {
Box::new(functions::Functions::new( Box::new(functions::Functions::new(
too_many_arguments_threshold, too_many_arguments_threshold,
@ -704,9 +745,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
avoid_breaking_exported_api, avoid_breaking_exported_api,
)) ))
}); });
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>(); store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents)));
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(|_| Box::new(neg_multiply::NegMultiply)); 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(let_if_seq::LetIfSeq));
store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); 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(match_result_ok::MatchResultOk));
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); 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(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(explicit_write::ExplicitWrite));
store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)); store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new( store.register_late_pass(move |tcx| {
conf.trivial_copy_size_limit, Box::new(pass_by_ref_or_value::PassByRefOrValue::new(
conf.pass_by_value_size_limit, trivial_copy_size_limit,
conf.avoid_breaking_exported_api, pass_by_value_size_limit,
&sess.target, avoid_breaking_exported_api,
); tcx.sess.target.pointer_width,
store.register_late_pass(move |_| Box::new(pass_by_ref_or_value)); ))
});
store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef)); 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(infinite_iter::InfiniteIter));
store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody)); 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, 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(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(ptr_offset_with_cast::PtrOffsetWithCast));
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); 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_constants::AssertionsOnConstants));
store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)); store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString)); 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(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds, msrv())));
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain)); 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_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(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens)); 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_early_pass(|| Box::new(redundant_else::RedundantElse));
store.register_late_pass(|_| Box::new(create_dir::CreateDir)); store.register_late_pass(|_| Box::new(create_dir::CreateDir));
store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); 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 || { store.register_early_pass(move || {
Box::new(literal_representation::LiteralDigitGrouping::new( 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 || { store.register_early_pass(move || {
Box::new(literal_representation::DecimalLiteralRepresentation::new( Box::new(literal_representation::DecimalLiteralRepresentation::new(
literal_representation_threshold, 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 |_| { store.register_late_pass(move |_| {
Box::new(item_name_repetitions::ItemNameRepetitions::new( Box::new(item_name_repetitions::ItemNameRepetitions::new(
enum_variant_name_threshold, 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)); 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 |_| { store.register_late_pass(move |_| {
Box::new(upper_case_acronyms::UpperCaseAcronyms::new( Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
avoid_breaking_exported_api, 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(mutable_debug_assertion::DebugAssertWithMutCall));
store.register_late_pass(|_| Box::new(exit::Exit)); store.register_late_pass(|_| Box::new(exit::Exit));
store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome)); 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.into())));
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.into())));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)); 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(as_conversions::AsConversions));
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore)); store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default()); 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 |_| { store.register_late_pass(move |_| {
Box::new(excessive_bools::ExcessiveBools::new( Box::new(excessive_bools::ExcessiveBools::new(
max_struct_bools, 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)); 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(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::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress)); store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default()); 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(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend)); 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(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_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse)); 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(equatable_if_let::PatternEquality));
store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn)); store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn));
store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn)); 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 || { store.register_early_pass(move || {
Box::new(non_expressive_names::NonExpressiveNames { Box::new(non_expressive_names::NonExpressiveNames {
single_char_binding_names_threshold, 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(standard_macro_braces)));
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(&macro_matcher)));
store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default()); 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(pattern_type_mismatch::PatternTypeMismatch));
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); 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(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); 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()))); 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_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::InlineAsmX86AttSyntax));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); 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_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle)); store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|_| Box::<unused_async::UnusedAsync>::default()); 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()))); 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 |_| { store.register_late_pass(move |_| {
Box::new(missing_enforced_import_rename::ImportRename::new( 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(allowed_scripts)));
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)); 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(self_named_constructors::SelfNamedConstructors));
store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator));
store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert)); 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 |_| { store.register_late_pass(move |_| {
Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new( Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
enable_raw_pointer_heuristic_for_send, 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 |_| { store.register_late_pass(move |_| {
Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new( Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(
accept_comment_above_statement, accept_comment_above_statement,
accept_comment_above_attributes, accept_comment_above_attributes,
)) ))
}); });
let allow_mixed_uninlined = conf.allow_mixed_uninlined_format_args; store
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined))); .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_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); 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(move |_| Box::new(manual_bits::ManualBits::new(msrv())));
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default()); 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))); 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))); 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 |_| { store.register_late_pass(move |_| {
Box::new(cargo::Cargo { Box::new(cargo::Cargo {
ignore_publish: cargo_ignore_publish, 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_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_early_pass(|| Box::new(pub_use::PubUse));
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); 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(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(strings::TrimSplitWhitespace));
store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); 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(|_| 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_rem_euclid::ManualRemEuclid::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::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(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(|_| Box::<std_instead_of_core::StdReexports>::default());
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv()))); 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(from_raw_with_void_ptr::FromRawWithVoidPtr));
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)); 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()))); 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 |_| { store.register_late_pass(move |_| {
Box::new(semicolon_block::SemicolonBlock::new( Box::new(semicolon_block::SemicolonBlock::new(
semicolon_inside_block_ignore_singleline, 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(|_| Box::new(allow_attributes::AllowAttribute));
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
let unnecessary_box_size = conf.unnecessary_box_size;
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new( Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
avoid_breaking_exported_api, 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(lines_filter_map_ok::LinesFilterMapOk));
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation)); 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 || { store.register_early_pass(move || {
Box::new(excessive_nesting::ExcessiveNesting { Box::new(excessive_nesting::ExcessiveNesting {
excessive_nesting_threshold, 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(redundant_type_annotations::RedundantTypeAnnotations));
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)); store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf)); 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 |_| { store.register_late_pass(move |_| {
Box::new(min_ident_chars::MinIdentChars { Box::new(min_ident_chars::MinIdentChars {
allowed_idents_below_min_chars: allowed_idents_below_min_chars.clone(), allowed_idents_below_min_chars: allowed_idents_below_min_chars.clone(),
min_ident_chars_threshold, 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(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(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
store.register_late_pass(move |_| { 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(), 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 || { store.register_early_pass(move || {
Box::new(raw_strings::RawStrings { 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)); 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(manual_float_methods::ManualFloatMethods));
store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes));
store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError)); 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 |_| { store.register_late_pass(move |_| {
Box::new(absolute_paths::AbsolutePaths { Box::new(absolute_paths::AbsolutePaths {
absolute_paths_max_segments, 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(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_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` // 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_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::Span;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -310,20 +310,17 @@ fn elision_suggestions(
// elision doesn't work for explicit self types, see rust-lang/rust#69064 // 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 { fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
if_chain! { if let Some(ident) = ident
if let Some(ident) = ident; && ident.name == kw::SelfLower
if ident.name == kw::SelfLower; && !func.implicit_self.has_implicit_self()
if !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(); !visitor.all_lts().is_empty()
then { } else {
let mut visitor = RefVisitor::new(cx); false
visitor.visit_ty(self_ty);
!visitor.all_lts().is_empty()
} else {
false
}
} }
} }

View file

@ -4,7 +4,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, LitKind}; use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token; use rustc_ast::token;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -255,56 +254,48 @@ impl LiteralDigitGrouping {
} }
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) { fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
if_chain! { if let Some(src) = snippet_opt(cx, span)
if let Some(src) = snippet_opt(cx, span); && let Ok(lit_kind) = LitKind::from_token_lit(lit)
if let Ok(lit_kind) = LitKind::from_token_lit(lit); && let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
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) {
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) { return;
return; }
}
if Self::is_literal_uuid_formatted(&num_lit) { if Self::is_literal_uuid_formatted(&num_lit) {
return; 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)?; let consistent = Self::parts_consistent(
if let Some(fraction) = num_lit.fraction { integral_group_size,
let fractional_group_size = Self::get_group_size( fractional_group_size,
fraction.rsplit('_'), num_lit.integer.len(),
num_lit.radix, fraction.len(),
self.lint_fraction_readability)?; );
if !consistent {
let consistent = Self::parts_consistent(integral_group_size, return Err(WarningType::InconsistentDigitGrouping);
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
}
}; };
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) { fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
// Lint integral literals. // Lint integral literals.
if_chain! { if let Ok(lit_kind) = LitKind::from_token_lit(lit)
if let Ok(lit_kind) = LitKind::from_token_lit(lit); && let LitKind::Int(val, _) = lit_kind
if let LitKind::Int(val, _) = lit_kind; && let Some(src) = snippet_opt(cx, span)
if let Some(src) = snippet_opt(cx, span); && let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind); && num_lit.radix == Radix::Decimal
if num_lit.radix == Radix::Decimal; && val >= u128::from(self.threshold)
if val >= u128::from(self.threshold); {
then { let hex = format!("{val:#X}");
let hex = format!("{val:#X}"); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { warning_type.display(num_lit.format(), cx, span);
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::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_enclosing_block, is_integer_const}; use clippy_utils::{get_enclosing_block, is_integer_const};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::intravisit::{walk_block, walk_expr};
use rustc_hir::{Expr, Pat}; use rustc_hir::{Expr, Pat};
@ -30,59 +29,57 @@ pub(super) fn check<'tcx>(
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
walk_block(&mut initialize_visitor, block); walk_block(&mut initialize_visitor, block);
if_chain! { if let Some((name, ty, initializer)) = initialize_visitor.get_result()
if let Some((name, ty, initializer)) = initialize_visitor.get_result(); && is_integer_const(cx, initializer, 0)
if is_integer_const(cx, initializer, 0); {
then { let mut applicability = Applicability::MaybeIncorrect;
let mut applicability = Applicability::MaybeIncorrect; let span = expr.span.with_hi(arg.span.hi());
let span = expr.span.with_hi(arg.span.hi());
let int_name = match ty.map(Ty::kind) { let int_name = match ty.map(Ty::kind) {
// usize or inferred // usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => { Some(ty::Uint(UintTy::Usize)) | None => {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
EXPLICIT_COUNTER_LOOP, EXPLICIT_COUNTER_LOOP,
span, span,
&format!("the variable `{name}` is used as a loop counter"), &format!("the variable `{name}` is used as a loop counter"),
"consider using", "consider using",
format!( format!(
"for ({name}, {}) in {}.enumerate()", "for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability), snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability), make_iterator_snippet(cx, arg, &mut applicability),
), ),
applicability, applicability,
); );
return; return;
} },
Some(ty::Int(int_ty)) => int_ty.name_str(), Some(ty::Int(int_ty)) => int_ty.name_str(),
Some(ty::Uint(uint_ty)) => uint_ty.name_str(), Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
_ => return, _ => return,
}; };
span_lint_and_then( span_lint_and_then(
cx, cx,
EXPLICIT_COUNTER_LOOP, EXPLICIT_COUNTER_LOOP,
span, span,
&format!("the variable `{name}` is used as a loop counter"), &format!("the variable `{name}` is used as a loop counter"),
|diag| { |diag| {
diag.span_suggestion( diag.span_suggestion(
span, span,
"consider using", "consider using",
format!( format!(
"for ({name}, {}) in (0_{int_name}..).zip({})", "for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability), snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability), make_iterator_snippet(cx, arg, &mut applicability),
), ),
applicability, applicability,
); );
diag.note(format!( diag.note(format!(
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`" "`{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::source::snippet_with_applicability;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt}; 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_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::lang_items::LangItem; use rustc_hir::lang_items::LangItem;
@ -23,77 +22,79 @@ pub(super) fn check<'tcx>(
let inner_expr = peel_blocks_with_stmt(body); let inner_expr = peel_blocks_with_stmt(body);
// Check for the specific case that the result is returned and optimize suggestion for that (more // Check for the specific case that the result is returned and optimize suggestion for that (more
// cases can be added later) // cases can be added later)
if_chain! { if let Some(higher::If {
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr); cond,
if let Some(binding_id) = get_binding(pat); then,
if let ExprKind::Block(block, _) = then.kind; r#else: None,
if let [stmt] = block.stmts; }) = higher::If::hir(inner_expr)
if let StmtKind::Semi(semi) = stmt.kind; && let Some(binding_id) = get_binding(pat)
if let ExprKind::Ret(Some(ret_value)) = semi.kind; && let ExprKind::Block(block, _) = then.kind
if let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind; && let [stmt] = block.stmts
if is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome); && let StmtKind::Semi(semi) = stmt.kind
if path_res(cx, inner_ret) == Res::Local(binding_id); && let ExprKind::Ret(Some(ret_value)) = semi.kind
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr); && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind
then { && is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome)
let mut applicability = Applicability::MachineApplicable; && path_res(cx, inner_ret) == Res::Local(binding_id)
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
// Checks if `pat` is a single reference to a binding (`&x`) {
let is_ref_to_binding = let mut applicability = Applicability::MachineApplicable;
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// If `pat` is not a binding or a reference to a binding (`x` or `&x`) // Checks if `pat` is a single reference to a binding (`&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`) let is_ref_to_binding =
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) { matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
snippet.push_str( // If `pat` is not a binding or a reference to a binding (`x` or `&x`)
&format!( // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
".map(|{}| {})", if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet_with_applicability(cx, pat.span, "..", &mut applicability), snippet.push_str(
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), &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,
);
},
); );
} }
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 { if let Some(ret) = block.expr {
return Some((last_stmt, ret)); return Some((last_stmt, ret));
} }
if_chain! { if let [.., snd_last, _] = block.stmts
if let [.., snd_last, _] = block.stmts; && let StmtKind::Semi(last_expr) = last_stmt.kind
if let StmtKind::Semi(last_expr) = last_stmt.kind; && let ExprKind::Ret(Some(ret)) = last_expr.kind
if let ExprKind::Ret(Some(ret)) = last_expr.kind; {
then { return Some((snd_last, ret));
return Some((snd_last, ret));
}
} }
} }
None None
} }
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id); 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 // This should be the loop
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
// This should be the function body // This should be the function body
if let Some((_, Node::Block(block))) = parent_iter.next(); && let Some((_, Node::Block(block))) = parent_iter.next()
if let Some((last_stmt, last_ret)) = extract(block); && let Some((last_stmt, last_ret)) = extract(block)
if last_stmt.hir_id == node_hir; && last_stmt.hir_id == node_hir
if is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone); && is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone)
if let Some((_, Node::Expr(_block))) = parent_iter.next(); && let Some((_, Node::Expr(_block))) = parent_iter.next()
// This includes the function header // This includes the function header
if let Some((_, func)) = parent_iter.next(); && let Some((_, func)) = parent_iter.next()
if func.fn_kind().is_some(); && func.fn_kind().is_some()
then { {
Some((block.stmts.last().unwrap(), last_ret)) Some((block.stmts.last().unwrap(), last_ret))
} else { } else {
None None
}
} }
} }

View file

@ -3,7 +3,6 @@ use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::is_local_used; use clippy_utils::visitors::is_local_used;
use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt}; use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, Pat, PatKind}; use rustc_hir::{Expr, Pat, PatKind};
@ -21,66 +20,51 @@ pub(super) fn check<'tcx>(
span: Span, span: Span,
) { ) {
let inner_expr = peel_blocks_with_stmt(body); let inner_expr = peel_blocks_with_stmt(body);
if_chain! { if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None }) = higher::IfLet::hir(cx, inner_expr)
= higher::IfLet::hir(cx, inner_expr);
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop // 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; && let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind
if path_to_local_id(let_expr, pat_hir_id); && 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` // 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; && let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind
if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id); && 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(variant_id) = cx.tcx.opt_parent(ctor_id)
let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_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); && let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id)
if some_ctor || ok_ctor; && (some_ctor || ok_ctor)
// Ensure expr in `if let` is not used afterwards // Ensure expr in `if let` is not used afterwards
if !is_local_used(cx, if_then, pat_hir_id); && !is_local_used(cx, if_then, pat_hir_id)
then { {
let if_let_type = if some_ctor { "Some" } else { "Ok" }; let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message // Prepare the error message
let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used"); let msg =
format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
// Prepare the help message // Prepare the help message
let mut applicability = Applicability::MaybeIncorrect; let mut applicability = Applicability::MaybeIncorrect;
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
let copied = match cx.typeck_results().expr_ty(let_expr).kind() { let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
ty::Ref(_, inner, _) => match inner.kind() { ty::Ref(_, inner, _) => match inner.kind() {
ty::Ref(..) => ".copied()", 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, // If suggestion is not a one-liner, it won't be shown inline within the error message. In that
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs // case, it will be shown in the extra `help` message at the end, which is why the first
// to refer to the correct relative position of the suggestion. // `help_msg` needs to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') { let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..." "remove the `if let` statement in the for loop and then..."
} else { } else {
"...and remove the `if let` statement in the for loop" "...and remove the `if let` statement in the for loop"
}; };
span_lint_and_then( span_lint_and_then(cx, MANUAL_FLATTEN, span, &msg, |diag| {
cx, diag.span_suggestion(arg.span, "try", sugg, applicability);
MANUAL_FLATTEN, diag.span_help(inner_expr.span, help_msg);
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::sugg::Sugg;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg}; use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
use if_chain::if_chain;
use rustc_ast::ast; use rustc_ast::ast;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::walk_block; use rustc_hir::intravisit::walk_block;
@ -59,22 +58,31 @@ pub(super) fn check<'tcx>(
.map(|o| { .map(|o| {
o.and_then(|(lhs, rhs)| { o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs); let rhs = fetch_cloned_expr(rhs);
if_chain! { if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind; && let ExprKind::Index(base_right, idx_right, _) = rhs.kind
if 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))
if 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()
if 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)
if 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)
if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
// Source and destination must be different // Source and destination must be different
if path_to_local(base_left) != path_to_local(base_right); && path_to_local(base_left) != path_to_local(base_right)
then { {
Some((ty, IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, Some((
IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) ty,
} else { IndexExpr {
None 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>| { let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
if_chain! { if let ExprKind::MethodCall(method, recv, [], _) = end.kind
if let ExprKind::MethodCall(method, recv, [], _) = end.kind; && method.ident.name == sym::len
if method.ident.name == sym::len; && path_to_local(recv) == path_to_local(base)
if path_to_local(recv) == path_to_local(base); {
then { if sugg.to_string() == end_str {
if sugg.to_string() == end_str { sugg::EMPTY.into()
sugg::EMPTY.into()
} else {
sugg
}
} else { } else {
match limits { sugg
ast::RangeLimits::Closed => { }
sugg + &sugg::ONE.into() } else {
}, match limits {
ast::RangeLimits::HalfOpen => sugg, 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 dst_base_str = snippet(cx, dst.base.span, "???");
let src_base_str = snippet(cx, src.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 dst_base_str
} else { } else {
format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into() 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" "clone_from_slice"
}; };
format!( let src = if is_array_length_equal_to_range(cx, start, end, src.base) {
"{dst}.{method_str}(&{src_base_str}[{}..{}]);", src_base_str
src_offset.maybe_par(), } else {
src_limit.maybe_par() 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`; /// 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> { fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if_chain! { if let ExprKind::MethodCall(method, arg, [], _) = expr.kind
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind; && method.ident.name == sym::clone
if method.ident.name == sym::clone; {
then { arg } else { expr } arg
} else {
expr
} }
} }
@ -446,3 +456,34 @@ fn get_loop_counters<'a, 'tcx>(
.into() .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<'_>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
if_chain! { if let ExprKind::Block(
if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind; Block {
if let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind; stmts: [], expr: None, ..
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()); ) = body.kind
then { && let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind
span_lint_and_sugg( && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name)
cx, && let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind()
MISSING_SPIN_LOOP, && cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did())
body.span, {
"busy-waiting loop should at least have a spin loop hint", span_lint_and_sugg(
"try", cx,
(if is_no_std_crate(cx) { MISSING_SPIN_LOOP,
"{ core::hint::spin_loop() }" body.span,
} else { "busy-waiting loop should at least have a spin loop hint",
"{ std::hint::spin_loop() }" "try",
}).into(), (if is_no_std_crate(cx) {
Applicability::MachineApplicable "{ core::hint::spin_loop() }"
); } else {
} "{ std::hint::spin_loop() }"
})
.into(),
Applicability::MachineApplicable,
);
} }
} }

View file

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

View file

@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method; use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used; use clippy_utils::visitors::is_local_used;
use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq}; use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast; use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -187,15 +186,13 @@ pub(super) fn check<'tcx>(
} }
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
if_chain! { if let ExprKind::MethodCall(method, recv, [], _) = expr.kind
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind; && method.ident.name == sym::len
if method.ident.name == sym::len; && let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind
if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind; && path.segments.len() == 1
if path.segments.len() == 1; && path.segments[0].ident.name == var
if path.segments[0].ident.name == var; {
then { return true;
return true;
}
} }
false false
@ -207,17 +204,15 @@ fn is_end_eq_array_len<'tcx>(
limits: ast::RangeLimits, limits: ast::RangeLimits,
indexed_ty: Ty<'tcx>, indexed_ty: Ty<'tcx>,
) -> bool { ) -> bool {
if_chain! { if let ExprKind::Lit(lit) = end.kind
if let ExprKind::Lit(lit) = end.kind; && let ast::LitKind::Int(end_int, _) = lit.node
if let ast::LitKind::Int(end_int, _) = lit.node; && let ty::Array(_, arr_len_const) = indexed_ty.kind()
if 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)
if let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env); {
then { return match limits {
return match limits { ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), };
};
}
} }
false false
@ -248,51 +243,49 @@ struct VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarVisitor<'a, 'tcx> { impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { 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 // the indexed container is referenced by a name
if let ExprKind::Path(ref seqpath) = seqexpr.kind; && let QPath::Resolved(None, seqvar) = *seqpath
if let QPath::Resolved(None, seqvar) = *seqpath; && seqvar.segments.len() == 1
if seqvar.segments.len() == 1; && is_local_used(self.cx, idx, self.var)
if is_local_used(self.cx, idx, self.var); {
then { if self.prefer_mutable {
if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name);
self.indexed_mut.insert(seqvar.segments[0].ident.name); }
} let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); match res {
match res { Res::Local(hir_id) => {
Res::Local(hir_id) => { let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); let extent = self
let extent = self .cx
.cx .tcx
.tcx .region_scope_tree(parent_def_id)
.region_scope_tree(parent_def_id) .var_scope(hir_id.local_id)
.var_scope(hir_id.local_id) .unwrap();
.unwrap(); if index_used_directly {
if index_used_directly { self.indexed_directly.insert(
self.indexed_directly.insert( seqvar.segments[0].ident.name,
seqvar.segments[0].ident.name, (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), );
); } else {
} else { self.indexed_indirectly
self.indexed_indirectly .insert(seqvar.segments[0].ident.name, Some(extent));
.insert(seqvar.segments[0].ident.name, Some(extent)); }
} return false; // no need to walk further *on the variable*
return false; // no need to walk further *on the variable* },
}, Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => { if index_used_directly {
if index_used_directly { self.indexed_directly.insert(
self.indexed_directly.insert( seqvar.segments[0].ident.name,
seqvar.segments[0].ident.name, (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)), );
); } else {
} else { self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); }
} return false; // no need to walk further *on the variable*
return false; // no need to walk further *on the variable* },
}, _ => (),
_ => (),
}
} }
} }
true true
@ -301,42 +294,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { 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 // a range index op
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind; && let Some(trait_id) = self
if let Some(trait_id) = self
.cx .cx
.typeck_results() .typeck_results()
.type_dependent_def_id(expr.hir_id) .type_dependent_def_id(expr.hir_id)
.and_then(|def_id| self.cx.tcx.trait_of_item(def_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 && 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)); || (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); && !self.check(args_1, args_0, expr)
then { {
return; return;
}
} }
if_chain! { if let ExprKind::Index(seqexpr, idx, _) = expr.kind
// an index op // an index op
if let ExprKind::Index(seqexpr, idx, _) = expr.kind; && !self.check(idx, seqexpr, expr)
if !self.check(idx, seqexpr, expr); {
then { return;
return;
}
} }
if_chain! { if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
// directly using a variable // directly using a variable
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; && let Res::Local(local_id) = path.res
if let Res::Local(local_id) = path.res; {
then { if local_id == self.var {
if local_id == self.var { self.nonindex = true;
self.nonindex = true; } else {
} else { // not the correct variable, but still a variable
// not the correct variable, but still a variable self.referenced.insert(path.segments[0].ident.name);
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::path_to_local;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -44,54 +43,50 @@ pub(super) fn check<'tcx>(
// Determine whether it is safe to lint the body // Determine whether it is safe to lint the body
let mut same_item_push_visitor = SameItemPushVisitor::new(cx); let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
walk_expr(&mut same_item_push_visitor, body); walk_expr(&mut same_item_push_visitor, body);
if_chain! { if same_item_push_visitor.should_lint()
if same_item_push_visitor.should_lint(); && let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push
if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push; && let vec_ty = cx.typeck_results().expr_ty(vec)
let vec_ty = cx.typeck_results().expr_ty(vec); && let ty = vec_ty.walk().nth(1).unwrap().expect_ty()
let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); && cx
if cx
.tcx .tcx
.lang_items() .lang_items()
.clone_trait() .clone_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[])); .map_or(false, |id| implements_trait(cx, ty, id, &[]))
then { {
// Make sure that the push does not involve possibly mutating values // Make sure that the push does not involve possibly mutating values
match pushed_item.kind { match pushed_item.kind {
ExprKind::Path(ref qpath) => { ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) { match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant // immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => { Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id); let node = cx.tcx.hir().get(hir_id);
if_chain! { if let Node::Pat(pat) = node
if let Node::Pat(pat) = node; && let PatKind::Binding(bind_ann, ..) = pat.kind
if let PatKind::Binding(bind_ann, ..) = pat.kind; && !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut))
if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut)); && let parent_node = cx.tcx.hir().parent_id(hir_id)
let parent_node = cx.tcx.hir().parent_id(hir_id); && let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node)
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); && let Some(init) = parent_let_expr.init
if let Some(init) = parent_let_expr.init; {
then { match init.kind {
match init.kind { // immutable bindings that are initialized with literal
// immutable bindings that are initialized with literal ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt), // immutable bindings that are initialized with constant
// immutable bindings that are initialized with constant ExprKind::Path(ref path) => {
ExprKind::Path(ref path) => { if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { emit_lint(cx, vec, pushed_item, ctxt);
emit_lint(cx, vec, pushed_item, ctxt);
}
}
_ => {},
} }
} },
_ => {},
} }
}, }
// constant },
Res::Def(DefKind::Const, ..) => 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), },
_ => {}, ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
} _ => {},
} }
} }
} }
@ -118,16 +113,14 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
} }
fn should_lint(&self) -> bool { fn should_lint(&self) -> bool {
if_chain! { if !self.non_deterministic_expr
if !self.non_deterministic_expr; && !self.multiple_pushes
if !self.multiple_pushes; && let Some((vec, _, _)) = self.vec_push
if let Some((vec, _, _)) = self.vec_push; && let Some(hir_id) = path_to_local(vec)
if let Some(hir_id) = path_to_local(vec); {
then { !self.used_locals.contains(&hir_id)
!self.used_locals.contains(&hir_id) } else {
} else { false
false
}
} }
} }
} }
@ -180,18 +173,16 @@ fn get_vec_push<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
stmt: &'tcx Stmt<'_>, stmt: &'tcx Stmt<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> { ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
if_chain! { if let StmtKind::Semi(semi_stmt) = &stmt.kind
// Extract method being called // Extract method being called
if let StmtKind::Semi(semi_stmt) = &stmt.kind; && let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
// Figure out the parameters for the method call // 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 // 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); && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec)
if path.ident.name.as_str() == "push"; && path.ident.name.as_str() == "push"
then { {
return Some((self_expr, pushed_item, semi_stmt.span.ctxt())) return Some((self_expr, pushed_item, semi_stmt.span.ctxt()));
}
} }
None None
} }

View file

@ -2,7 +2,6 @@ use super::SINGLE_ELEMENT_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, snippet_with_applicability}; use clippy_utils::source::{indent_of, snippet_with_applicability};
use clippy_utils::visitors::contains_break_or_continue; use clippy_utils::visitors::contains_break_or_continue;
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX; use rustc_ast::util::parser::PREC_PREFIX;
use rustc_ast::Mutability; use rustc_ast::Mutability;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -66,36 +65,36 @@ pub(super) fn check<'tcx>(
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""), ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
_ => return, _ => return,
}; };
if_chain! { if let ExprKind::Block(block, _) = body.kind
if let ExprKind::Block(block, _) = body.kind; && !block.stmts.is_empty()
if !block.stmts.is_empty(); && !contains_break_or_continue(body)
if !contains_break_or_continue(body); {
then { let mut applicability = Applicability::MachineApplicable;
let mut applicability = Applicability::MachineApplicable; let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
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 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();
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned(); block_str.remove(0);
block_str.remove(0); block_str.pop();
block_str.pop(); let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`. // Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty() && ( if !prefix.is_empty()
&& (
// Precedence of internal expression is less than or equal to precedence of `&expr`. // 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_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. /// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { 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; 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); 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; return;
} }
@ -53,7 +53,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
diag, diag,
"remove the `.enumerate()` call", "remove the `.enumerate()` call",
vec![ vec![
(pat.span, snippet(cx, tuple[1].span, "..").into_owned()), (pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()), (arg.span, base_iter.to_string()),
], ],
); );

View file

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

View file

@ -2,7 +2,6 @@ use super::WHILE_IMMUTABLE_CONDITION;
use clippy_utils::consts::constant; use clippy_utils::consts::constant;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use if_chain::if_chain;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap; use rustc_hir::def_id::DefIdMap;
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::intravisit::{walk_expr, Visitor};
@ -95,20 +94,18 @@ struct VarCollectorVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
if_chain! { if let ExprKind::Path(ref qpath) = ex.kind
if let ExprKind::Path(ref qpath) = ex.kind; && let QPath::Resolved(None, _) = *qpath
if let QPath::Resolved(None, _) = *qpath; {
then { match self.cx.qpath_res(qpath, ex.hir_id) {
match self.cx.qpath_res(qpath, ex.hir_id) { Res::Local(hir_id) => {
Res::Local(hir_id) => { self.ids.insert(hir_id);
self.ids.insert(hir_id); },
}, Res::Def(DefKind::Static(_), def_id) => {
Res::Def(DefKind::Static(_), def_id) => { let mutable = self.cx.tcx.is_mutable_static(def_id);
let mutable = self.cx.tcx.is_mutable_static(def_id); self.def_ids.insert(def_id, mutable);
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::source::snippet_with_applicability;
use clippy_utils::visitors::is_res_used; 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 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_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::intravisit::{walk_expr, Visitor};
@ -15,59 +14,53 @@ use rustc_span::symbol::sym;
use rustc_span::Symbol; use rustc_span::Symbol;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 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 // check for `Some(..)` pattern
if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind; && 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); && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
// check for call to `Iterator::next` // check for call to `Iterator::next`
if let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind; && let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind
if method_name.ident.name == sym::next; && method_name.ident.name == sym::next
if is_trait_method(cx, let_expr, sym::Iterator); && is_trait_method(cx, let_expr, sym::Iterator)
if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr); && let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr)
// get the loop containing the match expression // get the loop containing the match expression
if !uses_iter(cx, &iter_expr_struct, if_then); && !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)
{ {
".by_ref()" let mut applicability = Applicability::MachineApplicable;
} else { 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); // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
span_lint_and_sugg( // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
cx, // afterwards a mutable borrow of a field isn't necessary.
WHILE_LET_ON_ITERATOR, let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
expr.span.with_hi(scrutinee_expr.span.hi()), || !iter_expr_struct.can_move
"this loop could be written as a `for` loop", || !iter_expr_struct.fields.is_empty()
"try", || needs_mutable_borrow(cx, &iter_expr_struct, expr)
format!("for {loop_var} in {iterator}{by_ref}"), {
applicability, ".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)] #[derive(Debug)]

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use hir::def::{DefKind, Res}; use hir::def::{DefKind, Res};
use if_chain::if_chain;
use rustc_ast::ast; use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -89,30 +88,26 @@ impl MacroUseImports {
impl<'tcx> LateLintPass<'tcx> for MacroUseImports { impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! { if cx.sess().opts.edition >= Edition::Edition2018
if cx.sess().opts.edition >= Edition::Edition2018; && let hir::ItemKind::Use(path, _kind) = &item.kind
if let hir::ItemKind::Use(path, _kind) = &item.kind; && let hir_id = item.hir_id()
let hir_id = item.hir_id(); && let attrs = cx.tcx.hir().attrs(hir_id)
let attrs = cx.tcx.hir().attrs(hir_id); && let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use))
if 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 {
if let Some(id) = path.res.iter().find_map(|res| match res {
Res::Def(DefKind::Mod, id) => Some(id), Res::Def(DefKind::Mod, id) => Some(id),
_ => None, _ => None,
}); })
if !id.is_local(); && !id.is_local()
then { {
for kid in cx.tcx.module_children(id) { for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span; let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id); let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span, hir_id)); self.imports.push((def_path, span, hir_id));
}
}
} else {
if item.span.from_expansion() {
self.push_unique_macro_pat_ty(cx, item.span);
} }
} }
} 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) { 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::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::{is_entrypoint_fn, is_no_std_crate}; use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, QPath}; use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -43,21 +42,19 @@ impl LateLintPass<'_> for MainRecursion {
return; return;
} }
if_chain! { if let ExprKind::Call(func, _) = &expr.kind
if let ExprKind::Call(func, _) = &expr.kind; && let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind
if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind; && let Some(def_id) = path.res.opt_def_id()
if let Some(def_id) = path.res.opt_def_id(); && is_entrypoint_fn(cx, def_id)
if is_entrypoint_fn(cx, def_id); {
then { span_lint_and_help(
span_lint_and_help( cx,
cx, MAIN_RECURSION,
MAIN_RECURSION, func.span,
func.span, &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")), None,
None, "consider using another function for this recursion",
"consider using another function for this recursion" );
)
}
} }
} }
} }

View file

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

View file

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

View file

@ -5,18 +5,15 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch; use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, Visitable}; use clippy_utils::{is_lint_allowed, is_never_expr, pat_and_expr_can_be_question_mark, peel_blocks};
use clippy_utils::{is_lint_allowed, pat_and_expr_can_be_question_mark, peel_blocks};
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::declare_tool_lint; use rustc_session::declare_tool_lint;
use rustc_span::symbol::{sym, Symbol}; use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span; use rustc_span::Span;
use std::ops::ControlFlow;
use std::slice; use std::slice;
declare_clippy_lint! { declare_clippy_lint! {
@ -51,7 +48,7 @@ declare_clippy_lint! {
} }
impl<'tcx> QuestionMark { 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) { if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
return; return;
} }
@ -67,7 +64,7 @@ impl<'tcx> QuestionMark {
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => { 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) if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
&& let Some(if_else) = if_else && 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) && 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()) && (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
{ {
@ -91,10 +88,9 @@ impl<'tcx> QuestionMark {
return; return;
} }
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes; let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
let diverging_arm_opt = arms let diverging_arm_opt = arms.iter().enumerate().find(|(_, arm)| {
.iter() is_never_expr(cx, arm.body).is_some() && pat_allowed_for_else(cx, arm.pat, check_types)
.enumerate() });
.find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
let Some((idx, diverging_arm)) = diverging_arm_opt else { let Some((idx, diverging_arm)) = diverging_arm_opt else {
return; return;
}; };
@ -272,104 +268,6 @@ fn replace_in_pattern(
sn_pat.into_owned() 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 { fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
// Check whether the pattern contains any bindings, as the // Check whether the pattern contains any bindings, as the
// binding might potentially be used in the body. // 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::source::snippet;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use clippy_utils::{eq_expr_value, higher, match_def_path, paths}; use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::intravisit::{walk_expr, Visitor};
@ -71,55 +70,61 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
return; return;
} }
if_chain! { if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr); && let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
if let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind; && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id); && let ExprKind::Path(target_path) = &target_arg.kind
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) {
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) { StripKind::Prefix
StripKind::Prefix } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) { StripKind::Suffix
StripKind::Suffix } else {
} else { return;
return; };
}; let target_res = cx.qpath_res(target_path, target_arg.hir_id);
let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err {
if target_res == Res::Err { return;
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! { let test_span = expr.span.until(then.span);
if let Res::Local(hir_id) = target_res; span_lint_and_then(
if let Some(used_mutably) = mutated_variables(then, cx); cx,
if used_mutably.contains(&hir_id); MANUAL_STRIP,
then { strippings[0],
return; &format!("stripping a {kind_word} manually"),
} |diag| {
}
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| {
diag.span_note(test_span, format!("the {kind_word} was tested here")); diag.span_note(test_span, format!("the {kind_word} was tested here"));
multispan_sugg( multispan_sugg(
diag, diag,
&format!("try using the `strip_{kind_word}` method"), &format!("try using the `strip_{kind_word}` method"),
vec![(test_span, vec![(
format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ", test_span,
snippet(cx, target_arg.span, ".."), format!(
snippet(cx, pattern.span, "..")))] "if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
.into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))), 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. // 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>> { 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 ExprKind::MethodCall(_, arg, [], _) = expr.kind; && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
if 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)
if match_def_path(cx, method_def_id, &paths::STR_LEN); {
then { Some(arg)
Some(arg) } else {
} else { None
None
}
} }
} }
@ -201,36 +204,38 @@ fn find_stripping<'tcx>(
impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
if_chain! { if is_ref_str(self.cx, ex)
if is_ref_str(self.cx, ex); && let unref = peel_ref(ex)
let unref = peel_ref(ex); && let ExprKind::Index(indexed, index, _) = &unref.kind
if let ExprKind::Index(indexed, index, _) = &unref.kind; && let Some(higher::Range { start, end, .. }) = higher::Range::hir(index)
if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index); && let ExprKind::Path(path) = &indexed.kind
if let ExprKind::Path(path) = &indexed.kind; && self.cx.qpath_res(path, ex.hir_id) == self.target
if self.cx.qpath_res(path, ex.hir_id) == self.target; {
then { match (self.strip_kind, start, end) {
match (self.strip_kind, start, end) { (StripKind::Prefix, Some(start), None) => {
(StripKind::Prefix, Some(start), None) => { if eq_pattern_length(self.cx, self.pattern, start) {
if eq_pattern_length(self.cx, self.pattern, start) { self.results.push(ex.span);
self.results.push(ex.span); return;
return; }
} },
}, (StripKind::Suffix, None, Some(end)) => {
(StripKind::Suffix, None, Some(end)) => { if let ExprKind::Binary(
if_chain! { Spanned {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind; node: BinOpKind::Sub, ..
if let Some(left_arg) = len_arg(self.cx, left); },
if let ExprKind::Path(left_path) = &left_arg.kind; left,
if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target; right,
if eq_pattern_length(self.cx, self.pattern, right); ) = end.kind
then { && let Some(left_arg) = len_arg(self.cx, left)
self.results.push(ex.span); && let ExprKind::Path(left_path) = &left_arg.kind
return; && 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::source::{snippet, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{iter_input_pats, method_chain_args}; use clippy_utils::{iter_input_pats, method_chain_args};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -163,16 +162,14 @@ fn unit_closure<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> { ) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
if_chain! { if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind; && let body = cx.tcx.hir().body(body)
let body = cx.tcx.hir().body(body); && let body_expr = &body.value
let body_expr = &body.value; && fn_decl.inputs.len() == 1
if fn_decl.inputs.len() == 1; && is_unit_expression(cx, body_expr)
if is_unit_expression(cx, body_expr); && let Some(binding) = iter_input_pats(fn_decl, body).next()
if let Some(binding) = iter_input_pats(fn_decl, body).next(); {
then { return Some((binding, body_expr));
return Some((binding, body_expr));
}
} }
None 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::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, is_res_lang_ctor}; use clippy_utils::{higher, is_res_lang_ctor};
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; use rustc_hir::{Expr, ExprKind, LangItem, PatKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -56,33 +55,31 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
return; 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 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
if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation && ok_path.ident.as_str() == "ok"
if ok_path.ident.as_str() == "ok"; && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
if 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)
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome); && let ctxt = expr.span.ctxt()
let ctxt = expr.span.ctxt(); && let_expr.span.ctxt() == ctxt
if let_expr.span.ctxt() == ctxt; && let_pat.span.ctxt() == ctxt
if let_pat.span.ctxt() == ctxt; {
then { let mut applicability = Applicability::MachineApplicable;
let mut applicability = Applicability::MachineApplicable; let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
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 trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0; let sugg = format!(
let sugg = format!( "{ifwhile} let Ok({some_expr_string}) = {}",
"{ifwhile} let Ok({some_expr_string}) = {}", trimmed_ok.trim().trim_end_matches('.'),
trimmed_ok.trim().trim_end_matches('.'), );
); span_lint_and_sugg(
span_lint_and_sugg( cx,
cx, MATCH_RESULT_OK,
MATCH_RESULT_OK, expr.span.with_hi(let_expr.span.hi()),
expr.span.with_hi(let_expr.span.hi()), "matching on `Some` with `ok()` is redundant",
"matching on `Some` with `ok()` is redundant", &format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"), sugg,
sugg, applicability,
applicability, );
);
}
} }
} }
} }

View file

@ -5,7 +5,6 @@ use clippy_utils::visitors::is_local_used;
use clippy_utils::{ use clippy_utils::{
is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq, 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_errors::MultiSpan;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind}; 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>>, outer_else_body: Option<&'tcx Expr<'tcx>>,
) { ) {
let inner_expr = peel_blocks_with_stmt(outer_then_body); 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) = IfLetOrMatch::parse(cx, inner_expr); && let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)), 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 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" // one of the arms must be "wild-like"
if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a)); && 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]); let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
Some((scrutinee, then.pat, Some(els.body))) Some((scrutinee, then.pat, Some(els.body)))
} else { } else {
None 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 expression must be a local binding
// match <local> { .. } // match <local> { .. }
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee)); && let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee))
if !pat_contains_or(inner_then_pat); && !pat_contains_or(inner_then_pat)
// the binding must come from the pattern of the containing match arm // the binding must come from the pattern of the containing match arm
// ..<local>.. => match <local> { .. } // ..<local>.. => match <local> { .. }
if let (Some(binding_span), is_innermost_parent_pat_struct) && let (Some(binding_span), is_innermost_parent_pat_struct)
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id); = find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id)
// the "else" branches must be equal // 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, None) => true,
(None, Some(e)) | (Some(e), None) => is_unit_expr(e), (None, Some(e)) | (Some(e), None) => is_unit_expr(e),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b), (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
}; }
// the binding must not be used in the if guard // the binding must not be used in the if guard
if outer_guard.map_or( && outer_guard.map_or(
true, true,
|(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id) |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
); )
// ...or anywhere in the inner expression // ...or anywhere in the inner expression
if match inner { && match inner {
IfLetOrMatch::IfLet(_, _, body, els) => { IfLetOrMatch::IfLet(_, _, body, els) => {
!is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id)) !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)), 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; use super::INFALLIBLE_DESTRUCTURING_MATCH;
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool { pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
if_chain! { if !local.span.from_expansion()
if !local.span.from_expansion(); && let Some(expr) = local.init
if let Some(expr) = local.init; && let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; && arms.len() == 1
if arms.len() == 1 && arms[0].guard.is_none(); && arms[0].guard.is_none()
if let PatKind::TupleStruct( && let PatKind::TupleStruct(QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; && args.len() == 1
if args.len() == 1; && let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind
if let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind; && let body = peel_blocks(arms[0].body)
let body = peel_blocks(arms[0].body); && path_to_local_id(body, arg)
if path_to_local_id(body, arg); {
let mut applicability = Applicability::MachineApplicable;
then { span_lint_and_sugg(
let mut applicability = Applicability::MachineApplicable; cx,
span_lint_and_sugg( INFALLIBLE_DESTRUCTURING_MATCH,
cx, local.span,
INFALLIBLE_DESTRUCTURING_MATCH, "you seem to be trying to use `match` to destructure a single infallible pattern. \
local.span, Consider using `let`",
"you seem to be trying to use `match` to destructure a single infallible pattern. \ "try",
Consider using `let`", format!(
"try", "let {}({}{}) = {};",
format!( snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
"let {}({}{}) = {};", if binding.0 == ByRef::Yes { "ref " } else { "" },
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
if binding.0 == ByRef::Yes { "ref " } else { "" }, snippet_with_applicability(cx, target.span, "..", &mut applicability),
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), ),
snippet_with_applicability(cx, target.span, "..", &mut applicability), applicability,
), );
applicability, return true;
);
return true;
}
} }
false false
} }

View file

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

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