mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-31 22:41:50 +00:00
Merge commit 'edb720b199083f4107b858a8761648065bf38d86' into clippyup
This commit is contained in:
parent
9aa2330e41
commit
6246f0446a
64
CHANGELOG.md
64
CHANGELOG.md
@ -6,11 +6,70 @@ document.
|
||||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[1e8fdf49...master](https://github.com/rust-lang/rust-clippy/compare/1e8fdf49...master)
|
||||
[7671c283...master](https://github.com/rust-lang/rust-clippy/compare/7671c283...master)
|
||||
|
||||
## Rust 1.74
|
||||
|
||||
Current stable, released 2023-11-16
|
||||
|
||||
[View all 94 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-08-11T15%3A29%3A18Z..2023-09-25T08%3A48%3A22Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`redundant_as_str`]
|
||||
[#11526](https://github.com/rust-lang/rust-clippy/pull/11526)
|
||||
* [`needless_borrows_for_generic_args`]
|
||||
[#11511](https://github.com/rust-lang/rust-clippy/pull/11511)
|
||||
* [`path_ends_with_ext`]
|
||||
[#11483](https://github.com/rust-lang/rust-clippy/pull/11483)
|
||||
* [`unnecessary_map_on_constructor`]
|
||||
[#11413](https://github.com/rust-lang/rust-clippy/pull/11413)
|
||||
* [`missing_asserts_for_indexing`]
|
||||
[#10692](https://github.com/rust-lang/rust-clippy/pull/10692)
|
||||
* [`iter_out_of_bounds`]
|
||||
[#11396](https://github.com/rust-lang/rust-clippy/pull/11396)
|
||||
* [`implied_bounds_in_impls`]
|
||||
[#11362](https://github.com/rust-lang/rust-clippy/pull/11362)
|
||||
* [`reserve_after_initialization`]
|
||||
[#11373](https://github.com/rust-lang/rust-clippy/pull/11373)
|
||||
* [`should_panic_without_expect`]
|
||||
[#11204](https://github.com/rust-lang/rust-clippy/pull/11204)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Renamed `incorrect_clone_impl_on_copy_type` to [`non_canonical_clone_impl`]
|
||||
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
|
||||
* Renamed `incorrect_partial_ord_impl_on_ord_type` to [`non_canonical_partial_ord_impl`]
|
||||
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
|
||||
* Moved [`non_canonical_clone_impl`] to `suspicious` (Now warn-by-default)
|
||||
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
|
||||
* Moved [`non_canonical_partial_ord_impl`] to `suspicious` (Now warn-by-default)
|
||||
[#11358](https://github.com/rust-lang/rust-clippy/pull/11358)
|
||||
* Moved [`needless_pass_by_ref_mut`] to `nursery` (Now allow-by-default)
|
||||
[#11596](https://github.com/rust-lang/rust-clippy/pull/11596)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`undocumented_unsafe_blocks`]: The config values [`accept-comment-above-statement`] and
|
||||
[`accept-comment-above-attributes`] to `true` by default
|
||||
[#11170](https://github.com/rust-lang/rust-clippy/pull/11170)
|
||||
* [`explicit_iter_loop`]: Added [`enforce-iter-loop-reborrow`] to disable reborrow linting by default
|
||||
[#11418](https://github.com/rust-lang/rust-clippy/pull/11418)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`enum_variant_names`]: No longer crashes if the threshold is 0 and the enum has no variants
|
||||
[#11552](https://github.com/rust-lang/rust-clippy/pull/11552)
|
||||
* [`cast_possible_truncation`]: No longer crashes on values larger than `u64::MAX`
|
||||
[#11517](https://github.com/rust-lang/rust-clippy/pull/11517)
|
||||
* [`tuple_array_conversions`]: No longer crashes if the array length is not usize
|
||||
[#11379](https://github.com/rust-lang/rust-clippy/pull/11379)
|
||||
* [`useless_conversion`]: No longer crashes, when the receiver is a non-fn item
|
||||
[#11070](https://github.com/rust-lang/rust-clippy/pull/11070)
|
||||
|
||||
## Rust 1.73
|
||||
|
||||
Current stable, released 2023-10-05
|
||||
Released 2023-10-05
|
||||
|
||||
[View all 103 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-07-02T12%3A24%3A40Z..2023-08-11T11%3A09%3A56Z+base%3Amaster)
|
||||
|
||||
@ -5123,6 +5182,7 @@ Released 2018-09-13
|
||||
[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
|
||||
[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
|
||||
[`iter_out_of_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_out_of_bounds
|
||||
[`iter_over_hash_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_over_hash_type
|
||||
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
|
||||
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
|
||||
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero
|
||||
|
@ -146,16 +146,10 @@ For example, the [`else_if_without_else`][else_if_without_else] lint is register
|
||||
pub mod else_if_without_else;
|
||||
// ...
|
||||
|
||||
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
|
||||
pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
// ...
|
||||
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
|
||||
// ...
|
||||
|
||||
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
|
||||
// ...
|
||||
LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
|
||||
// ...
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.75"
|
||||
version = "0.1.76"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
@ -270,7 +270,7 @@ When using `cargo dev new_lint`, the lint is automatically registered and
|
||||
nothing more has to be done.
|
||||
|
||||
When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
|
||||
pass may have to be registered manually in the `register_plugins` function in
|
||||
pass may have to be registered manually in the `register_lints` function in
|
||||
`clippy_lints/src/lib.rs`:
|
||||
|
||||
```rust,ignore
|
||||
@ -436,7 +436,7 @@ need to ensure that the MSRV configured for the project is >= the MSRV of the
|
||||
required Rust feature. If multiple features are required, just use the one with
|
||||
a lower MSRV.
|
||||
|
||||
First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`].
|
||||
First, add an MSRV alias for the required feature in [`clippy_config::msrvs`].
|
||||
This can be accessed later as `msrvs::STR_STRIP_PREFIX`, for example.
|
||||
|
||||
```rust
|
||||
@ -506,7 +506,7 @@ fn msrv_1_45() {
|
||||
```
|
||||
|
||||
As a last step, the lint should be added to the lint documentation. This is done
|
||||
in `clippy_lints/src/utils/conf.rs`:
|
||||
in `clippy_config/src/conf.rs`:
|
||||
|
||||
```rust
|
||||
define_Conf! {
|
||||
@ -516,7 +516,7 @@ define_Conf! {
|
||||
}
|
||||
```
|
||||
|
||||
[`clippy_utils::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/msrvs/index.html
|
||||
[`clippy_config::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_config/msrvs/index.html
|
||||
|
||||
## Author lint
|
||||
|
||||
@ -657,7 +657,7 @@ Adding a configuration to a lint can be useful for
|
||||
thresholds or to constrain some behavior that can be seen as a false positive
|
||||
for some users. Adding a configuration is done in the following steps:
|
||||
|
||||
1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this:
|
||||
1. Adding a new configuration entry to [`clippy_config::conf`] like this:
|
||||
|
||||
```rust,ignore
|
||||
/// Lint: LINT_NAME.
|
||||
@ -736,7 +736,7 @@ for some users. Adding a configuration is done in the following steps:
|
||||
|
||||
Run `cargo collect-metadata` to generate documentation changes for the book.
|
||||
|
||||
[`clippy_lints::utils::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/conf.rs
|
||||
[`clippy_config::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_config/src/conf.rs
|
||||
[`clippy_lints` lib file]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs
|
||||
[`tests/ui`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui
|
||||
[`tests/ui-toml`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui-toml
|
||||
|
@ -186,7 +186,7 @@ However, sometimes we might want to declare a new lint by hand. In this case,
|
||||
we'd use `cargo dev update_lints` command afterwards.
|
||||
|
||||
When a lint is manually declared, we might need to register the lint pass
|
||||
manually in the `register_plugins` function in `clippy_lints/src/lib.rs`:
|
||||
manually in the `register_lints` function in `clippy_lints/src/lib.rs`:
|
||||
|
||||
```rust
|
||||
store.register_late_pass(|_| Box::new(foo_functions::FooFunctions));
|
||||
|
@ -1 +1,7 @@
|
||||
avoid-breaking-exported-api = false
|
||||
|
||||
# use the various `span_lint_*` methods instead, which also add a link to the docs
|
||||
disallowed-methods = [
|
||||
"rustc_lint::context::LintContext::struct_span_lint",
|
||||
"rustc_middle::ty::context::TyCtxt::struct_span_lint_hir"
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.75"
|
||||
version = "0.1.76"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -1,7 +1,6 @@
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::{ser, Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Rename {
|
||||
@ -33,32 +32,19 @@ impl DisallowedPath {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum MatchLintBehaviour {
|
||||
AllTypes,
|
||||
WellKnownTypes,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct MacroMatcher {
|
||||
pub name: String,
|
||||
pub braces: (String, String),
|
||||
pub braces: (char, char),
|
||||
}
|
||||
|
||||
impl Hash for MacroMatcher {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MacroMatcher {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
impl Eq for MacroMatcher {}
|
||||
|
||||
impl<'de> Deserialize<'de> for MacroMatcher {
|
||||
fn deserialize<D>(deser: D) -> Result<Self, D::Error>
|
||||
where
|
||||
@ -83,7 +69,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
|
||||
V: de::MapAccess<'de>,
|
||||
{
|
||||
let mut name = None;
|
||||
let mut brace: Option<String> = None;
|
||||
let mut brace: Option<char> = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Name => {
|
||||
@ -104,7 +90,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
|
||||
let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
|
||||
Ok(MacroMatcher {
|
||||
name,
|
||||
braces: [("(", ")"), ("{", "}"), ("[", "]")]
|
||||
braces: [('(', ')'), ('{', '}'), ('[', ']')]
|
||||
.into_iter()
|
||||
.find(|b| b.0 == brace)
|
||||
.map(|(o, c)| (o.to_owned(), c.to_owned()))
|
||||
|
@ -320,8 +320,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
|
||||
extract_msrv_attr!({context_import});
|
||||
}}
|
||||
|
||||
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
|
||||
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
|
||||
// TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed.
|
||||
// TODO: Update msrv config comment in `clippy_config/src/conf.rs`
|
||||
"#
|
||||
)
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.75"
|
||||
version = "0.1.76"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
@ -14,7 +14,6 @@ cargo_metadata = "0.15.3"
|
||||
clippy_config = { path = "../clippy_config" }
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
||||
if_chain = "1.0"
|
||||
itertools = "0.10.1"
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.7"
|
||||
|
@ -52,24 +52,22 @@ declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
|
||||
impl LateLintPass<'_> for AllowAttribute {
|
||||
// Separate each crate's features.
|
||||
fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.sess(), attr.span);
|
||||
if cx.tcx.features().lint_reasons;
|
||||
if let AttrStyle::Outer = attr.style;
|
||||
if let Some(ident) = attr.ident();
|
||||
if ident.name == rustc_span::symbol::sym::allow;
|
||||
if !is_from_proc_macro(cx, &attr);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ALLOW_ATTRIBUTES,
|
||||
ident.span,
|
||||
"#[allow] attribute found",
|
||||
"replace it with",
|
||||
"expect".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
if !in_external_macro(cx.sess(), attr.span)
|
||||
&& cx.tcx.features().lint_reasons
|
||||
&& let AttrStyle::Outer = attr.style
|
||||
&& let Some(ident) = attr.ident()
|
||||
&& ident.name == rustc_span::symbol::sym::allow
|
||||
&& !is_from_proc_macro(cx, &attr)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ALLOW_ATTRIBUTES,
|
||||
ident.span,
|
||||
"#[allow] attribute found",
|
||||
"replace it with",
|
||||
"expect".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,9 @@ declare_clippy_lint! {
|
||||
/// This lint warns when you use `Arc` with a type that does not implement `Send` or `Sync`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `Arc<T>` is only `Send`/`Sync` when `T` is [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send-for-Arc%3CT%3E),
|
||||
/// `Arc<T>` is an Atomic `RC<T>` and guarantees that updates to the reference counter are
|
||||
/// Atomic. This is useful in multiprocessing scenarios. To send an `Arc<T>` across processes
|
||||
/// and make use of the atomic ref counter, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send-for-Arc%3CT%3E),
|
||||
/// either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`
|
||||
///
|
||||
/// ### Example
|
||||
@ -34,7 +36,7 @@ declare_clippy_lint! {
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub ARC_WITH_NON_SEND_SYNC,
|
||||
suspicious,
|
||||
"using `Arc` with a type that does not implement `Send` or `Sync`"
|
||||
"using `Arc` with a type that does not implement `Send` and `Sync`"
|
||||
}
|
||||
declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]);
|
||||
|
||||
@ -61,19 +63,25 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync {
|
||||
cx,
|
||||
ARC_WITH_NON_SEND_SYNC,
|
||||
expr.span,
|
||||
"usage of an `Arc` that is not `Send` or `Sync`",
|
||||
"usage of an `Arc` that is not `Send` and `Sync`",
|
||||
|diag| {
|
||||
with_forced_trimmed_paths!({
|
||||
diag.note(format!("`Arc<{arg_ty}>` is not `Send` and `Sync` as:"));
|
||||
|
||||
if !is_send {
|
||||
diag.note(format!("the trait `Send` is not implemented for `{arg_ty}`"));
|
||||
diag.note(format!("- the trait `Send` is not implemented for `{arg_ty}`"));
|
||||
}
|
||||
if !is_sync {
|
||||
diag.note(format!("the trait `Sync` is not implemented for `{arg_ty}`"));
|
||||
diag.note(format!("- the trait `Sync` is not implemented for `{arg_ty}`"));
|
||||
}
|
||||
|
||||
diag.note(format!("required for `{ty}` to implement `Send` and `Sync`"));
|
||||
diag.help("consider using an `Rc` instead. `Arc` does not provide benefits for non `Send` and `Sync` types");
|
||||
|
||||
diag.help("consider using an `Rc` instead or wrapping the inner type with a `Mutex`");
|
||||
diag.note("if you intend to use `Arc` with `Send` and `Sync` traits");
|
||||
|
||||
diag.note(format!(
|
||||
"wrap the inner type with a `Mutex` or implement `Send` and `Sync` for `{arg_ty}`"
|
||||
));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -5,7 +5,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sug
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::macros::{is_panic, macro_backtrace};
|
||||
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::token::{Token, TokenKind};
|
||||
use rustc_ast::tokenstream::TokenTree;
|
||||
use rustc_ast::{
|
||||
@ -20,7 +19,7 @@ use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{sym, DUMMY_SP, Span};
|
||||
use rustc_span::{sym, Span, DUMMY_SP};
|
||||
use semver::Version;
|
||||
|
||||
static UNIX_SYSTEMS: &[&str] = &[
|
||||
@ -371,7 +370,7 @@ declare_clippy_lint! {
|
||||
/// let _ = 1 / random();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
#[clippy::version = "1.74.0"]
|
||||
pub SHOULD_PANIC_WITHOUT_EXPECT,
|
||||
pedantic,
|
||||
"ensures that all `should_panic` attributes specify its expected panic message"
|
||||
@ -470,13 +469,11 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
return;
|
||||
}
|
||||
for item in items {
|
||||
if_chain! {
|
||||
if let NestedMetaItem::MetaItem(mi) = &item;
|
||||
if let MetaItemKind::NameValue(lit) = &mi.kind;
|
||||
if mi.has_name(sym::since);
|
||||
then {
|
||||
check_semver(cx, item.span(), lit);
|
||||
}
|
||||
if let NestedMetaItem::MetaItem(mi) = &item
|
||||
&& let MetaItemKind::NameValue(lit) = &mi.kind
|
||||
&& mi.has_name(sym::since)
|
||||
{
|
||||
check_semver(cx, item.span(), lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -579,15 +576,13 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
|
||||
/// Returns the lint name if it is clippy lint.
|
||||
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
|
||||
if_chain! {
|
||||
if let Some(meta_item) = lint.meta_item();
|
||||
if meta_item.path.segments.len() > 1;
|
||||
if let tool_name = meta_item.path.segments[0].ident;
|
||||
if tool_name.name == sym::clippy;
|
||||
then {
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
return Some(lint_name);
|
||||
}
|
||||
if let Some(meta_item) = lint.meta_item()
|
||||
&& meta_item.path.segments.len() > 1
|
||||
&& let tool_name = meta_item.path.segments[0].ident
|
||||
&& tool_name.name == sym::clippy
|
||||
{
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
return Some(lint_name);
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -856,18 +851,17 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
|
||||
if_chain! {
|
||||
if msrv.meets(msrvs::TOOL_ATTRIBUTES);
|
||||
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check cfg_attr
|
||||
if attr.has_name(sym::cfg_attr);
|
||||
if let Some(items) = attr.meta_item_list();
|
||||
if items.len() == 2;
|
||||
&& attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
// check for `rustfmt`
|
||||
if let Some(feature_item) = items[0].meta_item();
|
||||
if feature_item.has_name(sym::rustfmt);
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
&& feature_item.has_name(sym::rustfmt)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
if let Some(skip_item) = &items[1].meta_item();
|
||||
if skip_item.has_name(sym!(rustfmt_skip))
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
@ -875,21 +869,20 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip;
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
if attr.style == AttrStyle::Outer;
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -989,12 +982,10 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
mismatched.extend(find_mismatched_target_os(list));
|
||||
},
|
||||
MetaItemKind::Word => {
|
||||
if_chain! {
|
||||
if let Some(ident) = meta.ident();
|
||||
if let Some(os) = find_os(ident.name.as_str());
|
||||
then {
|
||||
mismatched.push((os, ident.span));
|
||||
}
|
||||
if let Some(ident) = meta.ident()
|
||||
&& let Some(os) = find_os(ident.name.as_str())
|
||||
{
|
||||
mismatched.push((os, ident.span));
|
||||
}
|
||||
},
|
||||
MetaItemKind::NameValue(..) => {},
|
||||
@ -1005,30 +996,28 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
mismatched
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if attr.has_name(sym::cfg);
|
||||
if let Some(list) = attr.meta_item_list();
|
||||
let mismatched = find_mismatched_target_os(&list);
|
||||
if !mismatched.is_empty();
|
||||
then {
|
||||
let mess = "operating system used in target family position";
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(list) = attr.meta_item_list()
|
||||
&& let mismatched = find_mismatched_target_os(&list)
|
||||
&& !mismatched.is_empty()
|
||||
{
|
||||
let mess = "operating system used in target family position";
|
||||
|
||||
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
|
||||
// Avoid showing the unix suggestion multiple times in case
|
||||
// we have more than one mismatch for unix-like systems
|
||||
let mut unix_suggested = false;
|
||||
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
|
||||
// Avoid showing the unix suggestion multiple times in case
|
||||
// we have more than one mismatch for unix-like systems
|
||||
let mut unix_suggested = false;
|
||||
|
||||
for (os, span) in mismatched {
|
||||
let sugg = format!("target_os = \"{os}\"");
|
||||
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
|
||||
for (os, span) in mismatched {
|
||||
let sugg = format!("target_os = \"{os}\"");
|
||||
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
|
||||
|
||||
if !unix_suggested && is_unix(os) {
|
||||
diag.help("did you mean `unix`?");
|
||||
unix_suggested = true;
|
||||
}
|
||||
if !unix_suggested && is_unix(os) {
|
||||
diag.help("did you mean `unix`?");
|
||||
unix_suggested = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::visitors::{for_each_expr, Descend};
|
||||
use clippy_utils::{get_parent_expr, higher};
|
||||
use core::ops::ControlFlow;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
@ -114,15 +113,13 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||
let _: Option<!> = for_each_expr(cond, |e| {
|
||||
if let ExprKind::Closure(closure) = e.kind {
|
||||
// do not lint if the closure is called using an iterator (see #1141)
|
||||
if_chain! {
|
||||
if let Some(parent) = get_parent_expr(cx, e);
|
||||
if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
|
||||
let caller = cx.typeck_results().expr_ty(self_arg);
|
||||
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
|
||||
if implements_trait(cx, caller, iter_id, &[]);
|
||||
then {
|
||||
return ControlFlow::Continue(Descend::No);
|
||||
}
|
||||
if let Some(parent) = get_parent_expr(cx, e)
|
||||
&& let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
|
||||
&& let caller = cx.typeck_results().expr_ty(self_arg)
|
||||
&& let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
|
||||
&& implements_trait(cx, caller, iter_id, &[])
|
||||
{
|
||||
return ControlFlow::Continue(Descend::No);
|
||||
}
|
||||
|
||||
let body = cx.tcx.hir().body(closure.body);
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::eq_expr_value;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
|
||||
@ -151,17 +150,15 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
||||
return Ok(Bool::Term(n as u8));
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
|
||||
if implements_ord(self.cx, e_lhs);
|
||||
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
|
||||
if negate(e_binop.node) == Some(expr_binop.node);
|
||||
if eq_expr_value(self.cx, e_lhs, expr_lhs);
|
||||
if eq_expr_value(self.cx, e_rhs, expr_rhs);
|
||||
then {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
|
||||
}
|
||||
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind
|
||||
&& implements_ord(self.cx, e_lhs)
|
||||
&& let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
|
||||
&& negate(e_binop.node) == Some(expr_binop.node)
|
||||
&& eq_expr_value(self.cx, e_lhs, expr_lhs)
|
||||
&& eq_expr_value(self.cx, e_rhs, expr_rhs)
|
||||
{
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
|
||||
}
|
||||
}
|
||||
let n = self.terminals.len();
|
||||
|
@ -49,69 +49,62 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if !e.span.from_expansion();
|
||||
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind;
|
||||
if !addrof_target.span.from_expansion();
|
||||
if let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind;
|
||||
if !deref_target.span.from_expansion();
|
||||
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) );
|
||||
let ref_ty = cx.typeck_results().expr_ty(deref_target);
|
||||
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind();
|
||||
if !is_from_proc_macro(cx, e);
|
||||
then{
|
||||
|
||||
if let Some(parent_expr) = get_parent_expr(cx, e){
|
||||
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..)) &&
|
||||
!is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// modification to `&mut &*x` is different from `&mut x`
|
||||
if matches!(deref_target.kind, ExprKind::Path(..)
|
||||
| ExprKind::Field(..)
|
||||
| ExprKind::Index(..)
|
||||
| ExprKind::Unary(UnOp::Deref, ..))
|
||||
&& matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _)) {
|
||||
return;
|
||||
}
|
||||
if !e.span.from_expansion()
|
||||
&& let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
|
||||
&& !addrof_target.span.from_expansion()
|
||||
&& let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
|
||||
&& !deref_target.span.from_expansion()
|
||||
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
|
||||
&& let ref_ty = cx.typeck_results().expr_ty(deref_target)
|
||||
&& let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind()
|
||||
&& !is_from_proc_macro(cx, e)
|
||||
{
|
||||
if let Some(parent_expr) = get_parent_expr(cx, e) {
|
||||
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..))
|
||||
&& !is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
BORROW_DEREF_REF,
|
||||
e.span,
|
||||
"deref on an immutable reference",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"if you would like to reborrow, try removing `&*`",
|
||||
snippet_opt(cx, deref_target.span).unwrap(),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
|
||||
// has deref trait -> give 2 help
|
||||
// doesn't have deref trait -> give 1 help
|
||||
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait(){
|
||||
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"if you would like to deref, try using `&**`",
|
||||
format!(
|
||||
"&**{}",
|
||||
&snippet_opt(cx, deref_target.span).unwrap(),
|
||||
),
|
||||
Applicability::MaybeIncorrect
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
// modification to `&mut &*x` is different from `&mut x`
|
||||
if matches!(
|
||||
deref_target.kind,
|
||||
ExprKind::Path(..) | ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, ..)
|
||||
) && matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
BORROW_DEREF_REF,
|
||||
e.span,
|
||||
"deref on an immutable reference",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"if you would like to reborrow, try removing `&*`",
|
||||
snippet_opt(cx, deref_target.span).unwrap(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
// has deref trait -> give 2 help
|
||||
// doesn't have deref trait -> give 1 help
|
||||
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() {
|
||||
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"if you would like to deref, try using `&**`",
|
||||
format!("&**{}", &snippet_opt(cx, deref_target.span).unwrap()),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_hir::def_id::LOCAL_CRATE;
|
||||
use rustc_lint::LateContext;
|
||||
@ -15,31 +14,33 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
||||
let mut packages = metadata.packages.clone();
|
||||
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
if_chain! {
|
||||
if let Some(resolve) = &metadata.resolve;
|
||||
if let Some(local_id) = packages
|
||||
.iter()
|
||||
.find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None });
|
||||
then {
|
||||
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
|
||||
let group: Vec<&Package> = group.collect();
|
||||
if let Some(resolve) = &metadata.resolve
|
||||
&& let Some(local_id) = packages.iter().find_map(|p| {
|
||||
if p.name == local_name.as_str() {
|
||||
Some(&p.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
|
||||
let group: Vec<&Package> = group.collect();
|
||||
|
||||
if group.len() <= 1 {
|
||||
continue;
|
||||
}
|
||||
if group.len() <= 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
|
||||
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
|
||||
versions.sort();
|
||||
let versions = versions.iter().join(", ");
|
||||
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
|
||||
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
|
||||
versions.sort();
|
||||
let versions = versions.iter().join(", ");
|
||||
|
||||
span_lint(
|
||||
cx,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
DUMMY_SP,
|
||||
&format!("multiple versions for dependency `{name}`: {versions}"),
|
||||
);
|
||||
}
|
||||
span_lint(
|
||||
cx,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
DUMMY_SP,
|
||||
&format!("multiple versions for dependency `{name}`: {versions}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use cargo_metadata::Metadata;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use if_chain::if_chain;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
@ -9,19 +8,17 @@ use super::WILDCARD_DEPENDENCIES;
|
||||
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
|
||||
for dep in &metadata.packages[0].dependencies {
|
||||
// VersionReq::any() does not work
|
||||
if_chain! {
|
||||
if let Ok(wildcard_ver) = semver::VersionReq::parse("*");
|
||||
if let Some(ref source) = dep.source;
|
||||
if !source.starts_with("git");
|
||||
if dep.req == wildcard_ver;
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
WILDCARD_DEPENDENCIES,
|
||||
DUMMY_SP,
|
||||
&format!("wildcard dependency for `{}`", dep.name),
|
||||
);
|
||||
}
|
||||
if let Ok(wildcard_ver) = semver::VersionReq::parse("*")
|
||||
&& let Some(ref source) = dep.source
|
||||
&& !source.starts_with("git")
|
||||
&& dep.req == wildcard_ver
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
WILDCARD_DEPENDENCIES,
|
||||
DUMMY_SP,
|
||||
&format!("wildcard dependency for `{}`", dep.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
use super::{utils, CAST_POSSIBLE_WRAP};
|
||||
@ -78,13 +79,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
|
||||
),
|
||||
};
|
||||
|
||||
cx.struct_span_lint(CAST_POSSIBLE_WRAP, expr.span, message, |diag| {
|
||||
span_lint_and_then(cx, CAST_POSSIBLE_WRAP, expr.span, &message, |diag| {
|
||||
if let EmitState::LintOnPtrSize(16) = should_lint {
|
||||
diag
|
||||
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
|
||||
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types")
|
||||
} else {
|
||||
diag
|
||||
}
|
||||
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
|
||||
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{method_chain_args, sext};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
@ -28,13 +27,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
||||
|
||||
// Don't lint for positive constants.
|
||||
let const_val = constant(cx, cx.typeck_results(), cast_op);
|
||||
if_chain! {
|
||||
if let Some(Constant::Int(n)) = const_val;
|
||||
if let ty::Int(ity) = *cast_from.kind();
|
||||
if sext(cx.tcx, n, ity) >= 0;
|
||||
then {
|
||||
return false;
|
||||
}
|
||||
if let Some(Constant::Int(n)) = const_val
|
||||
&& let ty::Int(ity) = *cast_from.kind()
|
||||
&& sext(cx.tcx, n, ity) >= 0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't lint for the result of methods that always return non-negative values.
|
||||
@ -42,13 +39,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
|
||||
|
||||
if_chain! {
|
||||
if method_name == "unwrap";
|
||||
if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
|
||||
if let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind;
|
||||
then {
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
}
|
||||
if method_name == "unwrap"
|
||||
&& let Some(arglist) = method_chain_args(cast_op, &["unwrap"])
|
||||
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
|
||||
{
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
}
|
||||
|
||||
if allowed_methods.iter().any(|&name| method_name == name) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
@ -69,26 +68,24 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
|
||||
|
||||
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let map = cx.tcx.hir();
|
||||
if_chain! {
|
||||
if let Some(parent_id) = map.opt_parent_id(expr.hir_id);
|
||||
if let Some(parent) = map.find(parent_id);
|
||||
then {
|
||||
let expr = match parent {
|
||||
Node::Block(block) => {
|
||||
if let Some(parent_expr) = block.expr {
|
||||
parent_expr
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
Node::Expr(expr) => expr,
|
||||
_ => return false,
|
||||
};
|
||||
if let Some(parent_id) = map.opt_parent_id(expr.hir_id)
|
||||
&& let Some(parent) = map.find(parent_id)
|
||||
{
|
||||
let expr = match parent {
|
||||
Node::Block(block) => {
|
||||
if let Some(parent_expr) = block.expr {
|
||||
parent_expr
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
Node::Expr(expr) => expr,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
matches!(expr.kind, ExprKind::Cast(..))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(expr.kind, ExprKind::Cast(..))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -25,34 +24,32 @@ fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> {
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: &Msrv) {
|
||||
if_chain! {
|
||||
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS);
|
||||
if let ty::RawPtr(ptrty) = cast_to.kind();
|
||||
if let ty::Slice(_) = ptrty.ty.kind();
|
||||
if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind;
|
||||
if let ExprKind::Path(ref qpath) = fun.kind;
|
||||
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
|
||||
if let Some(rpk) = raw_parts_kind(cx, fun_def_id);
|
||||
let ctxt = expr.span.ctxt();
|
||||
if cast_expr.span.ctxt() == ctxt;
|
||||
then {
|
||||
let func = match rpk {
|
||||
RawPartsKind::Immutable => "from_raw_parts",
|
||||
RawPartsKind::Mutable => "from_raw_parts_mut"
|
||||
};
|
||||
let span = expr.span;
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
|
||||
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CAST_SLICE_FROM_RAW_PARTS,
|
||||
span,
|
||||
&format!("casting the result of `{func}` to {cast_to}"),
|
||||
"replace with",
|
||||
format!("core::ptr::slice_{func}({ptr}, {len})"),
|
||||
applicability
|
||||
);
|
||||
}
|
||||
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS)
|
||||
&& let ty::RawPtr(ptrty) = cast_to.kind()
|
||||
&& let ty::Slice(_) = ptrty.ty.kind()
|
||||
&& let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind
|
||||
&& let ExprKind::Path(ref qpath) = fun.kind
|
||||
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
|
||||
&& let Some(rpk) = raw_parts_kind(cx, fun_def_id)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& cast_expr.span.ctxt() == ctxt
|
||||
{
|
||||
let func = match rpk {
|
||||
RawPartsKind::Immutable => "from_raw_parts",
|
||||
RawPartsKind::Mutable => "from_raw_parts_mut",
|
||||
};
|
||||
let span = expr.span;
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
|
||||
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CAST_SLICE_FROM_RAW_PARTS,
|
||||
span,
|
||||
&format!("casting the result of `{func}` to {cast_to}"),
|
||||
"replace with",
|
||||
format!("core::ptr::slice_{func}({ptr}, {len})"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -10,32 +9,31 @@ use rustc_middle::ty::{self, UintTy};
|
||||
use super::CHAR_LIT_AS_U8;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Cast(e, _) = &expr.kind;
|
||||
if let ExprKind::Lit(l) = &e.kind;
|
||||
if let LitKind::Char(c) = l.node;
|
||||
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind();
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
|
||||
if let ExprKind::Cast(e, _) = &expr.kind
|
||||
&& let ExprKind::Lit(l) = &e.kind
|
||||
&& let LitKind::Char(c) = l.node
|
||||
&& ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind()
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CHAR_LIT_AS_U8,
|
||||
expr.span,
|
||||
"casting a character literal to `u8` truncates",
|
||||
|diag| {
|
||||
diag.note("`char` is four bytes wide, but `u8` is a single byte");
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CHAR_LIT_AS_U8,
|
||||
expr.span,
|
||||
"casting a character literal to `u8` truncates",
|
||||
|diag| {
|
||||
diag.note("`char` is four bytes wide, but `u8` is a single byte");
|
||||
|
||||
if c.is_ascii() {
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"use a byte literal instead",
|
||||
format!("b{snippet}"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if c.is_ascii() {
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"use a byte literal instead",
|
||||
format!("b{snippet}"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability};
|
||||
use rustc_lint::LateContext;
|
||||
@ -17,29 +16,35 @@ pub(super) fn check<'tcx>(
|
||||
cast_to: Ty<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if_chain! {
|
||||
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS);
|
||||
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, ty: from_ty }) = cast_from.kind();
|
||||
if let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, ty: to_ty }) = cast_to.kind();
|
||||
if matches!((from_mutbl, to_mutbl),
|
||||
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not));
|
||||
if from_ty == to_ty;
|
||||
then {
|
||||
let sugg = Sugg::hir(cx, cast_expr, "_");
|
||||
let constness = match *to_mutbl {
|
||||
Mutability::Not => "const",
|
||||
Mutability::Mut => "mut",
|
||||
};
|
||||
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS)
|
||||
&& let ty::RawPtr(TypeAndMut {
|
||||
mutbl: from_mutbl,
|
||||
ty: from_ty,
|
||||
}) = cast_from.kind()
|
||||
&& let ty::RawPtr(TypeAndMut {
|
||||
mutbl: to_mutbl,
|
||||
ty: to_ty,
|
||||
}) = cast_to.kind()
|
||||
&& matches!(
|
||||
(from_mutbl, to_mutbl),
|
||||
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not)
|
||||
)
|
||||
&& from_ty == to_ty
|
||||
{
|
||||
let sugg = Sugg::hir(cx, cast_expr, "_");
|
||||
let constness = match *to_mutbl {
|
||||
Mutability::Not => "const",
|
||||
Mutability::Mut => "mut",
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PTR_CAST_CONSTNESS,
|
||||
expr.span,
|
||||
"`as` casting between raw pointers while changing only its constness",
|
||||
&format!("try `pointer::cast_{constness}`, a safer alternative"),
|
||||
format!("{}.cast_{constness}()", sugg.maybe_par()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PTR_CAST_CONSTNESS,
|
||||
expr.span,
|
||||
"`as` casting between raw pointers while changing only its constness",
|
||||
&format!("try `pointer::cast_{constness}`, a safer alternative"),
|
||||
format!("{}.cast_{constness}()", sugg.maybe_par()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use clippy_utils::numeric_literal::NumericLiteral;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::visitors::{for_each_expr, Visitable};
|
||||
use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
@ -25,40 +24,40 @@ pub(super) fn check<'tcx>(
|
||||
) -> bool {
|
||||
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
|
||||
|
||||
if_chain! {
|
||||
if let ty::RawPtr(..) = cast_from.kind();
|
||||
if let ty::RawPtr(..) = cast_from.kind()
|
||||
// check both mutability and type are the same
|
||||
if cast_from.kind() == cast_to.kind();
|
||||
if let ExprKind::Cast(_, cast_to_hir) = expr.kind;
|
||||
&& cast_from.kind() == cast_to.kind()
|
||||
&& let ExprKind::Cast(_, cast_to_hir) = expr.kind
|
||||
// Ignore casts to e.g. type aliases and infer types
|
||||
// - p as pointer_alias
|
||||
// - p as _
|
||||
if let TyKind::Ptr(to_pointee) = cast_to_hir.kind;
|
||||
then {
|
||||
match to_pointee.ty.kind {
|
||||
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
|
||||
// - p as *const std::ffi::c_char (alias)
|
||||
// - p as *const std::os::raw::c_char (cfg dependant)
|
||||
TyKind::Path(qpath) => {
|
||||
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Ignore `p as *const _`
|
||||
TyKind::Infer => return false,
|
||||
_ => {},
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_CAST,
|
||||
expr.span,
|
||||
&format!("casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"),
|
||||
"try",
|
||||
cast_str.clone(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
&& let TyKind::Ptr(to_pointee) = cast_to_hir.kind
|
||||
{
|
||||
match to_pointee.ty.kind {
|
||||
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
|
||||
// - p as *const std::ffi::c_char (alias)
|
||||
// - p as *const std::os::raw::c_char (cfg dependant)
|
||||
TyKind::Path(qpath) => {
|
||||
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Ignore `p as *const _`
|
||||
TyKind::Infer => return false,
|
||||
_ => {},
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_CAST,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"
|
||||
),
|
||||
"try",
|
||||
cast_str.clone(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
// skip cast of local that is a type alias
|
||||
@ -86,14 +85,12 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
|
||||
// skip cast to non-primitive type
|
||||
if_chain! {
|
||||
if let ExprKind::Cast(_, cast_to) = expr.kind;
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
|
||||
if let Res::PrimTy(_) = path.res;
|
||||
then {}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
if let ExprKind::Cast(_, cast_to) = expr.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind
|
||||
&& let Res::PrimTy(_) = path.res
|
||||
{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip cast of fn call that returns type alias
|
||||
@ -106,18 +103,19 @@ pub(super) fn check<'tcx>(
|
||||
if let Some(lit) = get_numeric_literal(cast_expr) {
|
||||
let literal_str = &cast_str;
|
||||
|
||||
if_chain! {
|
||||
if let LitKind::Int(n, _) = lit.node;
|
||||
if let Some(src) = snippet_opt(cx, cast_expr.span);
|
||||
if cast_to.is_floating_point();
|
||||
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node);
|
||||
let from_nbits = 128 - n.leading_zeros();
|
||||
let to_nbits = fp_ty_mantissa_nbits(cast_to);
|
||||
if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal();
|
||||
then {
|
||||
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
|
||||
return true
|
||||
}
|
||||
if let LitKind::Int(n, _) = lit.node
|
||||
&& let Some(src) = snippet_opt(cx, cast_expr.span)
|
||||
&& cast_to.is_floating_point()
|
||||
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
|
||||
&& let from_nbits = 128 - n.leading_zeros()
|
||||
&& let to_nbits = fp_ty_mantissa_nbits(cast_to)
|
||||
&& from_nbits != 0
|
||||
&& to_nbits != 0
|
||||
&& from_nbits <= to_nbits
|
||||
&& num_lit.is_decimal()
|
||||
{
|
||||
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
|
||||
return true;
|
||||
}
|
||||
|
||||
match lit.node {
|
||||
|
@ -4,7 +4,6 @@ use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
@ -55,20 +54,17 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = if_chain! {
|
||||
if !in_constant(cx, item.hir_id);
|
||||
if !in_external_macro(cx.sess(), item.span);
|
||||
if let ExprKind::Binary(op, left, right) = &item.kind;
|
||||
|
||||
then {
|
||||
match op.node {
|
||||
BinOpKind::Ge | BinOpKind::Le => single_check(item),
|
||||
BinOpKind::And => double_check(cx, left, right),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
let result = if !in_constant(cx, item.hir_id)
|
||||
&& !in_external_macro(cx.sess(), item.span)
|
||||
&& let ExprKind::Binary(op, left, right) = &item.kind
|
||||
{
|
||||
match op.node {
|
||||
BinOpKind::Ge | BinOpKind::Le => single_check(item),
|
||||
BinOpKind::And => double_check(cx, left, right),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(cv) = result {
|
||||
@ -193,16 +189,13 @@ impl ConversionType {
|
||||
|
||||
/// Check for `expr <= (to_type::MAX as from_type)`
|
||||
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op, left, right) = &expr.kind;
|
||||
if let Some((candidate, check)) = normalize_le_ge(op, left, right);
|
||||
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
|
||||
|
||||
then {
|
||||
Conversion::try_new(candidate, from, to)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let ExprKind::Binary(ref op, left, right) = &expr.kind
|
||||
&& let Some((candidate, check)) = normalize_le_ge(op, left, right)
|
||||
&& let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX")
|
||||
{
|
||||
Conversion::try_new(candidate, from, to)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,33 +236,27 @@ fn get_types_from_cast<'a>(
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
// `to_type::max_value() as from_type`
|
||||
// or `to_type::MAX as from_type`
|
||||
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
|
||||
let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind
|
||||
// to_type::max_value(), from_type
|
||||
if let ExprKind::Cast(limit, from_type) = &expr.kind;
|
||||
if let TyKind::Path(ref from_type_path) = &from_type.kind;
|
||||
if let Some(from_sym) = int_ty_to_sym(from_type_path);
|
||||
|
||||
then {
|
||||
Some((limit, from_sym))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let TyKind::Path(ref from_type_path) = &from_type.kind
|
||||
&& let Some(from_sym) = int_ty_to_sym(from_type_path)
|
||||
{
|
||||
Some((limit, from_sym))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// `from_type::from(to_type::max_value())`
|
||||
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(from_func, [limit]) = &expr.kind
|
||||
// `from_type::from, to_type::max_value()`
|
||||
if let ExprKind::Call(from_func, [limit]) = &expr.kind;
|
||||
// `from_type::from`
|
||||
if let ExprKind::Path(ref path) = &from_func.kind;
|
||||
if let Some(from_sym) = get_implementing_type(path, INTS, "from");
|
||||
|
||||
then {
|
||||
Some((limit, from_sym))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let ExprKind::Path(ref path) = &from_func.kind
|
||||
&& let Some(from_sym) = get_implementing_type(path, INTS, "from")
|
||||
{
|
||||
Some((limit, from_sym))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
@ -298,31 +285,27 @@ fn get_types_from_cast<'a>(
|
||||
|
||||
/// Gets the type which implements the called function
|
||||
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
|
||||
if_chain! {
|
||||
if let QPath::TypeRelative(ty, path) = &path;
|
||||
if path.ident.name.as_str() == function;
|
||||
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
|
||||
if let [int] = tp.segments;
|
||||
then {
|
||||
let name = int.ident.name.as_str();
|
||||
candidates.iter().find(|c| &name == *c).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let QPath::TypeRelative(ty, path) = &path
|
||||
&& path.ident.name.as_str() == function
|
||||
&& let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind
|
||||
&& let [int] = tp.segments
|
||||
{
|
||||
let name = int.ident.name.as_str();
|
||||
candidates.iter().find(|c| &name == *c).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the type as a string, if it is a supported integer
|
||||
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
|
||||
if_chain! {
|
||||
if let QPath::Resolved(_, path) = *path;
|
||||
if let [ty] = path.segments;
|
||||
then {
|
||||
let name = ty.ident.name.as_str();
|
||||
INTS.iter().find(|c| &name == *c).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let QPath::Resolved(_, path) = *path
|
||||
&& let [ty] = path.segments
|
||||
{
|
||||
let name = ty.ident.name.as_str();
|
||||
INTS.iter().find(|c| &name == *c).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
@ -121,49 +120,55 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
|
||||
}
|
||||
|
||||
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
|
||||
if_chain! {
|
||||
if let ast::ExprKind::Block(ref block, _) = else_.kind;
|
||||
if !block_starts_with_comment(cx, block);
|
||||
if let Some(else_) = expr_block(block);
|
||||
if else_.attrs.is_empty();
|
||||
if !else_.span.from_expansion();
|
||||
if let ast::ExprKind::If(..) = else_.kind;
|
||||
then {
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let up_to_else = then_span.between(block.span);
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { !c.is_whitespace() } else { false };
|
||||
if let ast::ExprKind::Block(ref block, _) = else_.kind
|
||||
&& !block_starts_with_comment(cx, block)
|
||||
&& let Some(else_) = expr_block(block)
|
||||
&& else_.attrs.is_empty()
|
||||
&& !else_.span.from_expansion()
|
||||
&& let ast::ExprKind::If(..) = else_.kind
|
||||
{
|
||||
// Prevent "elseif"
|
||||
// Check that the "else" is followed by whitespace
|
||||
let up_to_else = then_span.between(block.span);
|
||||
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
|
||||
!c.is_whitespace()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
"collapse nested if block",
|
||||
format!(
|
||||
"{}{}",
|
||||
if requires_space { " " } else { "" },
|
||||
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
|
||||
if_chain! {
|
||||
if !block_starts_with_comment(cx, then);
|
||||
if let Some(inner) = expr_block(then);
|
||||
if inner.attrs.is_empty();
|
||||
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
|
||||
if !block_starts_with_comment(cx, then)
|
||||
&& let Some(inner) = expr_block(then)
|
||||
&& inner.attrs.is_empty()
|
||||
&& let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind
|
||||
// Prevent triggering on `if c { if let a = b { .. } }`.
|
||||
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
|
||||
let ctxt = expr.span.ctxt();
|
||||
if inner.span.ctxt() == ctxt;
|
||||
then {
|
||||
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
|
||||
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& inner.span.ctxt() == ctxt
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_IF,
|
||||
expr.span,
|
||||
"this `if` statement can be collapsed",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
|
||||
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
|
||||
@ -177,8 +182,8 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
|
||||
),
|
||||
app, // snippet
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ declare_clippy_lint! {
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IF_SAME_THEN_ELSE,
|
||||
correctness,
|
||||
style,
|
||||
"`if` with the same `then` and `else` blocks"
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,6 @@ use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
use if_chain::if_chain;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for types that implement `Copy` as well as
|
||||
@ -38,25 +36,23 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CopyIterator {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if_chain! {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
..
|
||||
}) = item.kind;
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
if is_copy(cx, ty);
|
||||
if let Some(trait_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id);
|
||||
then {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
COPY_ITERATOR,
|
||||
item.span,
|
||||
"you are implementing `Iterator` on a `Copy` type",
|
||||
None,
|
||||
"consider implementing `IntoIterator` instead",
|
||||
);
|
||||
}
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
..
|
||||
}) = item.kind
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& is_copy(cx, ty)
|
||||
&& let Some(trait_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Iterator, trait_id)
|
||||
{
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
COPY_ITERATOR,
|
||||
item.span,
|
||||
"you are implementing `Iterator` on a `Copy` type",
|
||||
None,
|
||||
"consider implementing `IntoIterator` instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,35 +53,31 @@ declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
|
||||
|
||||
impl EarlyLintPass for CrateInMacroDef {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if_chain! {
|
||||
if item.attrs.iter().any(is_macro_export);
|
||||
if let ItemKind::MacroDef(macro_def) = &item.kind;
|
||||
let tts = macro_def.body.tokens.clone();
|
||||
if let Some(span) = contains_unhygienic_crate_reference(&tts);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CRATE_IN_MACRO_DEF,
|
||||
span,
|
||||
"`crate` references the macro call's crate",
|
||||
"to reference the macro definition's crate, use",
|
||||
String::from("$crate"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
if item.attrs.iter().any(is_macro_export)
|
||||
&& let ItemKind::MacroDef(macro_def) = &item.kind
|
||||
&& let tts = macro_def.body.tokens.clone()
|
||||
&& let Some(span) = contains_unhygienic_crate_reference(&tts)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CRATE_IN_MACRO_DEF,
|
||||
span,
|
||||
"`crate` references the macro call's crate",
|
||||
"to reference the macro definition's crate, use",
|
||||
String::from("$crate"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_macro_export(attr: &Attribute) -> bool {
|
||||
if_chain! {
|
||||
if let AttrKind::Normal(normal) = &attr.kind;
|
||||
if let [segment] = normal.item.path.segments.as_slice();
|
||||
then {
|
||||
segment.ident.name == sym::macro_export
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let AttrKind::Normal(normal) = &attr.kind
|
||||
&& let [segment] = normal.item.path.segments.as_slice()
|
||||
{
|
||||
segment.ident.name == sym::macro_export
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,14 +85,12 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
|
||||
let mut prev_is_dollar = false;
|
||||
let mut cursor = tts.trees();
|
||||
while let Some(curr) = cursor.next() {
|
||||
if_chain! {
|
||||
if !prev_is_dollar;
|
||||
if let Some(span) = is_crate_keyword(curr);
|
||||
if let Some(next) = cursor.look_ahead(0);
|
||||
if is_token(next, &TokenKind::ModSep);
|
||||
then {
|
||||
return Some(span);
|
||||
}
|
||||
if !prev_is_dollar
|
||||
&& let Some(span) = is_crate_keyword(curr)
|
||||
&& let Some(next) = cursor.look_ahead(0)
|
||||
&& is_token(next, &TokenKind::ModSep)
|
||||
{
|
||||
return Some(span);
|
||||
}
|
||||
if let TokenTree::Delimited(_, _, tts) = &curr {
|
||||
let span = contains_unhygienic_crate_reference(tts);
|
||||
@ -110,10 +104,18 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
|
||||
}
|
||||
|
||||
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
|
||||
if_chain! {
|
||||
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt;
|
||||
if symbol.as_str() == "crate";
|
||||
then { Some(*span) } else { None }
|
||||
if let TokenTree::Token(
|
||||
Token {
|
||||
kind: TokenKind::Ident(symbol, _),
|
||||
span,
|
||||
},
|
||||
_,
|
||||
) = tt
|
||||
&& symbol.as_str() == "crate"
|
||||
{
|
||||
Some(*span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -33,22 +32,20 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
|
||||
|
||||
impl LateLintPass<'_> for CreateDir {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(func, [arg, ..]) = expr.kind;
|
||||
if let ExprKind::Path(ref path) = func.kind;
|
||||
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CREATE_DIR,
|
||||
expr.span,
|
||||
"calling `std::fs::create_dir` where there may be a better way",
|
||||
"consider calling `std::fs::create_dir_all` instead",
|
||||
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
}
|
||||
if let ExprKind::Call(func, [arg, ..]) = expr.kind
|
||||
&& let ExprKind::Path(ref path) = func.kind
|
||||
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CREATE_DIR,
|
||||
expr.span,
|
||||
"calling `std::fs::create_dir` where there may be a better way",
|
||||
"consider calling `std::fs::create_dir_all` instead",
|
||||
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, BytePos, Pos, Span};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -31,31 +31,6 @@ declare_clippy_lint! {
|
||||
"`dbg!` macro is intended as a debugging tool"
|
||||
}
|
||||
|
||||
/// Gets the span of the statement up to the next semicolon, if and only if the next
|
||||
/// non-whitespace character actually is a semicolon.
|
||||
/// E.g.
|
||||
/// ```rust,ignore
|
||||
///
|
||||
/// dbg!();
|
||||
/// ^^^^^^^ this span is returned
|
||||
///
|
||||
/// foo!(dbg!());
|
||||
/// no span is returned
|
||||
/// ```
|
||||
fn span_including_semi(cx: &LateContext<'_>, span: Span) -> Option<Span> {
|
||||
let sm = cx.sess().source_map();
|
||||
let sf = sm.lookup_source_file(span.hi());
|
||||
let src = sf.src.as_ref()?.get(span.hi().to_usize()..)?;
|
||||
let first_non_whitespace = src.find(|c: char| !c.is_whitespace())?;
|
||||
|
||||
if src.as_bytes()[first_non_whitespace] == b';' {
|
||||
let hi = span.hi() + BytePos::from_usize(first_non_whitespace + 1);
|
||||
Some(span.with_hi(hi))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DbgMacro {
|
||||
allow_dbg_in_tests: bool,
|
||||
@ -88,10 +63,10 @@ impl LateLintPass<'_> for DbgMacro {
|
||||
ExprKind::Block(..) => {
|
||||
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
|
||||
// remove the whole statement.
|
||||
if let Some(Node::Stmt(stmt)) = cx.tcx.hir().find_parent(expr.hir_id)
|
||||
&& let Some(span) = span_including_semi(cx, stmt.span.source_callsite())
|
||||
if let Some(Node::Stmt(_)) = cx.tcx.hir().find_parent(expr.hir_id)
|
||||
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
|
||||
{
|
||||
(span, String::new())
|
||||
(macro_call.span.to(semi_span), String::new())
|
||||
} else {
|
||||
(macro_call.span, String::from("()"))
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::if_chain_style::IF_CHAIN_STYLE_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
|
||||
#[cfg(feature = "internal")]
|
||||
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
|
||||
@ -141,6 +139,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::doc::MISSING_PANICS_DOC_INFO,
|
||||
crate::doc::MISSING_SAFETY_DOC_INFO,
|
||||
crate::doc::NEEDLESS_DOCTEST_MAIN_INFO,
|
||||
crate::doc::SUSPICIOUS_DOC_COMMENTS_INFO,
|
||||
crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
|
||||
crate::double_parens::DOUBLE_PARENS_INFO,
|
||||
crate::drop_forget_ref::DROP_NON_DROP_INFO,
|
||||
@ -232,6 +231,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
|
||||
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
|
||||
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
|
||||
crate::iter_over_hash_type::ITER_OVER_HASH_TYPE_INFO,
|
||||
crate::iter_without_into_iter::INTO_ITER_WITHOUT_ITER_INFO,
|
||||
crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO,
|
||||
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
|
||||
@ -628,7 +628,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::strings::STR_TO_STRING_INFO,
|
||||
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
|
||||
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
|
||||
crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO,
|
||||
crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
|
||||
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
|
||||
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{has_drop, is_copy};
|
||||
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
@ -81,33 +80,31 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if !expr.span.from_expansion();
|
||||
if !expr.span.from_expansion()
|
||||
// Avoid cases already linted by `field_reassign_with_default`
|
||||
if !self.reassigned_linted.contains(&expr.span);
|
||||
if let ExprKind::Call(path, ..) = expr.kind;
|
||||
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
|
||||
if !is_update_syntax_base(cx, expr);
|
||||
&& !self.reassigned_linted.contains(&expr.span)
|
||||
&& let ExprKind::Call(path, ..) = expr.kind
|
||||
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
|
||||
&& let ExprKind::Path(ref qpath) = path.kind
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
||||
&& !is_update_syntax_base(cx, expr)
|
||||
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
|
||||
if let QPath::Resolved(None, _path) = qpath;
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||
if let ty::Adt(def, ..) = expr_ty.kind();
|
||||
if !is_from_proc_macro(cx, expr);
|
||||
then {
|
||||
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEFAULT_TRAIT_ACCESS,
|
||||
expr.span,
|
||||
&format!("calling `{replacement}` is more clear than this expression"),
|
||||
"try",
|
||||
replacement,
|
||||
Applicability::Unspecified, // First resolve the TODO above
|
||||
);
|
||||
}
|
||||
&& let QPath::Resolved(None, _path) = qpath
|
||||
&& let expr_ty = cx.typeck_results().expr_ty(expr)
|
||||
&& let ty::Adt(def, ..) = expr_ty.kind()
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
{
|
||||
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEFAULT_TRAIT_ACCESS,
|
||||
expr.span,
|
||||
&format!("calling `{replacement}` is more clear than this expression"),
|
||||
"try",
|
||||
replacement,
|
||||
Applicability::Unspecified, // First resolve the TODO above
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,38 +121,36 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
|
||||
// `default` method of the `Default` trait, and store statement index in current block being
|
||||
// checked and the name of the bound variable
|
||||
let (local, variant, binding_name, binding_type, span) = if_chain! {
|
||||
let (local, variant, binding_name, binding_type, span) = if let StmtKind::Local(local) = stmt.kind
|
||||
// only take `let ...` statements
|
||||
if let StmtKind::Local(local) = stmt.kind;
|
||||
if let Some(expr) = local.init;
|
||||
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
|
||||
if !expr.span.from_expansion();
|
||||
&& let Some(expr) = local.init
|
||||
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
|
||||
&& !expr.span.from_expansion()
|
||||
// only take bindings to identifiers
|
||||
if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
|
||||
&& let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind
|
||||
// only when assigning `... = Default::default()`
|
||||
if is_expr_default(expr, cx);
|
||||
let binding_type = cx.typeck_results().node_type(binding_id);
|
||||
if let Some(adt) = binding_type.ty_adt_def();
|
||||
if adt.is_struct();
|
||||
let variant = adt.non_enum_variant();
|
||||
if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
|
||||
let module_did = cx.tcx.parent_module(stmt.hir_id);
|
||||
if variant
|
||||
&& is_expr_default(expr, cx)
|
||||
&& let binding_type = cx.typeck_results().node_type(binding_id)
|
||||
&& let Some(adt) = binding_type.ty_adt_def()
|
||||
&& adt.is_struct()
|
||||
&& let variant = adt.non_enum_variant()
|
||||
&& (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
|
||||
&& let module_did = cx.tcx.parent_module(stmt.hir_id)
|
||||
&& variant
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
|
||||
let all_fields_are_copy = variant
|
||||
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx))
|
||||
&& let all_fields_are_copy = variant
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| {
|
||||
is_copy(cx, cx.tcx.type_of(field.did).instantiate_identity())
|
||||
});
|
||||
if !has_drop(cx, binding_type) || all_fields_are_copy;
|
||||
then {
|
||||
(local, variant, ident.name, binding_type, expr.span)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
})
|
||||
&& (!has_drop(cx, binding_type) || all_fields_are_copy)
|
||||
{
|
||||
(local, variant, ident.name, binding_type, expr.span)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let init_ctxt = local.span.ctxt();
|
||||
@ -216,21 +211,19 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
.join(", ");
|
||||
|
||||
// give correct suggestion if generics are involved (see #6944)
|
||||
let binding_type = if_chain! {
|
||||
if let ty::Adt(adt_def, args) = binding_type.kind();
|
||||
if !args.is_empty();
|
||||
then {
|
||||
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
|
||||
let generic_args = args.iter().collect::<Vec<_>>();
|
||||
let tys_str = generic_args
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{adt_def_ty_name}::<{}>", &tys_str)
|
||||
} else {
|
||||
binding_type.to_string()
|
||||
}
|
||||
let binding_type = if let ty::Adt(adt_def, args) = binding_type.kind()
|
||||
&& !args.is_empty()
|
||||
{
|
||||
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
|
||||
let generic_args = args.iter().collect::<Vec<_>>();
|
||||
let tys_str = generic_args
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{adt_def_ty_name}::<{}>", &tys_str)
|
||||
} else {
|
||||
binding_type.to_string()
|
||||
};
|
||||
|
||||
let sugg = if ext_with_default {
|
||||
@ -260,48 +253,42 @@ impl<'tcx> LateLintPass<'tcx> for Default {
|
||||
|
||||
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
|
||||
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(fn_expr, _) = &expr.kind;
|
||||
if let ExprKind::Path(qpath) = &fn_expr.kind;
|
||||
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
|
||||
then {
|
||||
// right hand side of assignment is `Default::default`
|
||||
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let ExprKind::Call(fn_expr, _) = &expr.kind
|
||||
&& let ExprKind::Path(qpath) = &fn_expr.kind
|
||||
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
|
||||
{
|
||||
// right hand side of assignment is `Default::default`
|
||||
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
|
||||
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
|
||||
if_chain! {
|
||||
if let StmtKind::Semi(later_expr) = this.kind
|
||||
// only take assignments
|
||||
if let StmtKind::Semi(later_expr) = this.kind;
|
||||
if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
|
||||
&& let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind
|
||||
// only take assignments to fields where the left-hand side field is a field of
|
||||
// the same binding as the previous statement
|
||||
if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
|
||||
if let Some(second_binding_name) = path.segments.last();
|
||||
if second_binding_name.ident.name == binding_name;
|
||||
then {
|
||||
Some((field_ident, assign_rhs))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let ExprKind::Field(binding, field_ident) = assign_lhs.kind
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind
|
||||
&& let Some(second_binding_name) = path.segments.last()
|
||||
&& second_binding_name.ident.name == binding_name
|
||||
{
|
||||
Some((field_ident, assign_rhs))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
|
||||
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(parent) = get_parent_expr(cx, expr);
|
||||
if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
|
||||
then {
|
||||
base.hir_id == expr.hir_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
|
||||
{
|
||||
base.hir_id == expr.hir_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -56,32 +56,30 @@ fn is_alias(ty: hir::Ty<'_>) -> bool {
|
||||
|
||||
impl LateLintPass<'_> for DefaultConstructedUnitStructs {
|
||||
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
|
||||
if_chain!(
|
||||
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind
|
||||
// make sure we have a call to `Default::default`
|
||||
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind;
|
||||
if let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind;
|
||||
&& let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind
|
||||
// make sure this isn't a type alias:
|
||||
// `<Foo as Bar>::Assoc` cannot be used as a constructor
|
||||
if !is_alias(*base);
|
||||
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
|
||||
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
|
||||
&& !is_alias(*base)
|
||||
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
||||
// make sure we have a struct with no fields (unit struct)
|
||||
if let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind();
|
||||
if def.is_struct();
|
||||
if let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant();
|
||||
if !var.is_field_list_non_exhaustive();
|
||||
if !expr.span.from_expansion() && !qpath.span().from_expansion();
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
|
||||
expr.span.with_lo(qpath.qself_span().hi()),
|
||||
"use of `default` to create a unit struct",
|
||||
"remove this call to `default`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
}
|
||||
);
|
||||
&& let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind()
|
||||
&& def.is_struct()
|
||||
&& let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant()
|
||||
&& !var.is_field_list_non_exhaustive()
|
||||
&& !expr.span.from_expansion() && !qpath.span().from_expansion()
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
|
||||
expr.span.with_lo(qpath.qself_span().hi()),
|
||||
"use of `default` to create a unit struct",
|
||||
"remove this call to `default`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{get_parent_node, numeric_literal};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
||||
@ -82,40 +81,40 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
|
||||
|
||||
/// Check whether a passed literal has potential to cause fallback or not.
|
||||
fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) {
|
||||
if_chain! {
|
||||
if !in_external_macro(self.cx.sess(), lit.span);
|
||||
if matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)));
|
||||
if matches!(lit.node,
|
||||
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
|
||||
then {
|
||||
let (suffix, is_float) = match lit_ty.kind() {
|
||||
ty::Int(IntTy::I32) => ("i32", false),
|
||||
ty::Float(FloatTy::F64) => ("f64", true),
|
||||
// Default numeric fallback never results in other types.
|
||||
_ => return,
|
||||
};
|
||||
if !in_external_macro(self.cx.sess(), lit.span)
|
||||
&& matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)))
|
||||
&& matches!(
|
||||
lit.node,
|
||||
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
|
||||
)
|
||||
{
|
||||
let (suffix, is_float) = match lit_ty.kind() {
|
||||
ty::Int(IntTy::I32) => ("i32", false),
|
||||
ty::Float(FloatTy::F64) => ("f64", true),
|
||||
// Default numeric fallback never results in other types.
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
|
||||
src
|
||||
} else {
|
||||
match lit.node {
|
||||
LitKind::Int(src, _) => format!("{src}"),
|
||||
LitKind::Float(src, _) => format!("{src}"),
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
DEFAULT_NUMERIC_FALLBACK,
|
||||
emit_hir_id,
|
||||
lit.span,
|
||||
"default numeric fallback might occur",
|
||||
|diag| {
|
||||
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
|
||||
}
|
||||
);
|
||||
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
|
||||
src
|
||||
} else {
|
||||
match lit.node {
|
||||
LitKind::Int(src, _) => format!("{src}"),
|
||||
LitKind::Float(src, _) => format!("{src}"),
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
DEFAULT_NUMERIC_FALLBACK,
|
||||
emit_hir_id,
|
||||
lit.span,
|
||||
"default numeric fallback might occur",
|
||||
|diag| {
|
||||
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,36 +148,33 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
|
||||
ExprKind::Struct(_, fields, base) => {
|
||||
let ty = self.cx.typeck_results().expr_ty(expr);
|
||||
if_chain! {
|
||||
if let Some(adt_def) = ty.ty_adt_def();
|
||||
if adt_def.is_struct();
|
||||
if let Some(variant) = adt_def.variants().iter().next();
|
||||
then {
|
||||
let fields_def = &variant.fields;
|
||||
if let Some(adt_def) = ty.ty_adt_def()
|
||||
&& adt_def.is_struct()
|
||||
&& let Some(variant) = adt_def.variants().iter().next()
|
||||
{
|
||||
let fields_def = &variant.fields;
|
||||
|
||||
// Push field type then visit each field expr.
|
||||
for field in *fields {
|
||||
let bound =
|
||||
fields_def
|
||||
.iter()
|
||||
.find_map(|f_def| {
|
||||
if f_def.ident(self.cx.tcx) == field.ident
|
||||
{ Some(self.cx.tcx.type_of(f_def.did).instantiate_identity()) }
|
||||
else { None }
|
||||
});
|
||||
self.ty_bounds.push(bound.into());
|
||||
self.visit_expr(field.expr);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
|
||||
// Visit base with no bound.
|
||||
if let Some(base) = base {
|
||||
self.ty_bounds.push(ExplicitTyBound(false));
|
||||
self.visit_expr(base);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
return;
|
||||
// Push field type then visit each field expr.
|
||||
for field in *fields {
|
||||
let bound = fields_def.iter().find_map(|f_def| {
|
||||
if f_def.ident(self.cx.tcx) == field.ident {
|
||||
Some(self.cx.tcx.type_of(f_def.did).instantiate_identity())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
self.ty_bounds.push(bound.into());
|
||||
self.visit_expr(field.expr);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
|
||||
// Visit base with no bound.
|
||||
if let Some(base) = base {
|
||||
self.ty_bounds.push(ExplicitTyBound(false));
|
||||
self.visit_expr(base);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{implements_trait, peel_mid_ty_refs};
|
||||
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
|
||||
use clippy_utils::{
|
||||
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
|
||||
};
|
||||
use core::mem;
|
||||
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_errors::Applicability;
|
||||
@ -170,9 +171,7 @@ pub struct Dereferencing<'tcx> {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StateData<'tcx> {
|
||||
/// Span of the top level expression
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
first_expr: &'tcx Expr<'tcx>,
|
||||
adjusted_ty: Ty<'tcx>,
|
||||
}
|
||||
|
||||
@ -198,6 +197,7 @@ enum State {
|
||||
},
|
||||
ExplicitDerefField {
|
||||
name: Symbol,
|
||||
derefs_manually_drop: bool,
|
||||
},
|
||||
Reborrow {
|
||||
mutability: Mutability,
|
||||
@ -242,7 +242,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
// Stop processing sub expressions when a macro call is seen
|
||||
if expr.span.from_expansion() {
|
||||
if let Some((state, data)) = self.state.take() {
|
||||
report(cx, expr, state, data);
|
||||
report(cx, expr, state, data, cx.typeck_results());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -251,7 +251,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
let Some((kind, sub_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
|
||||
// The whole chain of reference operations has been seen
|
||||
if let Some((state, data)) = self.state.take() {
|
||||
report(cx, expr, state, data);
|
||||
report(cx, expr, state, data, typeck);
|
||||
}
|
||||
return;
|
||||
};
|
||||
@ -272,14 +272,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
(Some(use_cx), RefOp::Deref) => {
|
||||
let sub_ty = typeck.expr_ty(sub_expr);
|
||||
if let ExprUseNode::FieldAccess(name) = use_cx.node
|
||||
&& adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union())
|
||||
&& !use_cx.moved_before_use
|
||||
&& !ty_contains_field(sub_ty, name.name)
|
||||
{
|
||||
self.state = Some((
|
||||
State::ExplicitDerefField { name: name.name },
|
||||
State::ExplicitDerefField {
|
||||
name: name.name,
|
||||
derefs_manually_drop: is_manually_drop(sub_ty),
|
||||
},
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -293,8 +295,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
self.state = Some((
|
||||
State::ExplicitDeref { mutability: None },
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -313,8 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
mutbl,
|
||||
},
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -342,8 +342,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
|
||||
});
|
||||
let can_auto_borrow = match use_cx.node {
|
||||
ExprUseNode::Callee => true,
|
||||
ExprUseNode::FieldAccess(_) => adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()),
|
||||
ExprUseNode::FieldAccess(_)
|
||||
if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
|
||||
{
|
||||
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
|
||||
// field expressions when the base type is a union and the parent
|
||||
// expression is also a field access.
|
||||
//
|
||||
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
|
||||
// deref through `ManuallyDrop<_>` will not compile.
|
||||
!adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
|
||||
},
|
||||
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) => true,
|
||||
ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
|
||||
// Check for calls to trait methods where the trait is implemented
|
||||
// on a reference.
|
||||
@ -357,11 +367,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
.tcx
|
||||
.erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target))
|
||||
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
|
||||
&& let args = cx
|
||||
.typeck_results()
|
||||
.node_args_opt(hir_id)
|
||||
.map(|args| &args[1..])
|
||||
.unwrap_or_default()
|
||||
&& let args =
|
||||
typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default()
|
||||
&& let impl_ty =
|
||||
if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder().inputs()[0]
|
||||
.is_ref()
|
||||
@ -436,14 +443,16 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
count: deref_count - required_refs,
|
||||
msg,
|
||||
stability,
|
||||
for_field_access: match use_cx.node {
|
||||
ExprUseNode::FieldAccess(name) => Some(name.name),
|
||||
_ => None,
|
||||
for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node
|
||||
&& !use_cx.moved_before_use
|
||||
{
|
||||
Some(name.name)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
|
||||
},
|
||||
));
|
||||
@ -455,8 +464,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
self.state = Some((
|
||||
State::Borrow { mutability },
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
|
||||
},
|
||||
));
|
||||
@ -501,13 +509,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => {
|
||||
let adjusted_ty = data.adjusted_ty;
|
||||
let stability = state.stability;
|
||||
report(cx, expr, State::DerefedBorrow(state), data);
|
||||
report(cx, expr, State::DerefedBorrow(state), data, typeck);
|
||||
if stability.is_deref_stable() {
|
||||
self.state = Some((
|
||||
State::Borrow { mutability },
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -517,15 +524,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
let adjusted_ty = data.adjusted_ty;
|
||||
let stability = state.stability;
|
||||
let for_field_access = state.for_field_access;
|
||||
report(cx, expr, State::DerefedBorrow(state), data);
|
||||
report(cx, expr, State::DerefedBorrow(state), data, typeck);
|
||||
if let Some(name) = for_field_access
|
||||
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
|
||||
&& let sub_expr_ty = typeck.expr_ty(sub_expr)
|
||||
&& !ty_contains_field(sub_expr_ty, name)
|
||||
{
|
||||
self.state = Some((
|
||||
State::ExplicitDerefField { name },
|
||||
State::ExplicitDerefField {
|
||||
name,
|
||||
derefs_manually_drop: is_manually_drop(sub_expr_ty),
|
||||
},
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
first_expr: expr,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -535,8 +545,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
self.state = Some((
|
||||
State::ExplicitDeref { mutability: None },
|
||||
StateData {
|
||||
span: parent.span,
|
||||
hir_id: parent.hir_id,
|
||||
first_expr: parent,
|
||||
adjusted_ty,
|
||||
},
|
||||
));
|
||||
@ -566,13 +575,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
|
||||
self.state = state;
|
||||
},
|
||||
(Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
|
||||
if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
|
||||
(
|
||||
Some((
|
||||
State::ExplicitDerefField {
|
||||
name,
|
||||
derefs_manually_drop,
|
||||
},
|
||||
data,
|
||||
)),
|
||||
RefOp::Deref,
|
||||
) if let sub_expr_ty = typeck.expr_ty(sub_expr)
|
||||
&& !ty_contains_field(sub_expr_ty, name) =>
|
||||
{
|
||||
self.state = Some((State::ExplicitDerefField { name }, data));
|
||||
self.state = Some((
|
||||
State::ExplicitDerefField {
|
||||
name,
|
||||
derefs_manually_drop: derefs_manually_drop || is_manually_drop(sub_expr_ty),
|
||||
},
|
||||
data,
|
||||
));
|
||||
},
|
||||
|
||||
(Some((state, data)), _) => report(cx, expr, state, data),
|
||||
(Some((state, data)), _) => report(cx, expr, state, data, typeck),
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,26 +621,24 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if !pat.span.from_expansion();
|
||||
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
|
||||
if !pat.span.from_expansion()
|
||||
&& let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind()
|
||||
// only lint immutable refs, because borrowed `&mut T` cannot be moved out
|
||||
if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
|
||||
then {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
|
||||
self.current_body = self.current_body.or(cx.enclosing_body);
|
||||
self.ref_locals.insert(
|
||||
id,
|
||||
Some(RefPat {
|
||||
always_deref: true,
|
||||
spans: vec![pat.span],
|
||||
app,
|
||||
replacements: vec![(pat.span, snip.into())],
|
||||
hir_id: pat.hir_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
&& let ty::Ref(_, _, Mutability::Not) = *tam.kind()
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
|
||||
self.current_body = self.current_body.or(cx.enclosing_body);
|
||||
self.ref_locals.insert(
|
||||
id,
|
||||
Some(RefPat {
|
||||
always_deref: true,
|
||||
spans: vec![pat.span],
|
||||
app,
|
||||
replacements: vec![(pat.span, snip.into())],
|
||||
hir_id: pat.hir_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -689,6 +711,14 @@ fn try_parse_ref_op<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
|
||||
fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
|
||||
adjustments.iter().any(|a| {
|
||||
let ty = mem::replace(&mut ty, a.target);
|
||||
matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
|
||||
})
|
||||
}
|
||||
|
||||
// Checks whether the type for a deref call actually changed the type, not just the mutability of
|
||||
// the reference.
|
||||
fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
|
||||
@ -898,7 +928,13 @@ fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
|
||||
}
|
||||
|
||||
#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
|
||||
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData<'tcx>) {
|
||||
fn report<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
state: State,
|
||||
data: StateData<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'tcx>,
|
||||
) {
|
||||
match state {
|
||||
State::DerefMethod {
|
||||
ty_changed_count,
|
||||
@ -906,8 +942,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
mutbl,
|
||||
} => {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
let (expr_str, _expr_is_macro_call) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
let ty = typeck.expr_ty(expr);
|
||||
let (_, ref_count) = peel_mid_ty_refs(ty);
|
||||
let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
|
||||
// a deref call changing &T -> &U requires two deref operators the first time
|
||||
@ -947,7 +984,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_DEREF_METHODS,
|
||||
data.span,
|
||||
data.first_expr.span,
|
||||
match mutbl {
|
||||
Mutability::Not => "explicit `deref` method call",
|
||||
Mutability::Mut => "explicit `deref_mut` method call",
|
||||
@ -959,26 +996,34 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
},
|
||||
State::DerefedBorrow(state) => {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
|
||||
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
|
||||
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.hir_id) {
|
||||
Some(Node::Expr(e)) => match e.kind {
|
||||
ExprKind::Call(callee, _) if callee.hir_id != data.hir_id => (0, false),
|
||||
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
|
||||
_ => (e.precedence().order(), false),
|
||||
},
|
||||
_ => (0, false),
|
||||
};
|
||||
let sugg = if !snip_is_macro
|
||||
&& (calls_field || expr.precedence().order() < precedence)
|
||||
&& !has_enclosing_paren(&snip)
|
||||
{
|
||||
format!("({snip})")
|
||||
} else {
|
||||
snip.into()
|
||||
};
|
||||
diag.span_suggestion(data.span, "change this to", sugg, app);
|
||||
});
|
||||
let (snip, snip_is_macro) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_BORROW,
|
||||
data.first_expr.hir_id,
|
||||
data.first_expr.span,
|
||||
state.msg,
|
||||
|diag| {
|
||||
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) {
|
||||
Some(Node::Expr(e)) => match e.kind {
|
||||
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
|
||||
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
|
||||
_ => (e.precedence().order(), false),
|
||||
},
|
||||
_ => (0, false),
|
||||
};
|
||||
let sugg = if !snip_is_macro
|
||||
&& (calls_field || expr.precedence().order() < precedence)
|
||||
&& !has_enclosing_paren(&snip)
|
||||
{
|
||||
format!("({snip})")
|
||||
} else {
|
||||
snip.into()
|
||||
};
|
||||
diag.span_suggestion(data.first_expr.span, "change this to", sugg, app);
|
||||
},
|
||||
);
|
||||
},
|
||||
State::ExplicitDeref { mutability } => {
|
||||
if matches!(
|
||||
@ -996,7 +1041,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
}
|
||||
|
||||
let (prefix, precedence) = if let Some(mutability) = mutability
|
||||
&& !cx.typeck_results().expr_ty(expr).is_ref()
|
||||
&& !typeck.expr_ty(expr).is_ref()
|
||||
{
|
||||
let prefix = match mutability {
|
||||
Mutability::Not => "&",
|
||||
@ -1009,53 +1054,61 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EXPLICIT_AUTO_DEREF,
|
||||
data.hir_id,
|
||||
data.span,
|
||||
data.first_expr.hir_id,
|
||||
data.first_expr.span,
|
||||
"deref which would be done by auto-deref",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
|
||||
let (snip, snip_is_macro) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
let sugg =
|
||||
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
|
||||
format!("{prefix}({snip})")
|
||||
} else {
|
||||
format!("{prefix}{snip}")
|
||||
};
|
||||
diag.span_suggestion(data.span, "try", sugg, app);
|
||||
diag.span_suggestion(data.first_expr.span, "try", sugg, app);
|
||||
},
|
||||
);
|
||||
},
|
||||
State::ExplicitDerefField { .. } => {
|
||||
if matches!(
|
||||
expr.kind,
|
||||
ExprKind::Block(..)
|
||||
| ExprKind::ConstBlock(_)
|
||||
| ExprKind::If(..)
|
||||
| ExprKind::Loop(..)
|
||||
| ExprKind::Match(..)
|
||||
) && data.adjusted_ty.is_sized(cx.tcx, cx.param_env)
|
||||
{
|
||||
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.
|
||||
return;
|
||||
}
|
||||
|
||||
if let ExprKind::Field(parent_expr, _) = expr.kind
|
||||
&& let ty::Adt(adt, _) = cx.typeck_results().expr_ty(parent_expr).kind()
|
||||
&& adt.is_union()
|
||||
{
|
||||
// Auto deref does not apply on union field
|
||||
return;
|
||||
}
|
||||
State::ExplicitDerefField {
|
||||
derefs_manually_drop, ..
|
||||
} => {
|
||||
let (snip_span, needs_parens) = if matches!(expr.kind, ExprKind::Field(..))
|
||||
&& (derefs_manually_drop
|
||||
|| adjust_derefs_manually_drop(
|
||||
typeck.expr_adjustments(data.first_expr),
|
||||
typeck.expr_ty(data.first_expr),
|
||||
)) {
|
||||
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
|
||||
// field expressions when the base type is a union and the parent
|
||||
// expression is also a field access.
|
||||
//
|
||||
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
|
||||
// deref through `ManuallyDrop<_>` will not compile.
|
||||
let parent_id = cx.tcx.hir().parent_id(expr.hir_id);
|
||||
if parent_id == data.first_expr.hir_id {
|
||||
return;
|
||||
}
|
||||
(cx.tcx.hir().get(parent_id).expect_expr().span, true)
|
||||
} else {
|
||||
(expr.span, false)
|
||||
};
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EXPLICIT_AUTO_DEREF,
|
||||
data.hir_id,
|
||||
data.span,
|
||||
data.first_expr.hir_id,
|
||||
data.first_expr.span,
|
||||
"deref which would be done by auto-deref",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
|
||||
diag.span_suggestion(data.span, "try", snip.into_owned(), app);
|
||||
let snip = snippet_with_context(cx, snip_span, data.first_expr.span.ctxt(), "..", &mut app).0;
|
||||
let sugg = if needs_parens {
|
||||
format!("({snip})")
|
||||
} else {
|
||||
snip.into_owned()
|
||||
};
|
||||
diag.span_suggestion(data.first_expr.span, "try", sugg, app);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -148,83 +148,65 @@ fn check_struct<'tcx>(
|
||||
}
|
||||
|
||||
fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind;
|
||||
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res;
|
||||
if let variant_id = cx.tcx.parent(id);
|
||||
if let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id);
|
||||
if variant_def.fields.is_empty();
|
||||
if !variant_def.is_field_list_non_exhaustive();
|
||||
|
||||
then {
|
||||
let enum_span = cx.tcx.def_span(adt_def.did());
|
||||
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
|
||||
let variant_span = cx.tcx.def_span(variant_def.def_id);
|
||||
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVABLE_IMPLS,
|
||||
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind
|
||||
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
|
||||
&& let variant_id = cx.tcx.parent(id)
|
||||
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id)
|
||||
&& variant_def.fields.is_empty()
|
||||
&& !variant_def.is_field_list_non_exhaustive()
|
||||
{
|
||||
let enum_span = cx.tcx.def_span(adt_def.did());
|
||||
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
|
||||
let variant_span = cx.tcx.def_span(variant_def.def_id);
|
||||
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
|
||||
span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| {
|
||||
diag.span_suggestion_hidden(
|
||||
item.span,
|
||||
"this `impl` can be derived",
|
||||
|diag| {
|
||||
diag.span_suggestion_hidden(
|
||||
item.span,
|
||||
"remove the manual implementation...",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
diag.span_suggestion(
|
||||
enum_span.shrink_to_lo(),
|
||||
"...and instead derive it...",
|
||||
format!(
|
||||
"#[derive(Default)]\n{indent}",
|
||||
indent = " ".repeat(indent_enum),
|
||||
),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
diag.span_suggestion(
|
||||
variant_span.shrink_to_lo(),
|
||||
"...and mark the default variant",
|
||||
format!(
|
||||
"#[default]\n{indent}",
|
||||
indent = " ".repeat(indent_variant),
|
||||
),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
"remove the manual implementation...",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
diag.span_suggestion(
|
||||
enum_span.shrink_to_lo(),
|
||||
"...and instead derive it...",
|
||||
format!("#[derive(Default)]\n{indent}", indent = " ".repeat(indent_enum),),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.span_suggestion(
|
||||
variant_span.shrink_to_lo(),
|
||||
"...and mark the default variant",
|
||||
format!("#[default]\n{indent}", indent = " ".repeat(indent_variant),),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if_chain! {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
items: [child],
|
||||
self_ty,
|
||||
..
|
||||
}) = item.kind;
|
||||
if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
|
||||
if !item.span.from_expansion();
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
|
||||
if let impl_item_hir = child.id.hir_id();
|
||||
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
|
||||
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
|
||||
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
|
||||
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind();
|
||||
if let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
if !attrs.iter().any(|attr| attr.doc_str().is_some());
|
||||
if cx.tcx.hir().attrs(impl_item_hir).is_empty();
|
||||
|
||||
then {
|
||||
if adt_def.is_struct() {
|
||||
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
|
||||
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
|
||||
check_enum(cx, item, func_expr, adt_def);
|
||||
}
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
items: [child],
|
||||
self_ty,
|
||||
..
|
||||
}) = item.kind
|
||||
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
|
||||
&& !item.span.from_expansion()
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)
|
||||
&& let impl_item_hir = child.id.hir_id()
|
||||
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
|
||||
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
|
||||
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
|
||||
&& let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
|
||||
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
|
||||
&& !attrs.iter().any(|attr| attr.doc_str().is_some())
|
||||
&& cx.tcx.hir().attrs(impl_item_hir).is_empty()
|
||||
{
|
||||
if adt_def.is_struct() {
|
||||
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
|
||||
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
|
||||
check_enum(cx, item, func_expr, adt_def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
|
||||
use clippy_utils::{is_lint_allowed, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
|
||||
@ -232,42 +231,37 @@ fn check_hash_peq<'tcx>(
|
||||
ty: Ty<'tcx>,
|
||||
hash_is_automatically_derived: bool,
|
||||
) {
|
||||
if_chain! {
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
|
||||
then {
|
||||
// Look for the PartialEq implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
|
||||
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
|
||||
{
|
||||
// Look for the PartialEq implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
|
||||
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
|
||||
if !hash_is_automatically_derived || peq_is_automatically_derived {
|
||||
return;
|
||||
}
|
||||
if !hash_is_automatically_derived || peq_is_automatically_derived {
|
||||
return;
|
||||
}
|
||||
|
||||
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
|
||||
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
|
||||
|
||||
// Only care about `impl PartialEq<Foo> for Foo`
|
||||
// For `impl PartialEq<B> for A, input_types is [A, B]
|
||||
if trait_ref.instantiate_identity().args.type_at(1) == ty {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
span,
|
||||
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|
||||
|diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(
|
||||
cx.tcx.hir().span(hir_id),
|
||||
"`PartialEq` implemented here"
|
||||
);
|
||||
}
|
||||
// Only care about `impl PartialEq<Foo> for Foo`
|
||||
// For `impl PartialEq<B> for A, input_types is [A, B]
|
||||
if trait_ref.instantiate_identity().args.type_at(1) == ty {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
span,
|
||||
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|
||||
|diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialEq` implemented here");
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,49 +273,38 @@ fn check_ord_partial_ord<'tcx>(
|
||||
ty: Ty<'tcx>,
|
||||
ord_is_automatically_derived: bool,
|
||||
) {
|
||||
if_chain! {
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
|
||||
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
|
||||
if let Some(def_id) = &trait_ref.trait_def_id();
|
||||
if *def_id == ord_trait_def_id;
|
||||
then {
|
||||
// Look for the PartialOrd implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
|
||||
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
|
||||
&& let Some(def_id) = &trait_ref.trait_def_id()
|
||||
&& *def_id == ord_trait_def_id
|
||||
{
|
||||
// Look for the PartialOrd implementations for `ty`
|
||||
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
|
||||
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
|
||||
|
||||
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
|
||||
return;
|
||||
}
|
||||
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
|
||||
return;
|
||||
}
|
||||
|
||||
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
|
||||
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
|
||||
|
||||
// Only care about `impl PartialOrd<Foo> for Foo`
|
||||
// For `impl PartialOrd<B> for A, input_types is [A, B]
|
||||
if trait_ref.instantiate_identity().args.type_at(1) == ty {
|
||||
let mess = if partial_ord_is_automatically_derived {
|
||||
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
|
||||
} else {
|
||||
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
|
||||
};
|
||||
// Only care about `impl PartialOrd<Foo> for Foo`
|
||||
// For `impl PartialOrd<B> for A, input_types is [A, B]
|
||||
if trait_ref.instantiate_identity().args.type_at(1) == ty {
|
||||
let mess = if partial_ord_is_automatically_derived {
|
||||
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
|
||||
} else {
|
||||
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVE_ORD_XOR_PARTIAL_ORD,
|
||||
span,
|
||||
mess,
|
||||
|diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(
|
||||
cx.tcx.hir().span(hir_id),
|
||||
"`PartialOrd` implemented here"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialOrd` implemented here");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,27 +377,27 @@ fn check_unsafe_derive_deserialize<'tcx>(
|
||||
visitor.has_unsafe
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id();
|
||||
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
|
||||
if let ty::Adt(def, _) = ty.kind();
|
||||
if let Some(local_def_id) = def.did().as_local();
|
||||
let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
|
||||
if cx.tcx.inherent_impls(def.did())
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id()
|
||||
&& match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
|
||||
&& let ty::Adt(def, _) = ty.kind()
|
||||
&& let Some(local_def_id) = def.did().as_local()
|
||||
&& let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id)
|
||||
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
|
||||
&& cx
|
||||
.tcx
|
||||
.inherent_impls(def.did())
|
||||
.iter()
|
||||
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
|
||||
.any(|imp| has_unsafe(cx, imp));
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
item.span,
|
||||
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
|
||||
None,
|
||||
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
|
||||
);
|
||||
}
|
||||
.any(|imp| has_unsafe(cx, imp))
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
item.span,
|
||||
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
|
||||
None,
|
||||
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,12 +414,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let Some(header) = kind.header();
|
||||
if header.unsafety == Unsafety::Unsafe;
|
||||
then {
|
||||
self.has_unsafe = true;
|
||||
}
|
||||
if let Some(header) = kind.header()
|
||||
&& header.unsafety == Unsafety::Unsafe
|
||||
{
|
||||
self.has_unsafe = true;
|
||||
}
|
||||
|
||||
walk_fn(self, kind, decl, body_id, id);
|
||||
@ -463,30 +444,28 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
|
||||
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
|
||||
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
if_chain! {
|
||||
if let ty::Adt(adt, args) = ty.kind();
|
||||
if cx.tcx.visibility(adt.did()).is_public();
|
||||
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
|
||||
let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
|
||||
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]);
|
||||
if let ty::Adt(adt, args) = ty.kind()
|
||||
&& cx.tcx.visibility(adt.did()).is_public()
|
||||
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
||||
&& let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
if adt
|
||||
&& adt
|
||||
.all_fields()
|
||||
.map(|f| f.ty(cx.tcx, args))
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]));
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
"consider deriving `Eq` as well",
|
||||
"PartialEq, Eq".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
}
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]))
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
"consider deriving `Eq` as well",
|
||||
"PartialEq, Eq".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@ pub struct DisallowedNames {
|
||||
}
|
||||
|
||||
impl DisallowedNames {
|
||||
pub fn new(disallow: FxHashSet<String>) -> Self {
|
||||
pub fn new(disallowed_names: &[String]) -> Self {
|
||||
Self {
|
||||
disallow,
|
||||
disallow: disallowed_names.iter().cloned().collect(),
|
||||
test_modules_deep: 0,
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,14 @@ use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
|
||||
use if_chain::if_chain;
|
||||
use pulldown_cmark::Event::{
|
||||
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
|
||||
};
|
||||
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
|
||||
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
|
||||
use rustc_ast::ast::{Async, Attribute, Fn, FnRetTy, ItemKind};
|
||||
use rustc_ast::token::CommentKind;
|
||||
use rustc_ast::{AttrKind, AttrStyle};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::emitter::EmitterWriter;
|
||||
@ -30,8 +31,8 @@ use rustc_resolve::rustdoc::{
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{sym, BytePos, FileName, Pos, Span};
|
||||
use rustc_span::source_map::{FilePathMapping, SourceMap};
|
||||
use rustc_span::{sym, BytePos, FileName, Pos, Span};
|
||||
use std::ops::Range;
|
||||
use std::{io, thread};
|
||||
use url::Url;
|
||||
@ -261,6 +262,53 @@ declare_clippy_lint! {
|
||||
"`pub fn` or `pub trait` with `# Safety` docs"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Triple-slash comments (known as "outer doc comments") apply to items that follow it.
|
||||
/// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning.
|
||||
///
|
||||
/// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which
|
||||
/// applies to the parent item (i.e. the item that the comment is contained in,
|
||||
/// usually a module or crate).
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Inner doc comments can only appear before items, so there are certain cases where the suggestion
|
||||
/// made by this lint is not valid code. For example:
|
||||
/// ```rs
|
||||
/// fn foo() {}
|
||||
/// ///!
|
||||
/// fn bar() {}
|
||||
/// ```
|
||||
/// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment
|
||||
/// is not valid at that position.
|
||||
///
|
||||
/// ### Example
|
||||
/// In this example, the doc comment is attached to the *function*, rather than the *module*.
|
||||
/// ```no_run
|
||||
/// pub mod util {
|
||||
/// ///! This module contains utility functions.
|
||||
///
|
||||
/// pub fn dummy() {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// pub mod util {
|
||||
/// //! This module contains utility functions.
|
||||
///
|
||||
/// pub fn dummy() {}
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub SUSPICIOUS_DOC_COMMENTS,
|
||||
suspicious,
|
||||
"suspicious usage of (outer) doc comments"
|
||||
}
|
||||
|
||||
#[expect(clippy::module_name_repetitions)]
|
||||
#[derive(Clone)]
|
||||
pub struct DocMarkdown {
|
||||
@ -269,9 +317,9 @@ pub struct DocMarkdown {
|
||||
}
|
||||
|
||||
impl DocMarkdown {
|
||||
pub fn new(valid_idents: FxHashSet<String>) -> Self {
|
||||
pub fn new(valid_idents: &[String]) -> Self {
|
||||
Self {
|
||||
valid_idents,
|
||||
valid_idents: valid_idents.iter().cloned().collect(),
|
||||
in_trait_impl: false,
|
||||
}
|
||||
}
|
||||
@ -285,6 +333,7 @@ impl_lint_pass!(DocMarkdown => [
|
||||
MISSING_PANICS_DOC,
|
||||
NEEDLESS_DOCTEST_MAIN,
|
||||
UNNECESSARY_SAFETY_DOC,
|
||||
SUSPICIOUS_DOC_COMMENTS
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
|
||||
@ -428,25 +477,21 @@ fn lint_for_missing_headers(
|
||||
span,
|
||||
"docs for function returning `Result` missing `# Errors` section",
|
||||
);
|
||||
} else {
|
||||
if_chain! {
|
||||
if let Some(body_id) = body_id;
|
||||
if let Some(future) = cx.tcx.lang_items().future_trait();
|
||||
let typeck = cx.tcx.typeck_body(body_id);
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let ret_ty = typeck.expr_ty(body.value);
|
||||
if implements_trait(cx, ret_ty, future, &[]);
|
||||
if let ty::Coroutine(_, subs, _) = ret_ty.kind();
|
||||
if is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result);
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
MISSING_ERRORS_DOC,
|
||||
span,
|
||||
"docs for function returning `Result` missing `# Errors` section",
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if let Some(body_id) = body_id
|
||||
&& let Some(future) = cx.tcx.lang_items().future_trait()
|
||||
&& let typeck = cx.tcx.typeck_body(body_id)
|
||||
&& let body = cx.tcx.hir().body(body_id)
|
||||
&& let ret_ty = typeck.expr_ty(body.value)
|
||||
&& implements_trait(cx, ret_ty, future, &[])
|
||||
&& let ty::Coroutine(_, subs, _) = ret_ty.kind()
|
||||
&& is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MISSING_ERRORS_DOC,
|
||||
span,
|
||||
"docs for function returning `Result` missing `# Errors` section",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -483,6 +528,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
||||
return None;
|
||||
}
|
||||
|
||||
check_almost_inner_doc(cx, attrs);
|
||||
|
||||
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
|
||||
let mut doc = String::new();
|
||||
for fragment in &fragments {
|
||||
@ -511,6 +558,43 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
||||
))
|
||||
}
|
||||
|
||||
/// Looks for `///!` and `/**!` comments, which were probably meant to be `//!` and `/*!`
|
||||
fn check_almost_inner_doc(cx: &LateContext<'_>, attrs: &[Attribute]) {
|
||||
let replacements: Vec<_> = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if let AttrKind::DocComment(com_kind, sym) = attr.kind
|
||||
&& let AttrStyle::Outer = attr.style
|
||||
&& let Some(com) = sym.as_str().strip_prefix('!')
|
||||
{
|
||||
let sugg = match com_kind {
|
||||
CommentKind::Line => format!("//!{com}"),
|
||||
CommentKind::Block => format!("/*!{com}*/"),
|
||||
};
|
||||
Some((attr.span, sugg))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some((&(lo_span, _), &(hi_span, _))) = replacements.first().zip(replacements.last()) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SUSPICIOUS_DOC_COMMENTS,
|
||||
lo_span.to(hi_span),
|
||||
"this is an outer doc comment and does not apply to the parent module or crate",
|
||||
|diag| {
|
||||
diag.multipart_suggestion(
|
||||
"use an inner doc comment to document the parent module or crate",
|
||||
replacements,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
|
||||
|
||||
#[allow(clippy::too_many_lines)] // Only a big match statement
|
||||
@ -649,11 +733,12 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<u
|
||||
rustc_span::create_session_globals_then(edition, || {
|
||||
let filename = FileName::anon_source_code(&code);
|
||||
|
||||
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let fallback_bundle =
|
||||
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
|
||||
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
|
||||
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
|
||||
#[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_span_handler
|
||||
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let sess = ParseSess::with_span_handler(handler, sm);
|
||||
|
||||
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::peel_blocks;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -36,31 +35,30 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
|
||||
|
||||
impl LateLintPass<'_> for EmptyDrop {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if_chain! {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
items: [child],
|
||||
..
|
||||
}) = item.kind;
|
||||
if trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait();
|
||||
if let impl_item_hir = child.id.hir_id();
|
||||
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
|
||||
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
|
||||
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
|
||||
let func_expr = peel_blocks(func_expr);
|
||||
if let ExprKind::Block(block, _) = func_expr.kind;
|
||||
if block.stmts.is_empty() && block.expr.is_none();
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EMPTY_DROP,
|
||||
item.span,
|
||||
"empty drop implementation",
|
||||
"try removing this impl",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect
|
||||
);
|
||||
}
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(ref trait_ref),
|
||||
items: [child],
|
||||
..
|
||||
}) = item.kind
|
||||
&& trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait()
|
||||
&& let impl_item_hir = child.id.hir_id()
|
||||
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
|
||||
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
|
||||
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
|
||||
&& let func_expr = peel_blocks(func_expr)
|
||||
&& let ExprKind::Block(block, _) = func_expr.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& block.expr.is_none()
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EMPTY_DROP,
|
||||
item.span,
|
||||
"empty drop implementation",
|
||||
"try removing this impl",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,27 +114,23 @@ impl LateLintPass<'_> for EndianBytes {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind;
|
||||
if args.is_empty();
|
||||
let ty = cx.typeck_results().expr_ty(receiver);
|
||||
if ty.is_primitive_ty();
|
||||
if maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty);
|
||||
then {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind
|
||||
&& args.is_empty()
|
||||
&& let ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& ty.is_primitive_ty()
|
||||
&& maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Call(function, ..) = expr.kind;
|
||||
if let ExprKind::Path(qpath) = function.kind;
|
||||
if let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id();
|
||||
if let Some(function_name) = cx.get_def_path(def_id).last();
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if ty.is_primitive_ty();
|
||||
then {
|
||||
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
|
||||
}
|
||||
if let ExprKind::Call(function, ..) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = function.kind
|
||||
&& let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id()
|
||||
&& let Some(function_name) = cx.get_def_path(def_id).last()
|
||||
&& let ty = cx.typeck_results().expr_ty(expr)
|
||||
&& ty.is_primitive_ty()
|
||||
{
|
||||
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, TraitRef, Ty};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -247,8 +247,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig:
|
||||
/// This is needed because rustc is unable to late bind early-bound regions in a function signature.
|
||||
fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool {
|
||||
fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
|
||||
matches!(from_region.kind(), RegionKind::ReBound(..))
|
||||
&& !matches!(to_region.kind(), RegionKind::ReBound(..))
|
||||
matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..))
|
||||
}
|
||||
|
||||
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::indent_of;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -71,40 +70,31 @@ declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
|
||||
|
||||
impl LateLintPass<'_> for ExhaustiveItems {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if_chain! {
|
||||
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
|
||||
then {
|
||||
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
|
||||
if v.fields().iter().any(|f| {
|
||||
!cx.tcx.visibility(f.def_id).is_public()
|
||||
}) {
|
||||
// skip structs with private fields
|
||||
return;
|
||||
}
|
||||
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
|
||||
} else {
|
||||
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
|
||||
};
|
||||
let suggestion_span = item.span.shrink_to_lo();
|
||||
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
lint,
|
||||
item.span,
|
||||
msg,
|
||||
|diag| {
|
||||
let sugg = format!("#[non_exhaustive]\n{indent}");
|
||||
diag.span_suggestion(suggestion_span,
|
||||
"try adding #[non_exhaustive]",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect);
|
||||
}
|
||||
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind
|
||||
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
|
||||
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
|
||||
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
|
||||
{
|
||||
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
|
||||
if v.fields().iter().any(|f| !cx.tcx.visibility(f.def_id).is_public()) {
|
||||
// skip structs with private fields
|
||||
return;
|
||||
}
|
||||
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
|
||||
} else {
|
||||
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
|
||||
};
|
||||
let suggestion_span = item.span.shrink_to_lo();
|
||||
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
|
||||
span_lint_and_then(cx, lint, item.span, msg, |diag| {
|
||||
let sugg = format!("#[non_exhaustive]\n{indent}");
|
||||
diag.span_suggestion(
|
||||
suggestion_span,
|
||||
"try adding #[non_exhaustive]",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_entrypoint_fn;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@ -42,19 +41,17 @@ declare_lint_pass!(Exit => [EXIT]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Exit {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(path_expr, _args) = e.kind;
|
||||
if let ExprKind::Path(ref path) = path_expr.kind;
|
||||
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::process_exit, def_id);
|
||||
let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
|
||||
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent);
|
||||
if let ExprKind::Call(path_expr, _args) = e.kind
|
||||
&& let ExprKind::Path(ref path) = path_expr.kind
|
||||
&& let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::process_exit, def_id)
|
||||
&& let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id
|
||||
&& let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent)
|
||||
// If the next item up is a function we check if it is an entry point
|
||||
// and only then emit a linter warning
|
||||
if !is_entrypoint_fn(cx, parent.to_def_id());
|
||||
then {
|
||||
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
|
||||
}
|
||||
&& !is_entrypoint_fn(cx, parent.to_def_id())
|
||||
{
|
||||
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, path_def_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
|
||||
@ -101,30 +100,28 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
||||
/// If `kind` is a block that looks like `{ let result = $expr; result }` then
|
||||
/// returns $expr. Otherwise returns `kind`.
|
||||
fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) -> &'tcx ExprKind<'hir> {
|
||||
if_chain! {
|
||||
if let ExprKind::Block(block, _label @ None) = kind;
|
||||
if let Block {
|
||||
if let ExprKind::Block(block, _label @ None) = kind
|
||||
&& let Block {
|
||||
stmts: [Stmt { kind: StmtKind::Local(local), .. }],
|
||||
expr: Some(expr_end_of_block),
|
||||
rules: BlockCheckMode::DefaultBlock,
|
||||
..
|
||||
} = block;
|
||||
} = block
|
||||
|
||||
// Find id of the local that expr_end_of_block resolves to
|
||||
if let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind;
|
||||
if let Res::Local(expr_res) = expr_path.res;
|
||||
if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res);
|
||||
&& let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind
|
||||
&& let Res::Local(expr_res) = expr_path.res
|
||||
&& let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res)
|
||||
|
||||
// Find id of the local we found in the block
|
||||
if let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind;
|
||||
&& let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind
|
||||
|
||||
// If those two are the same hir id
|
||||
if res_pat.hir_id == local_hir_id;
|
||||
&& res_pat.hir_id == local_hir_id
|
||||
|
||||
if let Some(init) = local.init;
|
||||
then {
|
||||
return &init.kind;
|
||||
}
|
||||
&& let Some(init) = local.init
|
||||
{
|
||||
return &init.kind;
|
||||
}
|
||||
kind
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::method_chain_args;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
@ -53,13 +52,13 @@ declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
|
||||
impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
// check for `impl From<???> for ..`
|
||||
if_chain! {
|
||||
if let hir::ItemKind::Impl(impl_) = &item.kind;
|
||||
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id);
|
||||
if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id);
|
||||
then {
|
||||
lint_impl_body(cx, item.span, impl_.items);
|
||||
}
|
||||
if let hir::ItemKind::Impl(impl_) = &item.kind
|
||||
&& let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id)
|
||||
&& cx
|
||||
.tcx
|
||||
.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id)
|
||||
{
|
||||
lint_impl_body(cx, item.span, impl_.items);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,34 +97,33 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
|
||||
}
|
||||
|
||||
for impl_item in impl_items {
|
||||
if_chain! {
|
||||
if impl_item.ident.name == sym::from;
|
||||
if let ImplItemKind::Fn(_, body_id) =
|
||||
cx.tcx.hir().impl_item(impl_item.id).kind;
|
||||
then {
|
||||
// check the body for `begin_panic` or `unwrap`
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let mut fpu = FindPanicUnwrap {
|
||||
lcx: cx,
|
||||
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
|
||||
result: Vec::new(),
|
||||
};
|
||||
fpu.visit_expr(body.value);
|
||||
if impl_item.ident.name == sym::from
|
||||
&& let ImplItemKind::Fn(_, body_id) = cx.tcx.hir().impl_item(impl_item.id).kind
|
||||
{
|
||||
// check the body for `begin_panic` or `unwrap`
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let mut fpu = FindPanicUnwrap {
|
||||
lcx: cx,
|
||||
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
|
||||
result: Vec::new(),
|
||||
};
|
||||
fpu.visit_expr(body.value);
|
||||
|
||||
// if we've found one, lint
|
||||
if !fpu.result.is_empty() {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FALLIBLE_IMPL_FROM,
|
||||
impl_span,
|
||||
"consider implementing `TryFrom` instead",
|
||||
move |diag| {
|
||||
diag.help(
|
||||
"`From` is intended for infallible conversions only. \
|
||||
Use `TryFrom` if there's a possibility for the conversion to fail");
|
||||
diag.span_note(fpu.result, "potential failure(s)");
|
||||
});
|
||||
}
|
||||
// if we've found one, lint
|
||||
if !fpu.result.is_empty() {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FALLIBLE_IMPL_FROM,
|
||||
impl_span,
|
||||
"consider implementing `TryFrom` instead",
|
||||
move |diag| {
|
||||
diag.help(
|
||||
"`From` is intended for infallible conversions only. \
|
||||
Use `TryFrom` if there's a possibility for the conversion to fail",
|
||||
);
|
||||
diag.span_note(fpu.result, "potential failure(s)");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::numeric_literal;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, LitFloatType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
@ -64,73 +63,70 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
|
||||
impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if_chain! {
|
||||
if let ty::Float(fty) = *ty.kind();
|
||||
if let hir::ExprKind::Lit(lit) = expr.kind;
|
||||
if let LitKind::Float(sym, lit_float_ty) = lit.node;
|
||||
then {
|
||||
let sym_str = sym.as_str();
|
||||
let formatter = FloatFormat::new(sym_str);
|
||||
// Try to bail out if the float is for sure fine.
|
||||
// If its within the 2 decimal digits of being out of precision we
|
||||
// check if the parsed representation is the same as the string
|
||||
// since we'll need the truncated string anyway.
|
||||
let digits = count_digits(sym_str);
|
||||
let max = max_digits(fty);
|
||||
let type_suffix = match lit_float_ty {
|
||||
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
|
||||
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
|
||||
LitFloatType::Unsuffixed => None
|
||||
};
|
||||
let (is_whole, is_inf, mut float_str) = match fty {
|
||||
FloatTy::F32 => {
|
||||
let value = sym_str.parse::<f32>().unwrap();
|
||||
if let ty::Float(fty) = *ty.kind()
|
||||
&& let hir::ExprKind::Lit(lit) = expr.kind
|
||||
&& let LitKind::Float(sym, lit_float_ty) = lit.node
|
||||
{
|
||||
let sym_str = sym.as_str();
|
||||
let formatter = FloatFormat::new(sym_str);
|
||||
// Try to bail out if the float is for sure fine.
|
||||
// If its within the 2 decimal digits of being out of precision we
|
||||
// check if the parsed representation is the same as the string
|
||||
// since we'll need the truncated string anyway.
|
||||
let digits = count_digits(sym_str);
|
||||
let max = max_digits(fty);
|
||||
let type_suffix = match lit_float_ty {
|
||||
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
|
||||
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
|
||||
LitFloatType::Unsuffixed => None,
|
||||
};
|
||||
let (is_whole, is_inf, mut float_str) = match fty {
|
||||
FloatTy::F32 => {
|
||||
let value = sym_str.parse::<f32>().unwrap();
|
||||
|
||||
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
|
||||
},
|
||||
FloatTy::F64 => {
|
||||
let value = sym_str.parse::<f64>().unwrap();
|
||||
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
|
||||
},
|
||||
FloatTy::F64 => {
|
||||
let value = sym_str.parse::<f64>().unwrap();
|
||||
|
||||
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
|
||||
},
|
||||
};
|
||||
|
||||
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
|
||||
},
|
||||
};
|
||||
if is_inf {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_inf {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
|
||||
// Normalize the literal by stripping the fractional portion
|
||||
if sym_str.split('.').next().unwrap() != float_str {
|
||||
// If the type suffix is missing the suggestion would be
|
||||
// incorrectly interpreted as an integer so adding a `.0`
|
||||
// suffix to prevent that.
|
||||
if type_suffix.is_none() {
|
||||
float_str.push_str(".0");
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
LOSSY_FLOAT_LITERAL,
|
||||
expr.span,
|
||||
"literal cannot be represented as the underlying type without loss of precision",
|
||||
"consider changing the type or replacing it with",
|
||||
numeric_literal::format(&float_str, type_suffix, true),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
|
||||
// Normalize the literal by stripping the fractional portion
|
||||
if sym_str.split('.').next().unwrap() != float_str {
|
||||
// If the type suffix is missing the suggestion would be
|
||||
// incorrectly interpreted as an integer so adding a `.0`
|
||||
// suffix to prevent that.
|
||||
if type_suffix.is_none() {
|
||||
float_str.push_str(".0");
|
||||
}
|
||||
} else if digits > max as usize && float_str.len() < sym_str.len() {
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXCESSIVE_PRECISION,
|
||||
LOSSY_FLOAT_LITERAL,
|
||||
expr.span,
|
||||
"float has excessive precision",
|
||||
"consider changing the type or truncating it to",
|
||||
"literal cannot be represented as the underlying type without loss of precision",
|
||||
"consider changing the type or replacing it with",
|
||||
numeric_literal::format(&float_str, type_suffix, true),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
} else if digits > max as usize && float_str.len() < sym_str.len() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXCESSIVE_PRECISION,
|
||||
expr.span,
|
||||
"float has excessive precision",
|
||||
"consider changing the type or truncating it to",
|
||||
numeric_literal::format(&float_str, type_suffix, true),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_expr, higher, in_constant, is_no_std_crate, numeric_literal, peel_blocks, sugg,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -133,30 +132,25 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
|
||||
expr = inner_expr;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind()
|
||||
// if the expression is a float literal and it is unsuffixed then
|
||||
// add a suffix so the suggestion is valid and unambiguous
|
||||
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
|
||||
if let ExprKind::Lit(lit) = &expr.kind;
|
||||
if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
|
||||
then {
|
||||
let op = format!(
|
||||
"{suggestion}{}{}",
|
||||
// Check for float literals without numbers following the decimal
|
||||
// separator such as `2.` and adds a trailing zero
|
||||
if sym.as_str().ends_with('.') {
|
||||
"0"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
float_ty.name_str()
|
||||
).into();
|
||||
&& let ExprKind::Lit(lit) = &expr.kind
|
||||
&& let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
|
||||
{
|
||||
let op = format!(
|
||||
"{suggestion}{}{}",
|
||||
// Check for float literals without numbers following the decimal
|
||||
// separator such as `2.` and adds a trailing zero
|
||||
if sym.as_str().ends_with('.') { "0" } else { "" },
|
||||
float_ty.name_str()
|
||||
)
|
||||
.into();
|
||||
|
||||
suggestion = match suggestion {
|
||||
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
|
||||
_ => Sugg::NonParen(op)
|
||||
};
|
||||
}
|
||||
suggestion = match suggestion {
|
||||
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
|
||||
_ => Sugg::NonParen(op),
|
||||
};
|
||||
}
|
||||
|
||||
suggestion.maybe_par()
|
||||
@ -359,35 +353,59 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
|
||||
) = receiver.kind
|
||||
{
|
||||
// check if expression of the form x * x + y * y
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
|
||||
if eq_expr_value(cx, lmul_lhs, lmul_rhs);
|
||||
if eq_expr_value(cx, rmul_lhs, rmul_rhs);
|
||||
then {
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, "..")));
|
||||
}
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Mul, ..
|
||||
},
|
||||
lmul_lhs,
|
||||
lmul_rhs,
|
||||
) = add_lhs.kind
|
||||
&& let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Mul, ..
|
||||
},
|
||||
rmul_lhs,
|
||||
rmul_rhs,
|
||||
) = add_rhs.kind
|
||||
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
|
||||
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
|
||||
{
|
||||
return Some(format!(
|
||||
"{}.hypot({})",
|
||||
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
|
||||
Sugg::hir(cx, rmul_lhs, "..")
|
||||
));
|
||||
}
|
||||
|
||||
// check if expression of the form x.powi(2) + y.powi(2)
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment { ident: lmethod_name, .. },
|
||||
largs_0, [largs_1, ..],
|
||||
_
|
||||
) = &add_lhs.kind;
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment { ident: rmethod_name, .. },
|
||||
rargs_0, [rargs_1, ..],
|
||||
_
|
||||
) = &add_rhs.kind;
|
||||
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
|
||||
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1);
|
||||
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1);
|
||||
if Int(2) == lvalue && Int(2) == rvalue;
|
||||
then {
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
|
||||
}
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: lmethod_name, ..
|
||||
},
|
||||
largs_0,
|
||||
[largs_1, ..],
|
||||
_,
|
||||
) = &add_lhs.kind
|
||||
&& let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: rmethod_name, ..
|
||||
},
|
||||
rargs_0,
|
||||
[rargs_1, ..],
|
||||
_,
|
||||
) = &add_rhs.kind
|
||||
&& lmethod_name.as_str() == "powi"
|
||||
&& rmethod_name.as_str() == "powi"
|
||||
&& let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1)
|
||||
&& let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1)
|
||||
&& Int(2) == lvalue
|
||||
&& Int(2) == rvalue
|
||||
{
|
||||
return Some(format!(
|
||||
"{}.hypot({})",
|
||||
Sugg::hir(cx, largs_0, "..").maybe_par(),
|
||||
Sugg::hir(cx, rargs_0, "..")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,39 +429,44 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
|
||||
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
|
||||
// and suggest usage of `x.exp_m1() - (y - 1)` instead
|
||||
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
|
||||
if cx.typeck_results().expr_ty(lhs).is_floating_point();
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), rhs);
|
||||
if F32(1.0) == value || F64(1.0) == value;
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
|
||||
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
|
||||
if path.ident.name.as_str() == "exp";
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
IMPRECISE_FLOPS,
|
||||
expr.span,
|
||||
"(e.pow(x) - 1) can be computed more accurately",
|
||||
"consider using",
|
||||
format!(
|
||||
"{}.exp_m1()",
|
||||
Sugg::hir(cx, self_arg, "..").maybe_par()
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = expr.kind
|
||||
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
|
||||
&& let Some(value) = constant(cx, cx.typeck_results(), rhs)
|
||||
&& (F32(1.0) == value || F64(1.0) == value)
|
||||
&& let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
|
||||
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
|
||||
&& path.ident.name.as_str() == "exp"
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
IMPRECISE_FLOPS,
|
||||
expr.span,
|
||||
"(e.pow(x) - 1) can be computed more accurately",
|
||||
"consider using",
|
||||
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
|
||||
if cx.typeck_results().expr_ty(lhs).is_floating_point();
|
||||
if cx.typeck_results().expr_ty(rhs).is_floating_point();
|
||||
then {
|
||||
return Some((lhs, rhs));
|
||||
}
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Mul, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = &expr.kind
|
||||
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
|
||||
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
|
||||
{
|
||||
return Some((lhs, rhs));
|
||||
}
|
||||
|
||||
None
|
||||
@ -553,60 +576,72 @@ fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a
|
||||
}
|
||||
|
||||
fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr);
|
||||
let if_body_expr = peel_blocks(then);
|
||||
let else_body_expr = peel_blocks(r#else);
|
||||
if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
|
||||
then {
|
||||
let positive_abs_sugg = (
|
||||
"manual implementation of `abs` method",
|
||||
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
);
|
||||
let negative_abs_sugg = (
|
||||
"manual implementation of negation of `abs` method",
|
||||
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
);
|
||||
let sugg = if is_testing_positive(cx, cond, body) {
|
||||
if if_expr_positive {
|
||||
positive_abs_sugg
|
||||
} else {
|
||||
negative_abs_sugg
|
||||
}
|
||||
} else if is_testing_negative(cx, cond, body) {
|
||||
if if_expr_positive {
|
||||
negative_abs_sugg
|
||||
} else {
|
||||
positive_abs_sugg
|
||||
}
|
||||
if let Some(higher::If {
|
||||
cond,
|
||||
then,
|
||||
r#else: Some(r#else),
|
||||
}) = higher::If::hir(expr)
|
||||
&& let if_body_expr = peel_blocks(then)
|
||||
&& let else_body_expr = peel_blocks(r#else)
|
||||
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
|
||||
{
|
||||
let positive_abs_sugg = (
|
||||
"manual implementation of `abs` method",
|
||||
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
);
|
||||
let negative_abs_sugg = (
|
||||
"manual implementation of negation of `abs` method",
|
||||
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
|
||||
);
|
||||
let sugg = if is_testing_positive(cx, cond, body) {
|
||||
if if_expr_positive {
|
||||
positive_abs_sugg
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
sugg.0,
|
||||
"try",
|
||||
sugg.1,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
negative_abs_sugg
|
||||
}
|
||||
} else if is_testing_negative(cx, cond, body) {
|
||||
if if_expr_positive {
|
||||
negative_abs_sugg
|
||||
} else {
|
||||
positive_abs_sugg
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
sugg.0,
|
||||
"try",
|
||||
sugg.1,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
|
||||
then {
|
||||
return method_name_a.as_str() == method_name_b.as_str() &&
|
||||
args_a.len() == args_b.len() &&
|
||||
(
|
||||
["ln", "log2", "log10"].contains(&method_name_a.as_str()) ||
|
||||
method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])
|
||||
);
|
||||
}
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: method_name_a, ..
|
||||
},
|
||||
_,
|
||||
args_a,
|
||||
_,
|
||||
) = expr_a.kind
|
||||
&& let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: method_name_b, ..
|
||||
},
|
||||
_,
|
||||
args_b,
|
||||
_,
|
||||
) = expr_b.kind
|
||||
{
|
||||
return method_name_a.as_str() == method_name_b.as_str()
|
||||
&& args_a.len() == args_b.len()
|
||||
&& (["ln", "log2", "log10"].contains(&method_name_a.as_str())
|
||||
|| method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
|
||||
}
|
||||
|
||||
false
|
||||
@ -614,103 +649,98 @@ fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>
|
||||
|
||||
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
// check if expression of the form x.logN() / y.logN()
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Div, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = &expr.kind;
|
||||
if are_same_base_logs(cx, lhs, rhs);
|
||||
if let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind;
|
||||
if let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind;
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"log base can be expressed more clearly",
|
||||
"consider using",
|
||||
format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Div, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = &expr.kind
|
||||
&& are_same_base_logs(cx, lhs, rhs)
|
||||
&& let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind
|
||||
&& let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"log base can be expressed more clearly",
|
||||
"consider using",
|
||||
format!(
|
||||
"{}.log({})",
|
||||
Sugg::hir(cx, largs_self, "..").maybe_par(),
|
||||
Sugg::hir(cx, rargs_self, ".."),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Div, ..
|
||||
},
|
||||
div_lhs,
|
||||
div_rhs,
|
||||
) = &expr.kind;
|
||||
if let ExprKind::Binary(
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Div, ..
|
||||
},
|
||||
div_lhs,
|
||||
div_rhs,
|
||||
) = &expr.kind
|
||||
&& let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Mul, ..
|
||||
},
|
||||
mul_lhs,
|
||||
mul_rhs,
|
||||
) = &div_lhs.kind;
|
||||
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs);
|
||||
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs);
|
||||
then {
|
||||
// TODO: also check for constant values near PI/180 or 180/PI
|
||||
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
|
||||
(F32(180_f32) == lvalue || F64(180_f64) == lvalue)
|
||||
) = &div_lhs.kind
|
||||
&& let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs)
|
||||
&& let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs)
|
||||
{
|
||||
// TODO: also check for constant values near PI/180 or 180/PI
|
||||
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
|
||||
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
|
||||
{
|
||||
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind
|
||||
&& let ast::LitKind::Float(ref value, float_type) = literal.node
|
||||
&& float_type == ast::LitFloatType::Unsuffixed
|
||||
{
|
||||
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
if_chain! {
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind;
|
||||
if let ast::LitKind::Float(ref value, float_type) = literal.node;
|
||||
if float_type == ast::LitFloatType::Unsuffixed;
|
||||
then {
|
||||
if value.as_str().ends_with('.') {
|
||||
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
} else {
|
||||
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
}
|
||||
}
|
||||
if value.as_str().ends_with('.') {
|
||||
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
} else {
|
||||
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"conversion to degrees can be done more accurately",
|
||||
"consider using",
|
||||
proposal,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else if
|
||||
(F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
|
||||
(F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
|
||||
{
|
||||
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
if_chain! {
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind;
|
||||
if let ast::LitKind::Float(ref value, float_type) = literal.node;
|
||||
if float_type == ast::LitFloatType::Unsuffixed;
|
||||
then {
|
||||
if value.as_str().ends_with('.') {
|
||||
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
} else {
|
||||
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
}
|
||||
}
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"conversion to radians can be done more accurately",
|
||||
"consider using",
|
||||
proposal,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"conversion to degrees can be done more accurately",
|
||||
"consider using",
|
||||
proposal,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
|
||||
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
|
||||
{
|
||||
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
|
||||
if let ExprKind::Lit(literal) = mul_lhs.kind
|
||||
&& let ast::LitKind::Float(ref value, float_type) = literal.node
|
||||
&& float_type == ast::LitFloatType::Unsuffixed
|
||||
{
|
||||
if value.as_str().ends_with('.') {
|
||||
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
} else {
|
||||
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
|
||||
}
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
expr.span,
|
||||
"conversion to radians can be done more accurately",
|
||||
"consider using",
|
||||
proposal,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use clippy_utils::macros::{
|
||||
};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_lang_item};
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::{
|
||||
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
|
||||
@ -404,49 +403,43 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
|
||||
}
|
||||
|
||||
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if !value.span.from_expansion();
|
||||
if let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind;
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
|
||||
if is_diag_trait_item(cx, method_def_id, sym::ToString);
|
||||
let receiver_ty = cx.typeck_results().expr_ty(receiver);
|
||||
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
|
||||
let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter());
|
||||
if implements_trait(cx, target, display_trait_id, &[]);
|
||||
if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait();
|
||||
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
|
||||
then {
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!(
|
||||
"`to_string` applied to a type that implements `Display` in `{name}!` args"
|
||||
),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!(
|
||||
"`to_string` applied to a type that implements `Display` in `{name}!` args"
|
||||
),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
if !value.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
|
||||
&& implements_trait(cx, target, display_trait_id, &[])
|
||||
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
{
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{FormatArgsPiece, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
@ -141,27 +140,25 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
|
||||
}
|
||||
|
||||
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind
|
||||
// Get the hir_id of the object we are calling the method on
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind;
|
||||
// Is the method to_string() ?
|
||||
if path.ident.name == sym::to_string;
|
||||
&& path.ident.name == sym::to_string
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
|
||||
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& is_diag_trait_item(cx, expr_def_id, sym::ToString)
|
||||
// Is the method is called on self
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
|
||||
if let [segment] = path.segments;
|
||||
if segment.ident.name == kw::SelfLower;
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
|
||||
&& let [segment] = path.segments
|
||||
&& segment.ident.name == kw::SelfLower
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,55 +212,53 @@ fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_
|
||||
}
|
||||
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
|
||||
then {
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = impl_trait.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = impl_trait.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
|
||||
if_chain! {
|
||||
if impl_item.ident.name == sym::fmt;
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
|
||||
if let Some(did) = trait_ref.trait_def_id();
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(did);
|
||||
if matches!(name, sym::Debug | sym::Display);
|
||||
then {
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let formatter_name = body.params.get(1)
|
||||
.and_then(|param| param.pat.simple_ident())
|
||||
.map(|ident| ident.name);
|
||||
if impl_item.ident.name == sym::fmt
|
||||
&& let ImplItemKind::Fn(_, body_id) = impl_item.kind
|
||||
&& let Some(Impl {
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = get_parent_as_impl(cx.tcx, impl_item.hir_id())
|
||||
&& let Some(did) = trait_ref.trait_def_id()
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(did)
|
||||
&& matches!(name, sym::Debug | sym::Display)
|
||||
{
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
let formatter_name = body
|
||||
.params
|
||||
.get(1)
|
||||
.and_then(|param| param.pat.simple_ident())
|
||||
.map(|ident| ident.name);
|
||||
|
||||
Some(FormatTraitNames {
|
||||
name,
|
||||
formatter_name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(FormatTraitNames { name, formatter_name })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::is_span_if;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
@ -168,93 +167,84 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
|
||||
/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
|
||||
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
|
||||
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
|
||||
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind
|
||||
&& !lhs.span.from_expansion() && !rhs.span.from_expansion()
|
||||
// span between BinOp LHS and RHS
|
||||
let binop_span = lhs.span.between(rhs.span);
|
||||
&& let binop_span = lhs.span.between(rhs.span)
|
||||
// if RHS is an UnOp
|
||||
if let ExprKind::Unary(op, ref un_rhs) = rhs.kind;
|
||||
&& let ExprKind::Unary(op, ref un_rhs) = rhs.kind
|
||||
// from UnOp operator to UnOp operand
|
||||
let unop_operand_span = rhs.span.until(un_rhs.span);
|
||||
if let Some(binop_snippet) = snippet_opt(cx, binop_span);
|
||||
if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span);
|
||||
let binop_str = BinOpKind::to_string(&binop.node);
|
||||
&& let unop_operand_span = rhs.span.until(un_rhs.span)
|
||||
&& let Some(binop_snippet) = snippet_opt(cx, binop_span)
|
||||
&& let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span)
|
||||
&& let binop_str = BinOpKind::to_string(&binop.node)
|
||||
// no space after BinOp operator and space after UnOp operator
|
||||
if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ');
|
||||
then {
|
||||
let unop_str = UnOp::to_string(op);
|
||||
let eqop_span = lhs.span.between(un_rhs.span);
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SUSPICIOUS_UNARY_OP_FORMATTING,
|
||||
eqop_span,
|
||||
&format!(
|
||||
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
|
||||
`{binop_str}{unop_str}` is a single operator"
|
||||
),
|
||||
None,
|
||||
&format!(
|
||||
"put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"
|
||||
),
|
||||
);
|
||||
}
|
||||
&& binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ')
|
||||
{
|
||||
let unop_str = UnOp::to_string(op);
|
||||
let eqop_span = lhs.span.between(un_rhs.span);
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SUSPICIOUS_UNARY_OP_FORMATTING,
|
||||
eqop_span,
|
||||
&format!(
|
||||
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
|
||||
`{binop_str}{unop_str}` is a single operator"
|
||||
),
|
||||
None,
|
||||
&format!("put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
|
||||
fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if_chain! {
|
||||
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
|
||||
if is_block(else_) || is_if(else_);
|
||||
if !then.span.from_expansion() && !else_.span.from_expansion();
|
||||
if !in_external_macro(cx.sess(), expr.span);
|
||||
if let ExprKind::If(_, then, Some(else_)) = &expr.kind
|
||||
&& (is_block(else_) || is_if(else_))
|
||||
&& !then.span.from_expansion() && !else_.span.from_expansion()
|
||||
&& !in_external_macro(cx.sess(), expr.span)
|
||||
|
||||
// workaround for rust-lang/rust#43081
|
||||
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
|
||||
&& expr.span.lo().0 != 0 && expr.span.hi().0 != 0
|
||||
|
||||
// this will be a span from the closing ‘}’ of the “then” block (excluding) to
|
||||
// the “if” of the “else if” block (excluding)
|
||||
let else_span = then.span.between(else_.span);
|
||||
&& let else_span = then.span.between(else_.span)
|
||||
|
||||
// the snippet should look like " else \n " with maybe comments anywhere
|
||||
// it’s bad when there is a ‘\n’ after the “else”
|
||||
if let Some(else_snippet) = snippet_opt(cx, else_span);
|
||||
if let Some((pre_else, post_else)) = else_snippet.split_once("else");
|
||||
if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
|
||||
|
||||
then {
|
||||
// Allow allman style braces `} \n else \n {`
|
||||
if_chain! {
|
||||
if is_block(else_);
|
||||
if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
|
||||
// Exactly one eol before and after the else
|
||||
if !pre_else_post_eol.contains('\n');
|
||||
if !post_else_post_eol.contains('\n');
|
||||
then {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't warn if the only thing inside post_else_post_eol is a comment block.
|
||||
let trimmed_post_else_post_eol = post_else_post_eol.trim();
|
||||
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
|
||||
return
|
||||
}
|
||||
|
||||
let else_desc = if is_if(else_) { "if" } else { "{..}" };
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ELSE_FORMATTING,
|
||||
else_span,
|
||||
&format!("this is an `else {else_desc}` but the formatting might hide it"),
|
||||
None,
|
||||
&format!(
|
||||
"to remove this lint, remove the `else` or remove the new line between \
|
||||
`else` and `{else_desc}`",
|
||||
),
|
||||
);
|
||||
&& let Some(else_snippet) = snippet_opt(cx, else_span)
|
||||
&& let Some((pre_else, post_else)) = else_snippet.split_once("else")
|
||||
&& let Some((_, post_else_post_eol)) = post_else.split_once('\n')
|
||||
{
|
||||
// Allow allman style braces `} \n else \n {`
|
||||
if is_block(else_)
|
||||
&& let Some((_, pre_else_post_eol)) = pre_else.split_once('\n')
|
||||
// Exactly one eol before and after the else
|
||||
&& !pre_else_post_eol.contains('\n')
|
||||
&& !post_else_post_eol.contains('\n')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't warn if the only thing inside post_else_post_eol is a comment block.
|
||||
let trimmed_post_else_post_eol = post_else_post_eol.trim();
|
||||
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
|
||||
return;
|
||||
}
|
||||
|
||||
let else_desc = if is_if(else_) { "if" } else { "{..}" };
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ELSE_FORMATTING,
|
||||
else_span,
|
||||
&format!("this is an `else {else_desc}` but the formatting might hide it"),
|
||||
None,
|
||||
&format!(
|
||||
"to remove this lint, remove the `else` or remove the new line between \
|
||||
`else` and `{else_desc}`",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,61 +262,56 @@ fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize {
|
||||
fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if let ExprKind::Array(ref array) = expr.kind {
|
||||
for element in array {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
|
||||
if has_unary_equivalent(op.node) && lhs.span.eq_ctxt(op.span);
|
||||
let space_span = lhs.span.between(op.span);
|
||||
if let Some(space_snippet) = snippet_opt(cx, space_span);
|
||||
let lint_span = lhs.span.with_lo(lhs.span.hi());
|
||||
if space_snippet.contains('\n');
|
||||
if indentation(cx, op.span) <= indentation(cx, lhs.span);
|
||||
then {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
POSSIBLE_MISSING_COMMA,
|
||||
lint_span,
|
||||
"possibly missing a comma here",
|
||||
None,
|
||||
"to remove this lint, add a comma or write the expr in a single line",
|
||||
);
|
||||
}
|
||||
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind
|
||||
&& has_unary_equivalent(op.node)
|
||||
&& lhs.span.eq_ctxt(op.span)
|
||||
&& let space_span = lhs.span.between(op.span)
|
||||
&& let Some(space_snippet) = snippet_opt(cx, space_span)
|
||||
&& let lint_span = lhs.span.with_lo(lhs.span.hi())
|
||||
&& space_snippet.contains('\n')
|
||||
&& indentation(cx, op.span) <= indentation(cx, lhs.span)
|
||||
{
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
POSSIBLE_MISSING_COMMA,
|
||||
lint_span,
|
||||
"possibly missing a comma here",
|
||||
None,
|
||||
"to remove this lint, add a comma or write the expr in a single line",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
|
||||
if_chain! {
|
||||
if !first.span.from_expansion() && !second.span.from_expansion();
|
||||
if matches!(first.kind, ExprKind::If(..));
|
||||
if is_block(second) || is_if(second);
|
||||
if !first.span.from_expansion() && !second.span.from_expansion()
|
||||
&& matches!(first.kind, ExprKind::If(..))
|
||||
&& (is_block(second) || is_if(second))
|
||||
|
||||
// Proc-macros can give weird spans. Make sure this is actually an `if`.
|
||||
if is_span_if(cx, first.span);
|
||||
&& is_span_if(cx, first.span)
|
||||
|
||||
// If there is a line break between the two expressions, don't lint.
|
||||
// If there is a non-whitespace character, this span came from a proc-macro.
|
||||
let else_span = first.span.between(second.span);
|
||||
if let Some(else_snippet) = snippet_opt(cx, else_span);
|
||||
if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace());
|
||||
then {
|
||||
let (looks_like, next_thing) = if is_if(second) {
|
||||
("an `else if`", "the second `if`")
|
||||
} else {
|
||||
("an `else {..}`", "the next block")
|
||||
};
|
||||
&& let else_span = first.span.between(second.span)
|
||||
&& let Some(else_snippet) = snippet_opt(cx, else_span)
|
||||
&& !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace())
|
||||
{
|
||||
let (looks_like, next_thing) = if is_if(second) {
|
||||
("an `else if`", "the second `if`")
|
||||
} else {
|
||||
("an `else {..}`", "the next block")
|
||||
};
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ELSE_FORMATTING,
|
||||
else_span,
|
||||
&format!("this looks like {looks_like} but the `else` is missing"),
|
||||
None,
|
||||
&format!(
|
||||
"to remove this lint, add the missing `else` or add a new line before {next_thing}",
|
||||
),
|
||||
);
|
||||
}
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
SUSPICIOUS_ELSE_FORMATTING,
|
||||
else_span,
|
||||
&format!("this looks like {looks_like} but the `else` is missing"),
|
||||
None,
|
||||
&format!("to remove this lint, add the missing `else` or add a new line before {next_thing}",),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_integer_literal;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -46,52 +45,41 @@ declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind;
|
||||
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
|
||||
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind
|
||||
|
||||
// check if the first part of the path is some integer primitive
|
||||
if let TyKind::Path(ty_qpath) = &ty.kind;
|
||||
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
|
||||
if let def::Res::PrimTy(prim_ty) = ty_res;
|
||||
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
|
||||
&& let TyKind::Path(ty_qpath) = &ty.kind
|
||||
&& let ty_res = cx.qpath_res(ty_qpath, ty.hir_id)
|
||||
&& let def::Res::PrimTy(prim_ty) = ty_res
|
||||
&& matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_))
|
||||
|
||||
// check if the second part of the path indeed calls the associated
|
||||
// function `from_str_radix`
|
||||
if pathseg.ident.name.as_str() == "from_str_radix";
|
||||
&& pathseg.ident.name.as_str() == "from_str_radix"
|
||||
|
||||
// check if the second argument is a primitive `10`
|
||||
if is_integer_literal(radix, 10);
|
||||
&& is_integer_literal(radix, 10)
|
||||
{
|
||||
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if is_ty_stringish(cx, ty) { expr } else { &src }
|
||||
} else {
|
||||
&src
|
||||
};
|
||||
|
||||
then {
|
||||
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if is_ty_stringish(cx, ty) {
|
||||
expr
|
||||
} else {
|
||||
&src
|
||||
}
|
||||
} else {
|
||||
&src
|
||||
};
|
||||
let sugg =
|
||||
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
|
||||
|
||||
let sugg = Sugg::hir_with_applicability(
|
||||
cx,
|
||||
expr,
|
||||
"<string>",
|
||||
&mut Applicability::MachineApplicable
|
||||
).maybe_par();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FROM_STR_RADIX_10,
|
||||
exp.span,
|
||||
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
|
||||
"try",
|
||||
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
|
||||
Applicability::MaybeIncorrect
|
||||
);
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FROM_STR_RADIX_10,
|
||||
exp.span,
|
||||
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
|
||||
"try",
|
||||
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,10 @@ use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use super::IMPL_TRAIT_IN_PARAMS;
|
||||
|
||||
fn report(
|
||||
cx: &LateContext<'_>,
|
||||
param: &GenericParam<'_>,
|
||||
ident: &Ident,
|
||||
generics: &Generics<'_>,
|
||||
first_param_span: Span,
|
||||
) {
|
||||
fn report(cx: &LateContext<'_>, param: &GenericParam<'_>, generics: &Generics<'_>) {
|
||||
// No generics with nested generics, and no generics like FnMut(x)
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
@ -35,12 +27,7 @@ fn report(
|
||||
);
|
||||
} else {
|
||||
diag.span_suggestion_with_style(
|
||||
Span::new(
|
||||
first_param_span.lo() - rustc_span::BytePos(1),
|
||||
ident.span.hi(),
|
||||
ident.span.ctxt(),
|
||||
ident.span.parent(),
|
||||
),
|
||||
generics.span,
|
||||
"add a type parameter",
|
||||
format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]),
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
@ -52,54 +39,47 @@ fn report(
|
||||
}
|
||||
|
||||
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
|
||||
if_chain! {
|
||||
if let FnKind::ItemFn(ident, generics, _) = kind;
|
||||
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
|
||||
if !is_in_test_function(cx.tcx, hir_id);
|
||||
then {
|
||||
for param in generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, ident, generics, body.params[0].span);
|
||||
};
|
||||
}
|
||||
if let FnKind::ItemFn(_, generics, _) = kind
|
||||
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
|
||||
&& !is_in_test_function(cx.tcx, hir_id)
|
||||
{
|
||||
for param in generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, generics);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
if_chain! {
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id());
|
||||
if let hir::ItemKind::Impl(impl_) = item.kind;
|
||||
if let hir::Impl { of_trait, .. } = *impl_;
|
||||
if of_trait.is_none();
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
|
||||
if !is_in_test_function(cx.tcx, impl_item.hir_id());
|
||||
then {
|
||||
for param in impl_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
|
||||
}
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind
|
||||
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id())
|
||||
&& let hir::ItemKind::Impl(impl_) = item.kind
|
||||
&& let hir::Impl { of_trait, .. } = *impl_
|
||||
&& of_trait.is_none()
|
||||
&& let body = cx.tcx.hir().body(body_id)
|
||||
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
|
||||
&& !is_in_test_function(cx.tcx, impl_item.hir_id())
|
||||
{
|
||||
for param in impl_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, impl_item.generics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
|
||||
if_chain! {
|
||||
if !avoid_breaking_exported_api;
|
||||
if let TraitItemKind::Fn(_, _) = trait_item.kind;
|
||||
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
|
||||
if !avoid_breaking_exported_api
|
||||
&& let TraitItemKind::Fn(_, _) = trait_item.kind
|
||||
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id())
|
||||
// ^^ (Will always be a trait)
|
||||
if !item.vis_span.is_empty(); // Is public
|
||||
if !is_in_test_function(cx.tcx, trait_item.hir_id());
|
||||
then {
|
||||
for param in trait_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
|
||||
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
|
||||
}
|
||||
&& !item.vis_span.is_empty() // Is public
|
||||
&& !is_in_test_function(cx.tcx, trait_item.hir_id())
|
||||
{
|
||||
for param in trait_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, trait_item.generics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,15 +43,13 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
|
||||
// Body must be &(mut) <self_data>.name
|
||||
// self_data is not necessarily self, to also lint sub-getters, etc…
|
||||
|
||||
let block_expr = if_chain! {
|
||||
if let ExprKind::Block(block,_) = body.value.kind;
|
||||
if block.stmts.is_empty();
|
||||
if let Some(block_expr) = block.expr;
|
||||
then {
|
||||
block_expr
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& let Some(block_expr) = block.expr
|
||||
{
|
||||
block_expr
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let expr_span = block_expr.span;
|
||||
|
||||
@ -61,14 +59,12 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
|
||||
} else {
|
||||
block_expr
|
||||
};
|
||||
let (self_data, used_ident) = if_chain! {
|
||||
if let ExprKind::Field(self_data, ident) = expr.kind;
|
||||
if ident.name.as_str() != name;
|
||||
then {
|
||||
(self_data, ident)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let (self_data, used_ident) = if let ExprKind::Field(self_data, ident) = expr.kind
|
||||
&& ident.name.as_str() != name
|
||||
{
|
||||
(self_data, ident)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut used_field = None;
|
||||
|
@ -86,59 +86,60 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S
|
||||
}
|
||||
|
||||
fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
|
||||
if_chain! {
|
||||
if let Adt(adt, subst) = err_ty.kind();
|
||||
if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local();
|
||||
if let Some(hir::Node::Item(item)) = cx
|
||||
.tcx
|
||||
.hir()
|
||||
.find_by_def_id(local_def_id);
|
||||
if let hir::ItemKind::Enum(ref def, _) = item.kind;
|
||||
then {
|
||||
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
|
||||
if let Some((first_variant, variants)) = variants_size.split_first()
|
||||
&& first_variant.size >= large_err_threshold
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RESULT_LARGE_ERR,
|
||||
hir_ty_span,
|
||||
"the `Err`-variant returned from this function is very large",
|
||||
|diag| {
|
||||
diag.span_label(
|
||||
def.variants[first_variant.ind].span,
|
||||
format!("the largest variant contains at least {} bytes", variants_size[0].size),
|
||||
);
|
||||
if let Adt(adt, subst) = err_ty.kind()
|
||||
&& let Some(local_def_id) = err_ty
|
||||
.ty_adt_def()
|
||||
.expect("already checked this is adt")
|
||||
.did()
|
||||
.as_local()
|
||||
&& let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id(local_def_id)
|
||||
&& let hir::ItemKind::Enum(ref def, _) = item.kind
|
||||
{
|
||||
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
|
||||
if let Some((first_variant, variants)) = variants_size.split_first()
|
||||
&& first_variant.size >= large_err_threshold
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RESULT_LARGE_ERR,
|
||||
hir_ty_span,
|
||||
"the `Err`-variant returned from this function is very large",
|
||||
|diag| {
|
||||
diag.span_label(
|
||||
def.variants[first_variant.ind].span,
|
||||
format!("the largest variant contains at least {} bytes", variants_size[0].size),
|
||||
);
|
||||
|
||||
for variant in variants {
|
||||
if variant.size >= large_err_threshold {
|
||||
let variant_def = &def.variants[variant.ind];
|
||||
diag.span_label(
|
||||
variant_def.span,
|
||||
format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size),
|
||||
);
|
||||
}
|
||||
for variant in variants {
|
||||
if variant.size >= large_err_threshold {
|
||||
let variant_def = &def.variants[variant.ind];
|
||||
diag.span_label(
|
||||
variant_def.span,
|
||||
format!(
|
||||
"the variant `{}` contains at least {} bytes",
|
||||
variant_def.ident, variant.size
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
|
||||
},
|
||||
);
|
||||
}
|
||||
else {
|
||||
let ty_size = approx_ty_size(cx, err_ty);
|
||||
if ty_size >= large_err_threshold {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RESULT_LARGE_ERR,
|
||||
hir_ty_span,
|
||||
"the `Err`-variant returned from this function is very large",
|
||||
|diag: &mut Diagnostic| {
|
||||
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
|
||||
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let ty_size = approx_ty_size(cx, err_ty);
|
||||
if ty_size >= large_err_threshold {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RESULT_LARGE_ERR,
|
||||
hir_ty_span,
|
||||
"the `Err`-variant returned from this function is very large",
|
||||
|diag: &mut Diagnostic| {
|
||||
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
|
||||
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{higher, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Diagnostic;
|
||||
use rustc_hir::intravisit::{self as visit, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -127,15 +126,13 @@ impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
|
||||
}
|
||||
|
||||
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind;
|
||||
if path.ident.as_str() == "lock";
|
||||
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
|
||||
if is_type_diagnostic_item(cx, ty, sym::Mutex);
|
||||
then {
|
||||
Some(self_arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
|
||||
&& path.ident.as_str() == "lock"
|
||||
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
|
||||
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
|
||||
{
|
||||
Some(self_arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,8 @@ use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{Ty, TypeckResults};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use if_chain::if_chain;
|
||||
use rustc_span::Span;
|
||||
|
||||
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
@ -337,42 +335,38 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(fun, args) = e.kind;
|
||||
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
|
||||
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
|
||||
if let Some(ty_did) = ty_path.res.opt_def_id();
|
||||
then {
|
||||
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Call(fun, args) = e.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind
|
||||
&& let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind
|
||||
&& let Some(ty_did) = ty_path.res.opt_def_id()
|
||||
{
|
||||
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions
|
||||
.insert(e.span, "HashMap::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashMap::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions
|
||||
.insert(e.span, "HashSet::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashSet::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions.insert(e.span, "HashMap::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashMap::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
|
||||
if method.ident.name == sym::new {
|
||||
self.suggestions.insert(e.span, "HashSet::default()".to_string());
|
||||
} else if method.ident.name == sym!(with_capacity) {
|
||||
self.suggestions.insert(
|
||||
e.span,
|
||||
format!(
|
||||
"HashSet::with_capacity_and_hasher({}, Default::default())",
|
||||
snippet(self.cx, args[0].span, "capacity"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
|
||||
@ -40,42 +39,58 @@ declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let ExprKind::If(cond, then, None) = expr.kind;
|
||||
if let ExprKind::DropTemps(expr1) = cond.kind;
|
||||
if let Some((c, op_node, l)) = get_const(cx, expr1);
|
||||
if let BinOpKind::Ne | BinOpKind::Lt = op_node;
|
||||
if let ExprKind::Block(block, None) = then.kind;
|
||||
if let Block {
|
||||
if let ExprKind::If(cond, then, None) = expr.kind
|
||||
&& let ExprKind::DropTemps(expr1) = cond.kind
|
||||
&& let Some((c, op_node, l)) = get_const(cx, expr1)
|
||||
&& let BinOpKind::Ne | BinOpKind::Lt = op_node
|
||||
&& let ExprKind::Block(block, None) = then.kind
|
||||
&& let Block {
|
||||
stmts:
|
||||
[Stmt
|
||||
{ kind: StmtKind::Expr(ex) | StmtKind::Semi(ex), .. }],
|
||||
expr: None, ..} |
|
||||
Block { stmts: [], expr: Some(ex), ..} = block;
|
||||
if let ExprKind::AssignOp(op1, target, value) = ex.kind;
|
||||
let ty = cx.typeck_results().expr_ty(target);
|
||||
if Some(c) == get_int_max(ty);
|
||||
let ctxt = expr.span.ctxt();
|
||||
if ex.span.ctxt() == ctxt;
|
||||
if expr1.span.ctxt() == ctxt;
|
||||
if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target);
|
||||
if BinOpKind::Add == op1.node;
|
||||
if let ExprKind::Lit(lit) = value.kind;
|
||||
if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
|
||||
if block.expr.is_none();
|
||||
then {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
|
||||
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
|
||||
&& else_.hir_id == expr.hir_id
|
||||
{
|
||||
format!("{{{code} = {code}.saturating_add(1); }}")
|
||||
} else {
|
||||
format!("{code} = {code}.saturating_add(1);")
|
||||
};
|
||||
span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app);
|
||||
[
|
||||
Stmt {
|
||||
kind: StmtKind::Expr(ex) | StmtKind::Semi(ex),
|
||||
..
|
||||
},
|
||||
],
|
||||
expr: None,
|
||||
..
|
||||
}
|
||||
| Block {
|
||||
stmts: [],
|
||||
expr: Some(ex),
|
||||
..
|
||||
} = block
|
||||
&& let ExprKind::AssignOp(op1, target, value) = ex.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(target)
|
||||
&& Some(c) == get_int_max(ty)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& ex.span.ctxt() == ctxt
|
||||
&& expr1.span.ctxt() == ctxt
|
||||
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
|
||||
&& BinOpKind::Add == op1.node
|
||||
&& let ExprKind::Lit(lit) = value.kind
|
||||
&& let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
|
||||
&& block.expr.is_none()
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
|
||||
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
|
||||
&& else_.hir_id == expr.hir_id
|
||||
{
|
||||
format!("{{{code} = {code}.saturating_add(1); }}")
|
||||
} else {
|
||||
format!("{code} = {code}.saturating_add(1);")
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
IMPLICIT_SATURATING_ADD,
|
||||
expr.span,
|
||||
"manual saturating add detected",
|
||||
"use instead",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
|
||||
@ -46,83 +45,76 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
|
||||
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr)
|
||||
|
||||
// Check if the conditional expression is a binary operation
|
||||
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
|
||||
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
|
||||
|
||||
// Ensure that the binary operator is >, !=, or <
|
||||
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
|
||||
&& (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node)
|
||||
|
||||
// Check if assign operation is done
|
||||
if let Some(target) = subtracts_one(cx, then);
|
||||
&& let Some(target) = subtracts_one(cx, then)
|
||||
|
||||
// Extracting out the variable name
|
||||
if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
|
||||
|
||||
then {
|
||||
// Handle symmetric conditions in the if statement
|
||||
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
|
||||
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
|
||||
(cond_left, cond_right)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
|
||||
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
|
||||
(cond_right, cond_left)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
|
||||
{
|
||||
// Handle symmetric conditions in the if statement
|
||||
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
|
||||
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
|
||||
(cond_left, cond_right)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check if the variable in the condition statement is an integer
|
||||
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
|
||||
}
|
||||
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
|
||||
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
|
||||
(cond_right, cond_left)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Get the variable name
|
||||
let var_name = ares_path.segments[0].ident.name.as_str();
|
||||
match cond_num_val.kind {
|
||||
ExprKind::Lit(cond_lit) => {
|
||||
// Check if the constant is zero
|
||||
if let LitKind::Int(0, _) = cond_lit.node {
|
||||
if cx.typeck_results().expr_ty(cond_left).is_signed() {
|
||||
} else {
|
||||
print_lint_and_sugg(cx, var_name, expr);
|
||||
};
|
||||
}
|
||||
},
|
||||
ExprKind::Path(QPath::TypeRelative(_, name)) => {
|
||||
if_chain! {
|
||||
if name.ident.as_str() == "MIN";
|
||||
if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
|
||||
if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
|
||||
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
|
||||
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
|
||||
then {
|
||||
print_lint_and_sugg(cx, var_name, expr)
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, []) => {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
|
||||
if name.ident.as_str() == "min_value";
|
||||
if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
|
||||
if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
|
||||
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
|
||||
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
|
||||
then {
|
||||
print_lint_and_sugg(cx, var_name, expr)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
// Check if the variable in the condition statement is an integer
|
||||
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the variable name
|
||||
let var_name = ares_path.segments[0].ident.name.as_str();
|
||||
match cond_num_val.kind {
|
||||
ExprKind::Lit(cond_lit) => {
|
||||
// Check if the constant is zero
|
||||
if let LitKind::Int(0, _) = cond_lit.node {
|
||||
if cx.typeck_results().expr_ty(cond_left).is_signed() {
|
||||
} else {
|
||||
print_lint_and_sugg(cx, var_name, expr);
|
||||
};
|
||||
}
|
||||
},
|
||||
ExprKind::Path(QPath::TypeRelative(_, name)) => {
|
||||
if name.ident.as_str() == "MIN"
|
||||
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
|
||||
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
|
||||
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
|
||||
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
|
||||
{
|
||||
print_lint_and_sugg(cx, var_name, expr);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, []) => {
|
||||
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
|
||||
&& name.ident.as_str() == "min_value"
|
||||
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
|
||||
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
|
||||
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
|
||||
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
|
||||
{
|
||||
print_lint_and_sugg(cx, var_name, expr);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,18 +127,14 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
|
||||
(BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
|
||||
},
|
||||
ExprKind::Assign(target, value, _) => {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
|
||||
if BinOpKind::Sub == op1.node;
|
||||
|
||||
if SpanlessEq::new(cx).eq_expr(left1, target);
|
||||
|
||||
if is_integer_literal(right1, 1);
|
||||
then {
|
||||
Some(target)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let ExprKind::Binary(ref op1, left1, right1) = value.kind
|
||||
&& BinOpKind::Sub == op1.node
|
||||
&& SpanlessEq::new(cx).eq_expr(left1, target)
|
||||
&& is_integer_literal(right1, 1)
|
||||
{
|
||||
Some(target)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
|
@ -43,7 +43,7 @@ declare_clippy_lint! {
|
||||
/// Box::new(123)
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
#[clippy::version = "1.74.0"]
|
||||
pub IMPLIED_BOUNDS_IN_IMPLS,
|
||||
nursery,
|
||||
"specifying bounds that are implied by other bounds in `impl Trait` type"
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, ExprKind};
|
||||
@ -66,54 +65,53 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if !expr.span.from_expansion();
|
||||
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
if let Some(adt_def) = ty.ty_adt_def();
|
||||
if adt_def.is_struct();
|
||||
if let Some(variant) = adt_def.variants().iter().next();
|
||||
if fields.iter().all(|f| f.is_shorthand);
|
||||
then {
|
||||
let mut def_order_map = FxHashMap::default();
|
||||
for (idx, field) in variant.fields.iter().enumerate() {
|
||||
def_order_map.insert(field.name, idx);
|
||||
}
|
||||
|
||||
if is_consistent_order(fields, &def_order_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
|
||||
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
|
||||
|
||||
let mut fields_snippet = String::new();
|
||||
let (last_ident, idents) = ordered_fields.split_last().unwrap();
|
||||
for ident in idents {
|
||||
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
|
||||
}
|
||||
fields_snippet.push_str(&last_ident.to_string());
|
||||
|
||||
let base_snippet = if let Some(base) = base {
|
||||
format!(", ..{}", snippet(cx, base.span, ".."))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let sugg = format!("{} {{ {fields_snippet}{base_snippet} }}",
|
||||
snippet(cx, qpath.span(), ".."),
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INCONSISTENT_STRUCT_CONSTRUCTOR,
|
||||
expr.span,
|
||||
"struct constructor field order is inconsistent with struct definition field order",
|
||||
"try",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
if !expr.span.from_expansion()
|
||||
&& let ExprKind::Struct(qpath, fields, base) = expr.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(expr)
|
||||
&& let Some(adt_def) = ty.ty_adt_def()
|
||||
&& adt_def.is_struct()
|
||||
&& let Some(variant) = adt_def.variants().iter().next()
|
||||
&& fields.iter().all(|f| f.is_shorthand)
|
||||
{
|
||||
let mut def_order_map = FxHashMap::default();
|
||||
for (idx, field) in variant.fields.iter().enumerate() {
|
||||
def_order_map.insert(field.name, idx);
|
||||
}
|
||||
|
||||
if is_consistent_order(fields, &def_order_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
|
||||
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
|
||||
|
||||
let mut fields_snippet = String::new();
|
||||
let (last_ident, idents) = ordered_fields.split_last().unwrap();
|
||||
for ident in idents {
|
||||
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
|
||||
}
|
||||
fields_snippet.push_str(&last_ident.to_string());
|
||||
|
||||
let base_snippet = if let Some(base) = base {
|
||||
format!(", ..{}", snippet(cx, base.span, ".."))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let sugg = format!(
|
||||
"{} {{ {fields_snippet}{base_snippet} }}",
|
||||
snippet(cx, qpath.span(), ".."),
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INCONSISTENT_STRUCT_CONSTRUCTOR,
|
||||
expr.span,
|
||||
"struct constructor field order is inconsistent with struct definition field order",
|
||||
"try",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher::IfLet;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
@ -70,20 +69,17 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
|
||||
if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
|
||||
if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
|
||||
if self.msrv.meets(msrvs::SLICE_PATTERNS);
|
||||
|
||||
let found_slices = find_slice_values(cx, let_pat);
|
||||
if !found_slices.is_empty();
|
||||
let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
|
||||
if !filtered_slices.is_empty();
|
||||
then {
|
||||
for slice in filtered_slices.values() {
|
||||
lint_slice(cx, slice);
|
||||
}
|
||||
if (!expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some())
|
||||
&& let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
|
||||
&& !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
|
||||
&& self.msrv.meets(msrvs::SLICE_PATTERNS)
|
||||
&& let found_slices = find_slice_values(cx, let_pat)
|
||||
&& !found_slices.is_empty()
|
||||
&& let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then)
|
||||
&& !filtered_slices.is_empty()
|
||||
{
|
||||
for slice in filtered_slices.values() {
|
||||
lint_slice(cx, slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,28 +241,26 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
|
||||
max_suggested_slice,
|
||||
} = *self;
|
||||
|
||||
if_chain! {
|
||||
if let Some(use_info) = slice_lint_info.get_mut(&local_id)
|
||||
// Check if this is even a local we're interested in
|
||||
if let Some(use_info) = slice_lint_info.get_mut(&local_id);
|
||||
|
||||
let map = cx.tcx.hir();
|
||||
&& let map = cx.tcx.hir()
|
||||
|
||||
// Checking for slice indexing
|
||||
let parent_id = map.parent_id(expr.hir_id);
|
||||
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
|
||||
if let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind;
|
||||
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr);
|
||||
if let Ok(index_value) = index_value.try_into();
|
||||
if index_value < max_suggested_slice;
|
||||
&& let parent_id = map.parent_id(expr.hir_id)
|
||||
&& let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id)
|
||||
&& let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind
|
||||
&& let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr)
|
||||
&& let Ok(index_value) = index_value.try_into()
|
||||
&& index_value < max_suggested_slice
|
||||
|
||||
// Make sure that this slice index is read only
|
||||
let maybe_addrof_id = map.parent_id(parent_id);
|
||||
if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
|
||||
if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
|
||||
then {
|
||||
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
|
||||
return;
|
||||
}
|
||||
&& let maybe_addrof_id = map.parent_id(parent_id)
|
||||
&& let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id)
|
||||
&& let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind
|
||||
{
|
||||
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
|
||||
return;
|
||||
}
|
||||
|
||||
// The slice was used for something other than indexing
|
||||
|
@ -89,27 +89,17 @@ impl LateLintPass<'_> for InstantSubtraction {
|
||||
rhs,
|
||||
) = expr.kind
|
||||
{
|
||||
if_chain! {
|
||||
if is_instant_now_call(cx, lhs);
|
||||
|
||||
if is_an_instant(cx, rhs);
|
||||
if let Some(sugg) = Sugg::hir_opt(cx, rhs);
|
||||
|
||||
then {
|
||||
print_manual_instant_elapsed_sugg(cx, expr, sugg)
|
||||
} else {
|
||||
if_chain! {
|
||||
if !expr.span.from_expansion();
|
||||
if self.msrv.meets(msrvs::TRY_FROM);
|
||||
|
||||
if is_an_instant(cx, lhs);
|
||||
if is_a_duration(cx, rhs);
|
||||
|
||||
then {
|
||||
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_instant_now_call(cx, lhs)
|
||||
&& is_an_instant(cx, rhs)
|
||||
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
|
||||
{
|
||||
print_manual_instant_elapsed_sugg(cx, expr, sugg);
|
||||
} else if !expr.span.from_expansion()
|
||||
&& self.msrv.meets(msrvs::TRY_FROM)
|
||||
&& is_an_instant(cx, lhs)
|
||||
&& is_a_duration(cx, rhs)
|
||||
{
|
||||
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_sta
|
||||
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
78
clippy_lints/src/iter_over_hash_type.rs
Normal file
78
clippy_lints/src/iter_over_hash_type.rs
Normal 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",
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -47,43 +46,40 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if_chain! {
|
||||
if !item.span.from_expansion();
|
||||
if let ItemKind::Const(_, generics, _) = &item.kind;
|
||||
if !item.span.from_expansion()
|
||||
&& let ItemKind::Const(_, generics, _) = &item.kind
|
||||
// Since static items may not have generics, skip generic const items.
|
||||
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
|
||||
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
|
||||
if generics.params.is_empty() && !generics.has_where_clause_predicates;
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
if let ty::Array(element_type, cst) = ty.kind();
|
||||
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
|
||||
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);
|
||||
if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes());
|
||||
if self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size);
|
||||
|
||||
then {
|
||||
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
|
||||
let sugg_span = Span::new(
|
||||
hi_pos - BytePos::from_usize("const".len()),
|
||||
hi_pos,
|
||||
item.span.ctxt(),
|
||||
item.span.parent(),
|
||||
);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_CONST_ARRAYS,
|
||||
item.span,
|
||||
"large array defined as const",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
sugg_span,
|
||||
"make this a static item",
|
||||
"static",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
&& generics.params.is_empty() && !generics.has_where_clause_predicates
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& let ty::Array(element_type, cst) = ty.kind()
|
||||
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
|
||||
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
|
||||
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes())
|
||||
&& self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size)
|
||||
{
|
||||
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
|
||||
let sugg_span = Span::new(
|
||||
hi_pos - BytePos::from_usize("const".len()),
|
||||
hi_pos,
|
||||
item.span.ctxt(),
|
||||
item.span.parent(),
|
||||
);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_CONST_ARRAYS,
|
||||
item.span,
|
||||
"large array defined as const",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
sugg_span,
|
||||
"make this a static item",
|
||||
"static",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,37 +50,35 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
|
||||
|
||||
impl LateLintPass<'_> for LargeIncludeFile {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id);
|
||||
if cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|
||||
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id);
|
||||
if let ExprKind::Lit(lit) = &expr.kind;
|
||||
then {
|
||||
let len = match &lit.node {
|
||||
// include_bytes
|
||||
LitKind::ByteStr(bstr, _) => bstr.len(),
|
||||
// include_str
|
||||
LitKind::Str(sym, _) => sym.as_str().len(),
|
||||
_ => return,
|
||||
};
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id)
|
||||
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|
||||
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
|
||||
&& let ExprKind::Lit(lit) = &expr.kind
|
||||
{
|
||||
let len = match &lit.node {
|
||||
// include_bytes
|
||||
LitKind::ByteStr(bstr, _) => bstr.len(),
|
||||
// include_str
|
||||
LitKind::Str(sym, _) => sym.as_str().len(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if len as u64 <= self.max_file_size {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
LARGE_INCLUDE_FILE,
|
||||
expr.span,
|
||||
"attempted to include a large file",
|
||||
None,
|
||||
&format!(
|
||||
"the configuration allows a maximum size of {} bytes",
|
||||
self.max_file_size
|
||||
),
|
||||
);
|
||||
if len as u64 <= self.max_file_size {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
LARGE_INCLUDE_FILE,
|
||||
expr.span,
|
||||
"attempted to include a large file",
|
||||
None,
|
||||
&format!(
|
||||
"the configuration allows a maximum size of {} bytes",
|
||||
self.max_file_size
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
@ -15,9 +14,9 @@ use rustc_hir::{
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -132,37 +131,33 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
|
||||
if_chain! {
|
||||
if item.ident.name == sym::len;
|
||||
if let ImplItemKind::Fn(sig, _) = &item.kind;
|
||||
if sig.decl.implicit_self.has_implicit_self();
|
||||
if sig.decl.inputs.len() == 1;
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
|
||||
if matches!(sig.decl.output, FnRetTy::Return(_));
|
||||
if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
|
||||
if imp.of_trait.is_none();
|
||||
if let TyKind::Path(ty_path) = &imp.self_ty.kind;
|
||||
if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
|
||||
if let Some(local_id) = ty_id.as_local();
|
||||
let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
|
||||
if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
|
||||
if let Some(output) = parse_len_output(
|
||||
cx,
|
||||
cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder()
|
||||
);
|
||||
then {
|
||||
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
|
||||
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
|
||||
Some(Node::Item(x)) => match x.kind {
|
||||
ItemKind::Struct(..) => (x.ident.name, "struct"),
|
||||
ItemKind::Enum(..) => (x.ident.name, "enum"),
|
||||
ItemKind::Union(..) => (x.ident.name, "union"),
|
||||
_ => (x.ident.name, "type"),
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
|
||||
}
|
||||
if item.ident.name == sym::len
|
||||
&& let ImplItemKind::Fn(sig, _) = &item.kind
|
||||
&& sig.decl.implicit_self.has_implicit_self()
|
||||
&& sig.decl.inputs.len() == 1
|
||||
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
|
||||
&& matches!(sig.decl.output, FnRetTy::Return(_))
|
||||
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
|
||||
&& imp.of_trait.is_none()
|
||||
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
|
||||
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
|
||||
&& let Some(local_id) = ty_id.as_local()
|
||||
&& let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id)
|
||||
&& !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id)
|
||||
&& let Some(output) =
|
||||
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
|
||||
{
|
||||
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
|
||||
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
|
||||
Some(Node::Item(x)) => match x.kind {
|
||||
ItemKind::Struct(..) => (x.ident.name, "struct"),
|
||||
ItemKind::Enum(..) => (x.ident.name, "enum"),
|
||||
ItemKind::Union(..) => (x.ident.name, "union"),
|
||||
_ => (x.ident.name, "type"),
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::path_to_local_id;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{BindingAnnotation, Mutability};
|
||||
@ -61,76 +60,85 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
|
||||
let mut it = block.stmts.iter().peekable();
|
||||
while let Some(stmt) = it.next() {
|
||||
if_chain! {
|
||||
if let Some(expr) = it.peek();
|
||||
if let hir::StmtKind::Local(local) = stmt.kind;
|
||||
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
|
||||
if let hir::StmtKind::Expr(if_) = expr.kind;
|
||||
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
|
||||
if !is_local_used(cx, *cond, canonical_id);
|
||||
if let hir::ExprKind::Block(then, _) = then.kind;
|
||||
if let Some(value) = check_assign(cx, canonical_id, then);
|
||||
if !is_local_used(cx, value, canonical_id);
|
||||
then {
|
||||
let span = stmt.span.to(if_.span);
|
||||
if let Some(expr) = it.peek()
|
||||
&& let hir::StmtKind::Local(local) = stmt.kind
|
||||
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
|
||||
&& let hir::StmtKind::Expr(if_) = expr.kind
|
||||
&& let hir::ExprKind::If(
|
||||
hir::Expr {
|
||||
kind: hir::ExprKind::DropTemps(cond),
|
||||
..
|
||||
},
|
||||
then,
|
||||
else_,
|
||||
) = if_.kind
|
||||
&& !is_local_used(cx, *cond, canonical_id)
|
||||
&& let hir::ExprKind::Block(then, _) = then.kind
|
||||
&& let Some(value) = check_assign(cx, canonical_id, then)
|
||||
&& !is_local_used(cx, value, canonical_id)
|
||||
{
|
||||
let span = stmt.span.to(if_.span);
|
||||
|
||||
let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze(
|
||||
cx.tcx,
|
||||
cx.param_env,
|
||||
);
|
||||
if has_interior_mutability { return; }
|
||||
let has_interior_mutability = !cx
|
||||
.typeck_results()
|
||||
.node_type(canonical_id)
|
||||
.is_freeze(cx.tcx, cx.param_env);
|
||||
if has_interior_mutability {
|
||||
return;
|
||||
}
|
||||
|
||||
let (default_multi_stmts, default) = if let Some(else_) = else_ {
|
||||
if let hir::ExprKind::Block(else_, _) = else_.kind {
|
||||
if let Some(default) = check_assign(cx, canonical_id, else_) {
|
||||
(else_.stmts.len() > 1, default)
|
||||
} else if let Some(default) = local.init {
|
||||
(true, default)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
let (default_multi_stmts, default) = if let Some(else_) = else_ {
|
||||
if let hir::ExprKind::Block(else_, _) = else_.kind {
|
||||
if let Some(default) = check_assign(cx, canonical_id, else_) {
|
||||
(else_.stmts.len() > 1, default)
|
||||
} else if let Some(default) = local.init {
|
||||
(true, default)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if let Some(default) = local.init {
|
||||
(false, default)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
} else if let Some(default) = local.init {
|
||||
(false, default)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mutability = match mode {
|
||||
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
|
||||
_ => "",
|
||||
};
|
||||
let mutability = match mode {
|
||||
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
// FIXME: this should not suggest `mut` if we can detect that the variable is not
|
||||
// use mutably after the `if`
|
||||
// FIXME: this should not suggest `mut` if we can detect that the variable is not
|
||||
// use mutably after the `if`
|
||||
|
||||
let sug = format!(
|
||||
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
|
||||
name=ident.name,
|
||||
cond=snippet(cx, cond.span, "_"),
|
||||
then=if then.stmts.len() > 1 { " ..;" } else { "" },
|
||||
else=if default_multi_stmts { " ..;" } else { "" },
|
||||
value=snippet(cx, value.span, "<value>"),
|
||||
default=snippet(cx, default.span, "<default>"),
|
||||
);
|
||||
span_lint_and_then(cx,
|
||||
USELESS_LET_IF_SEQ,
|
||||
span,
|
||||
"`if _ { .. } else { .. }` is an expression",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"it is more idiomatic to write",
|
||||
sug,
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
if !mutability.is_empty() {
|
||||
diag.note("you might not need `mut` at all");
|
||||
}
|
||||
});
|
||||
}
|
||||
let sug = format!(
|
||||
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
|
||||
name=ident.name,
|
||||
cond=snippet(cx, cond.span, "_"),
|
||||
then=if then.stmts.len() > 1 { " ..;" } else { "" },
|
||||
else=if default_multi_stmts { " ..;" } else { "" },
|
||||
value=snippet(cx, value.span, "<value>"),
|
||||
default=snippet(cx, default.span, "<default>"),
|
||||
);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
USELESS_LET_IF_SEQ,
|
||||
span,
|
||||
"`if _ { .. } else { .. }` is an expression",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"it is more idiomatic to write",
|
||||
sug,
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
if !mutability.is_empty() {
|
||||
diag.note("you might not need `mut` at all");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,20 +149,23 @@ fn check_assign<'tcx>(
|
||||
decl: hir::HirId,
|
||||
block: &'tcx hir::Block<'_>,
|
||||
) -> Option<&'tcx hir::Expr<'tcx>> {
|
||||
if_chain! {
|
||||
if block.expr.is_none();
|
||||
if let Some(expr) = block.stmts.iter().last();
|
||||
if let hir::StmtKind::Semi(expr) = expr.kind;
|
||||
if let hir::ExprKind::Assign(var, value, _) = expr.kind;
|
||||
if path_to_local_id(var, decl);
|
||||
then {
|
||||
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
} else {
|
||||
if block.expr.is_none()
|
||||
&& let Some(expr) = block.stmts.iter().last()
|
||||
&& let hir::StmtKind::Semi(expr) = expr.kind
|
||||
&& let hir::ExprKind::Assign(var, value, _) = expr.kind
|
||||
&& path_to_local_id(var, decl)
|
||||
{
|
||||
if block
|
||||
.stmts
|
||||
.iter()
|
||||
.take(block.stmts.len() - 1)
|
||||
.any(|stmt| is_local_used(cx, stmt, decl))
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -27,27 +27,25 @@ declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
|
||||
|
||||
impl LateLintPass<'_> for UnderscoreTyped {
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.tcx.sess, local.span);
|
||||
if let Some(ty) = local.ty; // Ensure that it has a type defined
|
||||
if let TyKind::Infer = &ty.kind; // that type is '_'
|
||||
if local.span.eq_ctxt(ty.span);
|
||||
then {
|
||||
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
|
||||
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
|
||||
if snippet(cx, ty.span, "_").trim() != "_" {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
LET_WITH_TYPE_UNDERSCORE,
|
||||
local.span,
|
||||
"variable declared with type underscore",
|
||||
Some(ty.span.with_lo(local.pat.span.hi())),
|
||||
"remove the explicit type `_` declaration"
|
||||
)
|
||||
if !in_external_macro(cx.tcx.sess, local.span)
|
||||
&& let Some(ty) = local.ty // Ensure that it has a type defined
|
||||
&& let TyKind::Infer = &ty.kind // that type is '_'
|
||||
&& local.span.eq_ctxt(ty.span)
|
||||
{
|
||||
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
|
||||
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
|
||||
if snippet(cx, ty.span, "_").trim() != "_" {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
LET_WITH_TYPE_UNDERSCORE,
|
||||
local.span,
|
||||
"variable declared with type underscore",
|
||||
Some(ty.span.with_lo(local.pat.span.hi())),
|
||||
"remove the explicit type `_` declaration",
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ extern crate declare_clippy_lint;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_lint::{Lint, LintId};
|
||||
use rustc_session::Session;
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
pub mod deprecated_lints;
|
||||
@ -165,6 +164,7 @@ mod item_name_repetitions;
|
||||
mod items_after_statements;
|
||||
mod items_after_test_module;
|
||||
mod iter_not_returning_iterator;
|
||||
mod iter_over_hash_type;
|
||||
mod iter_without_into_iter;
|
||||
mod large_const_arrays;
|
||||
mod large_enum_variant;
|
||||
@ -308,7 +308,6 @@ mod slow_vector_initialization;
|
||||
mod std_instead_of_core;
|
||||
mod strings;
|
||||
mod strlen_on_c_strings;
|
||||
mod suspicious_doc_comments;
|
||||
mod suspicious_operation_groupings;
|
||||
mod suspicious_trait_impl;
|
||||
mod suspicious_xor_used_as_pow;
|
||||
@ -492,11 +491,83 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
|
||||
groups.register(store);
|
||||
}
|
||||
|
||||
/// Register all lints and lint groups with the rustc plugin registry
|
||||
/// Register all lints and lint groups with the rustc lint store
|
||||
///
|
||||
/// Used in `./src/driver.rs`.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &'static Conf) {
|
||||
pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
let Conf {
|
||||
ref absolute_paths_allowed_crates,
|
||||
absolute_paths_max_segments,
|
||||
accept_comment_above_attributes,
|
||||
accept_comment_above_statement,
|
||||
allow_dbg_in_tests,
|
||||
allow_expect_in_tests,
|
||||
allow_mixed_uninlined_format_args,
|
||||
allow_one_hash_in_raw_strings,
|
||||
allow_print_in_tests,
|
||||
allow_private_module_inception,
|
||||
allow_unwrap_in_tests,
|
||||
ref allowed_dotfiles,
|
||||
ref allowed_idents_below_min_chars,
|
||||
ref allowed_scripts,
|
||||
ref arithmetic_side_effects_allowed_binary,
|
||||
ref arithmetic_side_effects_allowed_unary,
|
||||
ref arithmetic_side_effects_allowed,
|
||||
array_size_threshold,
|
||||
avoid_breaking_exported_api,
|
||||
ref await_holding_invalid_types,
|
||||
cargo_ignore_publish,
|
||||
cognitive_complexity_threshold,
|
||||
ref disallowed_macros,
|
||||
ref disallowed_methods,
|
||||
ref disallowed_names,
|
||||
ref disallowed_types,
|
||||
ref doc_valid_idents,
|
||||
enable_raw_pointer_heuristic_for_send,
|
||||
enforce_iter_loop_reborrow,
|
||||
ref enforced_import_renames,
|
||||
enum_variant_name_threshold,
|
||||
enum_variant_size_threshold,
|
||||
excessive_nesting_threshold,
|
||||
future_size_threshold,
|
||||
ref ignore_interior_mutability,
|
||||
large_error_threshold,
|
||||
literal_representation_threshold,
|
||||
matches_for_let_else,
|
||||
max_fn_params_bools,
|
||||
max_include_file_size,
|
||||
max_struct_bools,
|
||||
max_suggested_slice_pattern_length,
|
||||
max_trait_bounds,
|
||||
min_ident_chars_threshold,
|
||||
missing_docs_in_crate_items,
|
||||
ref msrv,
|
||||
pass_by_value_size_limit,
|
||||
semicolon_inside_block_ignore_singleline,
|
||||
semicolon_outside_block_ignore_multiline,
|
||||
single_char_binding_names_threshold,
|
||||
stack_size_threshold,
|
||||
ref standard_macro_braces,
|
||||
struct_field_name_threshold,
|
||||
suppress_restriction_lint_in_const,
|
||||
too_large_for_stack,
|
||||
too_many_arguments_threshold,
|
||||
too_many_lines_threshold,
|
||||
trivial_copy_size_limit,
|
||||
type_complexity_threshold,
|
||||
unnecessary_box_size,
|
||||
unreadable_literal_lint_fractions,
|
||||
upper_case_acronyms_aggressive,
|
||||
vec_box_size_threshold,
|
||||
verbose_bit_mask_threshold,
|
||||
warn_on_all_wildcard_imports,
|
||||
|
||||
blacklisted_names: _,
|
||||
cyclomatic_complexity_threshold: _,
|
||||
} = *conf;
|
||||
let msrv = || msrv.clone();
|
||||
|
||||
register_removed_non_tool_lints(store);
|
||||
register_categories(store);
|
||||
|
||||
@ -521,7 +592,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| {
|
||||
Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new())
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(utils::internal_lints::if_chain_style::IfChainStyle));
|
||||
store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths));
|
||||
store.register_late_pass(|_| {
|
||||
Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default()
|
||||
@ -537,9 +607,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
});
|
||||
}
|
||||
|
||||
let arithmetic_side_effects_allowed = conf.arithmetic_side_effects_allowed.clone();
|
||||
let arithmetic_side_effects_allowed_binary = conf.arithmetic_side_effects_allowed_binary.clone();
|
||||
let arithmetic_side_effects_allowed_unary = conf.arithmetic_side_effects_allowed_unary.clone();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(
|
||||
arithmetic_side_effects_allowed
|
||||
@ -557,16 +624,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
|
||||
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
|
||||
store.register_late_pass(|_| Box::new(utils::author::Author));
|
||||
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(await_holding_invalid::AwaitHolding::new(
|
||||
await_holding_invalid_types.clone(),
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(serde_api::SerdeApi));
|
||||
let vec_box_size_threshold = conf.vec_box_size_threshold;
|
||||
let type_complexity_threshold = conf.type_complexity_threshold;
|
||||
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(types::Types::new(
|
||||
vec_box_size_threshold,
|
||||
@ -599,19 +662,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
|
||||
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
|
||||
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
|
||||
|
||||
let msrv = || conf.msrv.clone();
|
||||
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
|
||||
let allow_expect_in_tests = conf.allow_expect_in_tests;
|
||||
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
|
||||
let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const;
|
||||
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
|
||||
let allowed_dotfiles = conf
|
||||
.allowed_dotfiles
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(methods::DEFAULT_ALLOWED_DOTFILES.iter().copied().map(ToOwned::to_owned))
|
||||
.collect::<FxHashSet<_>>();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(methods::Methods::new(
|
||||
avoid_breaking_exported_api,
|
||||
@ -622,7 +673,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
))
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
|
||||
let matches_for_let_else = conf.matches_for_let_else;
|
||||
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(msrv())));
|
||||
@ -639,7 +689,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount));
|
||||
store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod));
|
||||
let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(index_refutable_slice::IndexRefutableSlice::new(
|
||||
max_suggested_slice_pattern_length,
|
||||
@ -648,7 +697,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
});
|
||||
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
|
||||
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
|
||||
let enforce_iter_loop_reborrow = conf.enforce_iter_loop_reborrow;
|
||||
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv(), enforce_iter_loop_reborrow)));
|
||||
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
|
||||
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
|
||||
@ -662,13 +710,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(no_effect::NoEffect));
|
||||
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
|
||||
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
|
||||
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(cognitive_complexity::CognitiveComplexity::new(
|
||||
cognitive_complexity_threshold,
|
||||
))
|
||||
});
|
||||
let too_large_for_stack = conf.too_large_for_stack;
|
||||
store.register_late_pass(move |_| Box::new(escape::BoxedLocal { too_large_for_stack }));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(vec::UselessVec {
|
||||
@ -684,18 +730,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
|
||||
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
|
||||
store.register_late_pass(|_| Box::<regex::Regex>::default());
|
||||
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
|
||||
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
|
||||
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
|
||||
store.register_late_pass(|_| Box::new(format::UselessFormat));
|
||||
store.register_late_pass(|_| Box::new(swap::Swap));
|
||||
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
|
||||
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
|
||||
let disallowed_names = conf.disallowed_names.iter().cloned().collect::<FxHashSet<_>>();
|
||||
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone())));
|
||||
let too_many_arguments_threshold = conf.too_many_arguments_threshold;
|
||||
let too_many_lines_threshold = conf.too_many_lines_threshold;
|
||||
let large_error_threshold = conf.large_error_threshold;
|
||||
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(functions::Functions::new(
|
||||
too_many_arguments_threshold,
|
||||
@ -704,9 +745,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
avoid_breaking_exported_api,
|
||||
))
|
||||
});
|
||||
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
|
||||
let missing_docs_in_crate_items = conf.missing_docs_in_crate_items;
|
||||
store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
|
||||
store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents)));
|
||||
store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply));
|
||||
store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq));
|
||||
store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
|
||||
@ -716,17 +755,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk));
|
||||
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
|
||||
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
|
||||
let enum_variant_size_threshold = conf.enum_variant_size_threshold;
|
||||
store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
|
||||
store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite));
|
||||
store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
|
||||
let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
|
||||
conf.trivial_copy_size_limit,
|
||||
conf.pass_by_value_size_limit,
|
||||
conf.avoid_breaking_exported_api,
|
||||
&sess.target,
|
||||
);
|
||||
store.register_late_pass(move |_| Box::new(pass_by_ref_or_value));
|
||||
store.register_late_pass(move |tcx| {
|
||||
Box::new(pass_by_ref_or_value::PassByRefOrValue::new(
|
||||
trivial_copy_size_limit,
|
||||
pass_by_value_size_limit,
|
||||
avoid_breaking_exported_api,
|
||||
tcx.sess.target.pointer_width,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef));
|
||||
store.register_late_pass(|_| Box::new(infinite_iter::InfiniteIter));
|
||||
store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody));
|
||||
@ -746,7 +785,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
suppress_restriction_lint_in_const,
|
||||
))
|
||||
});
|
||||
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
|
||||
store.register_late_pass(move |_| Box::new(non_copy_const::NonCopyConst::new(ignore_interior_mutability.clone())));
|
||||
store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
|
||||
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
|
||||
@ -755,10 +793,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants));
|
||||
store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
|
||||
store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString));
|
||||
let max_trait_bounds = conf.max_trait_bounds;
|
||||
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(max_trait_bounds, msrv())));
|
||||
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
|
||||
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
|
||||
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
|
||||
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
|
||||
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
|
||||
@ -779,21 +815,16 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_early_pass(|| Box::new(redundant_else::RedundantElse));
|
||||
store.register_late_pass(|_| Box::new(create_dir::CreateDir));
|
||||
store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType));
|
||||
let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
|
||||
store.register_early_pass(move || {
|
||||
Box::new(literal_representation::LiteralDigitGrouping::new(
|
||||
literal_representation_lint_fraction_readability,
|
||||
unreadable_literal_lint_fractions,
|
||||
))
|
||||
});
|
||||
let literal_representation_threshold = conf.literal_representation_threshold;
|
||||
store.register_early_pass(move || {
|
||||
Box::new(literal_representation::DecimalLiteralRepresentation::new(
|
||||
literal_representation_threshold,
|
||||
))
|
||||
});
|
||||
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
|
||||
let struct_field_name_threshold = conf.struct_field_name_threshold;
|
||||
let allow_private_module_inception = conf.allow_private_module_inception;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(item_name_repetitions::ItemNameRepetitions::new(
|
||||
enum_variant_name_threshold,
|
||||
@ -803,7 +834,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
|
||||
let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
|
||||
avoid_breaking_exported_api,
|
||||
@ -815,15 +845,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
|
||||
store.register_late_pass(|_| Box::new(exit::Exit));
|
||||
store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome));
|
||||
let array_size_threshold = u128::from(conf.array_size_threshold);
|
||||
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
|
||||
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
|
||||
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold.into())));
|
||||
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold.into())));
|
||||
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
|
||||
store.register_late_pass(|_| Box::new(as_conversions::AsConversions));
|
||||
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
|
||||
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
|
||||
let max_fn_params_bools = conf.max_fn_params_bools;
|
||||
let max_struct_bools = conf.max_struct_bools;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(excessive_bools::ExcessiveBools::new(
|
||||
max_struct_bools,
|
||||
@ -831,36 +858,30 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
|
||||
let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
|
||||
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
|
||||
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
|
||||
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
|
||||
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
|
||||
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
|
||||
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
|
||||
let future_size_threshold = conf.future_size_threshold;
|
||||
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
|
||||
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
|
||||
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
|
||||
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
|
||||
store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn));
|
||||
store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn));
|
||||
let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
|
||||
store.register_early_pass(move || {
|
||||
Box::new(non_expressive_names::NonExpressiveNames {
|
||||
single_char_binding_names_threshold,
|
||||
})
|
||||
});
|
||||
let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::<FxHashSet<_>>();
|
||||
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher)));
|
||||
store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(standard_macro_braces)));
|
||||
store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default());
|
||||
store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch));
|
||||
store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult));
|
||||
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
|
||||
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync));
|
||||
let disallowed_macros = conf.disallowed_macros.clone();
|
||||
store.register_late_pass(move |_| Box::new(disallowed_macros::DisallowedMacros::new(disallowed_macros.clone())));
|
||||
let disallowed_methods = conf.disallowed_methods.clone();
|
||||
store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
|
||||
@ -875,36 +896,30 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
|
||||
store.register_early_pass(move || Box::new(module_style::ModStyle));
|
||||
store.register_late_pass(|_| Box::<unused_async::UnusedAsync>::default());
|
||||
let disallowed_types = conf.disallowed_types.clone();
|
||||
store.register_late_pass(move |_| Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone())));
|
||||
let import_renames = conf.enforced_import_renames.clone();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(missing_enforced_import_rename::ImportRename::new(
|
||||
import_renames.clone(),
|
||||
enforced_import_renames.clone(),
|
||||
))
|
||||
});
|
||||
let scripts = conf.allowed_scripts.clone();
|
||||
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
|
||||
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(allowed_scripts)));
|
||||
store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings));
|
||||
store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors));
|
||||
store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator));
|
||||
store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert));
|
||||
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
|
||||
enable_raw_pointer_heuristic_for_send,
|
||||
))
|
||||
});
|
||||
let accept_comment_above_statement = conf.accept_comment_above_statement;
|
||||
let accept_comment_above_attributes = conf.accept_comment_above_attributes;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(
|
||||
accept_comment_above_statement,
|
||||
accept_comment_above_attributes,
|
||||
))
|
||||
});
|
||||
let allow_mixed_uninlined = conf.allow_mixed_uninlined_format_args;
|
||||
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined)));
|
||||
store
|
||||
.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args)));
|
||||
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
|
||||
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
|
||||
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
|
||||
@ -914,11 +929,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
|
||||
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
|
||||
let allow_dbg_in_tests = conf.allow_dbg_in_tests;
|
||||
store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
|
||||
let allow_print_in_tests = conf.allow_print_in_tests;
|
||||
store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests)));
|
||||
let cargo_ignore_publish = conf.cargo_ignore_publish;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(cargo::Cargo {
|
||||
ignore_publish: cargo_ignore_publish,
|
||||
@ -929,7 +941,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
|
||||
store.register_early_pass(|| Box::new(pub_use::PubUse));
|
||||
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
|
||||
let max_include_file_size = conf.max_include_file_size;
|
||||
store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
|
||||
store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace));
|
||||
store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
|
||||
@ -942,7 +953,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
|
||||
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
|
||||
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
|
||||
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
|
||||
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
|
||||
@ -959,8 +969,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr));
|
||||
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
|
||||
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv())));
|
||||
let semicolon_inside_block_ignore_singleline = conf.semicolon_inside_block_ignore_singleline;
|
||||
let semicolon_outside_block_ignore_multiline = conf.semicolon_outside_block_ignore_multiline;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(semicolon_block::SemicolonBlock::new(
|
||||
semicolon_inside_block_ignore_singleline,
|
||||
@ -983,7 +991,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
|
||||
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
|
||||
let unnecessary_box_size = conf.unnecessary_box_size;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
|
||||
avoid_breaking_exported_api,
|
||||
@ -993,8 +1000,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
|
||||
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
|
||||
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
|
||||
let excessive_nesting_threshold = conf.excessive_nesting_threshold;
|
||||
store.register_early_pass(move || {
|
||||
Box::new(excessive_nesting::ExcessiveNesting {
|
||||
excessive_nesting_threshold,
|
||||
@ -1010,15 +1015,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
|
||||
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
|
||||
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf));
|
||||
let allowed_idents_below_min_chars = conf.allowed_idents_below_min_chars.clone();
|
||||
let min_ident_chars_threshold = conf.min_ident_chars_threshold;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(min_ident_chars::MinIdentChars {
|
||||
allowed_idents_below_min_chars: allowed_idents_below_min_chars.clone(),
|
||||
min_ident_chars_threshold,
|
||||
})
|
||||
});
|
||||
let stack_size_threshold = conf.stack_size_threshold;
|
||||
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
|
||||
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
|
||||
store.register_late_pass(move |_| {
|
||||
@ -1033,10 +1035,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(),
|
||||
})
|
||||
});
|
||||
let needless_raw_string_hashes_allow_one = conf.allow_one_hash_in_raw_strings;
|
||||
store.register_early_pass(move || {
|
||||
Box::new(raw_strings::RawStrings {
|
||||
needless_raw_string_hashes_allow_one,
|
||||
allow_one_hash_in_raw_strings,
|
||||
})
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
|
||||
@ -1045,8 +1046,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
|
||||
store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes));
|
||||
store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError));
|
||||
let absolute_paths_max_segments = conf.absolute_paths_max_segments;
|
||||
let absolute_paths_allowed_crates = conf.absolute_paths_allowed_crates.clone();
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(absolute_paths::AbsolutePaths {
|
||||
absolute_paths_max_segments,
|
||||
@ -1066,6 +1065,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
|
||||
store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ use rustc_middle::hir::nested_filter as middle_nested_filter;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::{kw, Ident, Symbol};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -310,20 +310,17 @@ fn elision_suggestions(
|
||||
|
||||
// elision doesn't work for explicit self types, see rust-lang/rust#69064
|
||||
fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(ident) = ident;
|
||||
if ident.name == kw::SelfLower;
|
||||
if !func.implicit_self.has_implicit_self();
|
||||
if let Some(ident) = ident
|
||||
&& ident.name == kw::SelfLower
|
||||
&& !func.implicit_self.has_implicit_self()
|
||||
&& let Some(self_ty) = func.inputs.first()
|
||||
{
|
||||
let mut visitor = RefVisitor::new(cx);
|
||||
visitor.visit_ty(self_ty);
|
||||
|
||||
if let Some(self_ty) = func.inputs.first();
|
||||
then {
|
||||
let mut visitor = RefVisitor::new(cx);
|
||||
visitor.visit_ty(self_ty);
|
||||
|
||||
!visitor.all_lts().is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
!visitor.all_lts().is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::numeric_literal::{NumericLiteral, Radix};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{Expr, ExprKind, LitKind};
|
||||
use rustc_ast::token;
|
||||
use rustc_errors::Applicability;
|
||||
@ -255,56 +254,48 @@ impl LiteralDigitGrouping {
|
||||
}
|
||||
|
||||
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
|
||||
if_chain! {
|
||||
if let Some(src) = snippet_opt(cx, span);
|
||||
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
|
||||
if let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
|
||||
then {
|
||||
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
|
||||
return;
|
||||
}
|
||||
if let Some(src) = snippet_opt(cx, span)
|
||||
&& let Ok(lit_kind) = LitKind::from_token_lit(lit)
|
||||
&& let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
|
||||
{
|
||||
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if Self::is_literal_uuid_formatted(&num_lit) {
|
||||
return;
|
||||
}
|
||||
if Self::is_literal_uuid_formatted(&num_lit) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = (|| {
|
||||
let result = (|| {
|
||||
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
|
||||
if let Some(fraction) = num_lit.fraction {
|
||||
let fractional_group_size =
|
||||
Self::get_group_size(fraction.rsplit('_'), num_lit.radix, self.lint_fraction_readability)?;
|
||||
|
||||
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
|
||||
if let Some(fraction) = num_lit.fraction {
|
||||
let fractional_group_size = Self::get_group_size(
|
||||
fraction.rsplit('_'),
|
||||
num_lit.radix,
|
||||
self.lint_fraction_readability)?;
|
||||
|
||||
let consistent = Self::parts_consistent(integral_group_size,
|
||||
fractional_group_size,
|
||||
num_lit.integer.len(),
|
||||
fraction.len());
|
||||
if !consistent {
|
||||
return Err(WarningType::InconsistentDigitGrouping);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
|
||||
if let Err(warning_type) = result {
|
||||
let should_warn = match warning_type {
|
||||
| WarningType::UnreadableLiteral
|
||||
| WarningType::InconsistentDigitGrouping
|
||||
| WarningType::UnusualByteGroupings
|
||||
| WarningType::LargeDigitGroups => {
|
||||
!span.from_expansion()
|
||||
}
|
||||
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
|
||||
true
|
||||
}
|
||||
let consistent = Self::parts_consistent(
|
||||
integral_group_size,
|
||||
fractional_group_size,
|
||||
num_lit.integer.len(),
|
||||
fraction.len(),
|
||||
);
|
||||
if !consistent {
|
||||
return Err(WarningType::InconsistentDigitGrouping);
|
||||
};
|
||||
if should_warn {
|
||||
warning_type.display(num_lit.format(), cx, span);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if let Err(warning_type) = result {
|
||||
let should_warn = match warning_type {
|
||||
WarningType::UnreadableLiteral
|
||||
| WarningType::InconsistentDigitGrouping
|
||||
| WarningType::UnusualByteGroupings
|
||||
| WarningType::LargeDigitGroups => !span.from_expansion(),
|
||||
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
|
||||
};
|
||||
if should_warn {
|
||||
warning_type.display(num_lit.format(), cx, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -478,20 +469,18 @@ impl DecimalLiteralRepresentation {
|
||||
}
|
||||
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
|
||||
// Lint integral literals.
|
||||
if_chain! {
|
||||
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
|
||||
if let LitKind::Int(val, _) = lit_kind;
|
||||
if let Some(src) = snippet_opt(cx, span);
|
||||
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
|
||||
if num_lit.radix == Radix::Decimal;
|
||||
if val >= u128::from(self.threshold);
|
||||
then {
|
||||
let hex = format!("{val:#X}");
|
||||
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
|
||||
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
|
||||
warning_type.display(num_lit.format(), cx, span);
|
||||
});
|
||||
}
|
||||
if let Ok(lit_kind) = LitKind::from_token_lit(lit)
|
||||
&& let LitKind::Int(val, _) = lit_kind
|
||||
&& let Some(src) = snippet_opt(cx, span)
|
||||
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
|
||||
&& num_lit.radix == Radix::Decimal
|
||||
&& val >= u128::from(self.threshold)
|
||||
{
|
||||
let hex = format!("{val:#X}");
|
||||
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
|
||||
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
|
||||
warning_type.display(num_lit.format(), cx, span);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{get_enclosing_block, is_integer_const};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_block, walk_expr};
|
||||
use rustc_hir::{Expr, Pat};
|
||||
@ -30,59 +29,57 @@ pub(super) fn check<'tcx>(
|
||||
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
|
||||
walk_block(&mut initialize_visitor, block);
|
||||
|
||||
if_chain! {
|
||||
if let Some((name, ty, initializer)) = initialize_visitor.get_result();
|
||||
if is_integer_const(cx, initializer, 0);
|
||||
then {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let span = expr.span.with_hi(arg.span.hi());
|
||||
if let Some((name, ty, initializer)) = initialize_visitor.get_result()
|
||||
&& is_integer_const(cx, initializer, 0)
|
||||
{
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let span = expr.span.with_hi(arg.span.hi());
|
||||
|
||||
let int_name = match ty.map(Ty::kind) {
|
||||
// usize or inferred
|
||||
Some(ty::Uint(UintTy::Usize)) | None => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
span,
|
||||
&format!("the variable `{name}` is used as a loop counter"),
|
||||
"consider using",
|
||||
format!(
|
||||
"for ({name}, {}) in {}.enumerate()",
|
||||
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
|
||||
make_iterator_snippet(cx, arg, &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Some(ty::Int(int_ty)) => int_ty.name_str(),
|
||||
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
|
||||
_ => return,
|
||||
};
|
||||
let int_name = match ty.map(Ty::kind) {
|
||||
// usize or inferred
|
||||
Some(ty::Uint(UintTy::Usize)) | None => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
span,
|
||||
&format!("the variable `{name}` is used as a loop counter"),
|
||||
"consider using",
|
||||
format!(
|
||||
"for ({name}, {}) in {}.enumerate()",
|
||||
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
|
||||
make_iterator_snippet(cx, arg, &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
return;
|
||||
},
|
||||
Some(ty::Int(int_ty)) => int_ty.name_str(),
|
||||
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
span,
|
||||
&format!("the variable `{name}` is used as a loop counter"),
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"consider using",
|
||||
format!(
|
||||
"for ({name}, {}) in (0_{int_name}..).zip({})",
|
||||
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
|
||||
make_iterator_snippet(cx, arg, &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EXPLICIT_COUNTER_LOOP,
|
||||
span,
|
||||
&format!("the variable `{name}` is used as a loop counter"),
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"consider using",
|
||||
format!(
|
||||
"for ({name}, {}) in (0_{int_name}..).zip({})",
|
||||
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
|
||||
make_iterator_snippet(cx, arg, &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
|
||||
diag.note(format!(
|
||||
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
diag.note(format!(
|
||||
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
@ -23,77 +22,79 @@ pub(super) fn check<'tcx>(
|
||||
let inner_expr = peel_blocks_with_stmt(body);
|
||||
// Check for the specific case that the result is returned and optimize suggestion for that (more
|
||||
// cases can be added later)
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
|
||||
if let Some(binding_id) = get_binding(pat);
|
||||
if let ExprKind::Block(block, _) = then.kind;
|
||||
if let [stmt] = block.stmts;
|
||||
if let StmtKind::Semi(semi) = stmt.kind;
|
||||
if let ExprKind::Ret(Some(ret_value)) = semi.kind;
|
||||
if let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind;
|
||||
if is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome);
|
||||
if path_res(cx, inner_ret) == Res::Local(binding_id);
|
||||
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
|
||||
// Checks if `pat` is a single reference to a binding (`&x`)
|
||||
let is_ref_to_binding =
|
||||
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
|
||||
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
|
||||
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
|
||||
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".map(|{}| {})",
|
||||
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
}
|
||||
let ty = cx.typeck_results().expr_ty(inner_ret);
|
||||
if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".find(|{}{}| {})",
|
||||
"&".repeat(1 + usize::from(is_ref_to_binding)),
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
if is_ref_to_binding {
|
||||
snippet.push_str(".copied()");
|
||||
}
|
||||
} else {
|
||||
applicability = Applicability::MaybeIncorrect;
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".find(|{}| {})",
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
}
|
||||
// Extends to `last_stmt` to include semicolon in case of `return None;`
|
||||
let lint_span = span.to(last_stmt.span).to(last_ret.span);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_FIND,
|
||||
lint_span,
|
||||
"manual implementation of `Iterator::find`",
|
||||
|diag| {
|
||||
if applicability == Applicability::MaybeIncorrect {
|
||||
diag.note("you may need to dereference some variables");
|
||||
}
|
||||
diag.span_suggestion(
|
||||
lint_span,
|
||||
"replace with an iterator",
|
||||
snippet,
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
if let Some(higher::If {
|
||||
cond,
|
||||
then,
|
||||
r#else: None,
|
||||
}) = higher::If::hir(inner_expr)
|
||||
&& let Some(binding_id) = get_binding(pat)
|
||||
&& let ExprKind::Block(block, _) = then.kind
|
||||
&& let [stmt] = block.stmts
|
||||
&& let StmtKind::Semi(semi) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret_value)) = semi.kind
|
||||
&& let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome)
|
||||
&& path_res(cx, inner_ret) == Res::Local(binding_id)
|
||||
&& let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
|
||||
// Checks if `pat` is a single reference to a binding (`&x`)
|
||||
let is_ref_to_binding =
|
||||
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
|
||||
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
|
||||
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
|
||||
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".map(|{}| {})",
|
||||
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
}
|
||||
let ty = cx.typeck_results().expr_ty(inner_ret);
|
||||
if cx
|
||||
.tcx
|
||||
.lang_items()
|
||||
.copy_trait()
|
||||
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
|
||||
{
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".find(|{}{}| {})",
|
||||
"&".repeat(1 + usize::from(is_ref_to_binding)),
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
if is_ref_to_binding {
|
||||
snippet.push_str(".copied()");
|
||||
}
|
||||
} else {
|
||||
applicability = Applicability::MaybeIncorrect;
|
||||
snippet.push_str(
|
||||
&format!(
|
||||
".find(|{}| {})",
|
||||
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
|
||||
)[..],
|
||||
);
|
||||
}
|
||||
// Extends to `last_stmt` to include semicolon in case of `return None;`
|
||||
let lint_span = span.to(last_stmt.span).to(last_ret.span);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_FIND,
|
||||
lint_span,
|
||||
"manual implementation of `Iterator::find`",
|
||||
|diag| {
|
||||
if applicability == Applicability::MaybeIncorrect {
|
||||
diag.note("you may need to dereference some variables");
|
||||
}
|
||||
diag.span_suggestion(lint_span, "replace with an iterator", snippet, applicability);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,34 +125,30 @@ fn last_stmt_and_ret<'tcx>(
|
||||
if let Some(ret) = block.expr {
|
||||
return Some((last_stmt, ret));
|
||||
}
|
||||
if_chain! {
|
||||
if let [.., snd_last, _] = block.stmts;
|
||||
if let StmtKind::Semi(last_expr) = last_stmt.kind;
|
||||
if let ExprKind::Ret(Some(ret)) = last_expr.kind;
|
||||
then {
|
||||
return Some((snd_last, ret));
|
||||
}
|
||||
if let [.., snd_last, _] = block.stmts
|
||||
&& let StmtKind::Semi(last_expr) = last_stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = last_expr.kind
|
||||
{
|
||||
return Some((snd_last, ret));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
|
||||
if_chain! {
|
||||
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next()
|
||||
// This should be the loop
|
||||
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
|
||||
// This should be the function body
|
||||
if let Some((_, Node::Block(block))) = parent_iter.next();
|
||||
if let Some((last_stmt, last_ret)) = extract(block);
|
||||
if last_stmt.hir_id == node_hir;
|
||||
if is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone);
|
||||
if let Some((_, Node::Expr(_block))) = parent_iter.next();
|
||||
&& let Some((_, Node::Block(block))) = parent_iter.next()
|
||||
&& let Some((last_stmt, last_ret)) = extract(block)
|
||||
&& last_stmt.hir_id == node_hir
|
||||
&& is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone)
|
||||
&& let Some((_, Node::Expr(_block))) = parent_iter.next()
|
||||
// This includes the function header
|
||||
if let Some((_, func)) = parent_iter.next();
|
||||
if func.fn_kind().is_some();
|
||||
then {
|
||||
Some((block.stmts.last().unwrap(), last_ret))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let Some((_, func)) = parent_iter.next()
|
||||
&& func.fn_kind().is_some()
|
||||
{
|
||||
Some((block.stmts.last().unwrap(), last_ret))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use super::MANUAL_FLATTEN;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Expr, Pat, PatKind};
|
||||
@ -21,66 +20,51 @@ pub(super) fn check<'tcx>(
|
||||
span: Span,
|
||||
) {
|
||||
let inner_expr = peel_blocks_with_stmt(body);
|
||||
if_chain! {
|
||||
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
|
||||
= higher::IfLet::hir(cx, inner_expr);
|
||||
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
|
||||
= higher::IfLet::hir(cx, inner_expr)
|
||||
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
|
||||
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
|
||||
if path_to_local_id(let_expr, pat_hir_id);
|
||||
&& let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind
|
||||
&& path_to_local_id(let_expr, pat_hir_id)
|
||||
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
|
||||
if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
|
||||
if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id);
|
||||
if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
|
||||
let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id);
|
||||
let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
|
||||
if some_ctor || ok_ctor;
|
||||
&& let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind
|
||||
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
|
||||
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
|
||||
&& let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id)
|
||||
&& let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id)
|
||||
&& (some_ctor || ok_ctor)
|
||||
// Ensure expr in `if let` is not used afterwards
|
||||
if !is_local_used(cx, if_then, pat_hir_id);
|
||||
then {
|
||||
let if_let_type = if some_ctor { "Some" } else { "Ok" };
|
||||
// Prepare the error message
|
||||
let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
|
||||
&& !is_local_used(cx, if_then, pat_hir_id)
|
||||
{
|
||||
let if_let_type = if some_ctor { "Some" } else { "Ok" };
|
||||
// Prepare the error message
|
||||
let msg =
|
||||
format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
|
||||
|
||||
// Prepare the help message
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
|
||||
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
|
||||
ty::Ref(_, inner, _) => match inner.kind() {
|
||||
ty::Ref(..) => ".copied()",
|
||||
_ => ""
|
||||
}
|
||||
_ => ""
|
||||
};
|
||||
// Prepare the help message
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
|
||||
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
|
||||
ty::Ref(_, inner, _) => match inner.kind() {
|
||||
ty::Ref(..) => ".copied()",
|
||||
_ => "",
|
||||
},
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let sugg = format!("{arg_snippet}{copied}.flatten()");
|
||||
let sugg = format!("{arg_snippet}{copied}.flatten()");
|
||||
|
||||
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
|
||||
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
|
||||
// to refer to the correct relative position of the suggestion.
|
||||
let help_msg = if sugg.contains('\n') {
|
||||
"remove the `if let` statement in the for loop and then..."
|
||||
} else {
|
||||
"...and remove the `if let` statement in the for loop"
|
||||
};
|
||||
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that
|
||||
// case, it will be shown in the extra `help` message at the end, which is why the first
|
||||
// `help_msg` needs to refer to the correct relative position of the suggestion.
|
||||
let help_msg = if sugg.contains('\n') {
|
||||
"remove the `if let` statement in the for loop and then..."
|
||||
} else {
|
||||
"...and remove the `if let` statement in the for loop"
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_FLATTEN,
|
||||
span,
|
||||
&msg,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
arg.span,
|
||||
"try",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
diag.span_help(
|
||||
inner_expr.span,
|
||||
help_msg,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
span_lint_and_then(cx, MANUAL_FLATTEN, span, &msg, |diag| {
|
||||
diag.span_suggestion(arg.span, "try", sugg, applicability);
|
||||
diag.span_help(inner_expr.span, help_msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::walk_block;
|
||||
@ -59,22 +58,31 @@ pub(super) fn check<'tcx>(
|
||||
.map(|o| {
|
||||
o.and_then(|(lhs, rhs)| {
|
||||
let rhs = fetch_cloned_expr(rhs);
|
||||
if_chain! {
|
||||
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind;
|
||||
if let ExprKind::Index(base_right, idx_right, _) = rhs.kind;
|
||||
if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left));
|
||||
if get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some();
|
||||
if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
|
||||
if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
|
||||
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
|
||||
&& let ExprKind::Index(base_right, idx_right, _) = rhs.kind
|
||||
&& let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left))
|
||||
&& get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some()
|
||||
&& let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts)
|
||||
&& let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts)
|
||||
|
||||
// Source and destination must be different
|
||||
if path_to_local(base_left) != path_to_local(base_right);
|
||||
then {
|
||||
Some((ty, IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left },
|
||||
IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& path_to_local(base_left) != path_to_local(base_right)
|
||||
{
|
||||
Some((
|
||||
ty,
|
||||
IndexExpr {
|
||||
base: base_left,
|
||||
idx: start_left,
|
||||
idx_offset: offset_left,
|
||||
},
|
||||
IndexExpr {
|
||||
base: base_right,
|
||||
idx: start_right,
|
||||
idx_offset: offset_right,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -118,23 +126,19 @@ fn build_manual_memcpy_suggestion<'tcx>(
|
||||
}
|
||||
|
||||
let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(method, recv, [], _) = end.kind;
|
||||
if method.ident.name == sym::len;
|
||||
if path_to_local(recv) == path_to_local(base);
|
||||
then {
|
||||
if sugg.to_string() == end_str {
|
||||
sugg::EMPTY.into()
|
||||
} else {
|
||||
sugg
|
||||
}
|
||||
if let ExprKind::MethodCall(method, recv, [], _) = end.kind
|
||||
&& method.ident.name == sym::len
|
||||
&& path_to_local(recv) == path_to_local(base)
|
||||
{
|
||||
if sugg.to_string() == end_str {
|
||||
sugg::EMPTY.into()
|
||||
} else {
|
||||
match limits {
|
||||
ast::RangeLimits::Closed => {
|
||||
sugg + &sugg::ONE.into()
|
||||
},
|
||||
ast::RangeLimits::HalfOpen => sugg,
|
||||
}
|
||||
sugg
|
||||
}
|
||||
} else {
|
||||
match limits {
|
||||
ast::RangeLimits::Closed => sugg + &sugg::ONE.into(),
|
||||
ast::RangeLimits::HalfOpen => sugg,
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -174,7 +178,9 @@ fn build_manual_memcpy_suggestion<'tcx>(
|
||||
let dst_base_str = snippet(cx, dst.base.span, "???");
|
||||
let src_base_str = snippet(cx, src.base.span, "???");
|
||||
|
||||
let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY {
|
||||
let dst = if (dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY)
|
||||
|| is_array_length_equal_to_range(cx, start, end, dst.base)
|
||||
{
|
||||
dst_base_str
|
||||
} else {
|
||||
format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into()
|
||||
@ -186,11 +192,13 @@ fn build_manual_memcpy_suggestion<'tcx>(
|
||||
"clone_from_slice"
|
||||
};
|
||||
|
||||
format!(
|
||||
"{dst}.{method_str}(&{src_base_str}[{}..{}]);",
|
||||
src_offset.maybe_par(),
|
||||
src_limit.maybe_par()
|
||||
)
|
||||
let src = if is_array_length_equal_to_range(cx, start, end, src.base) {
|
||||
src_base_str
|
||||
} else {
|
||||
format!("{src_base_str}[{}..{}]", src_offset.maybe_par(), src_limit.maybe_par()).into()
|
||||
};
|
||||
|
||||
format!("{dst}.{method_str}(&{src});")
|
||||
}
|
||||
|
||||
/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
|
||||
@ -331,10 +339,12 @@ fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Opti
|
||||
}
|
||||
|
||||
fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind;
|
||||
if method.ident.name == sym::clone;
|
||||
then { arg } else { expr }
|
||||
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind
|
||||
&& method.ident.name == sym::clone
|
||||
{
|
||||
arg
|
||||
} else {
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,3 +456,34 @@ fn get_loop_counters<'a, 'tcx>(
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_array_length_equal_to_range(cx: &LateContext<'_>, start: &Expr<'_>, end: &Expr<'_>, arr: &Expr<'_>) -> bool {
|
||||
fn extract_lit_value(expr: &Expr<'_>) -> Option<u128> {
|
||||
if let ExprKind::Lit(lit) = expr.kind
|
||||
&& let ast::LitKind::Int(value, _) = lit.node
|
||||
{
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let arr_ty = cx.typeck_results().expr_ty(arr).peel_refs();
|
||||
|
||||
if let ty::Array(_, s) = arr_ty.kind() {
|
||||
let size: u128 = if let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) {
|
||||
size.into()
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let range = match (extract_lit_value(start), extract_lit_value(end)) {
|
||||
(Some(start_value), Some(end_value)) => end_value - start_value,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
size == range
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -31,26 +31,30 @@ fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
|
||||
if let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind;
|
||||
if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
|
||||
if let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind();
|
||||
if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MISSING_SPIN_LOOP,
|
||||
body.span,
|
||||
"busy-waiting loop should at least have a spin loop hint",
|
||||
"try",
|
||||
(if is_no_std_crate(cx) {
|
||||
"{ core::hint::spin_loop() }"
|
||||
} else {
|
||||
"{ std::hint::spin_loop() }"
|
||||
}).into(),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
if let ExprKind::Block(
|
||||
Block {
|
||||
stmts: [], expr: None, ..
|
||||
},
|
||||
_,
|
||||
) = body.kind
|
||||
&& let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind
|
||||
&& [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name)
|
||||
&& let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind()
|
||||
&& cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did())
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MISSING_SPIN_LOOP,
|
||||
body.span,
|
||||
"busy-waiting loop should at least have a spin loop hint",
|
||||
"try",
|
||||
(if is_no_std_crate(cx) {
|
||||
"{ core::hint::spin_loop() }"
|
||||
} else {
|
||||
"{ std::hint::spin_loop() }"
|
||||
})
|
||||
.into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use super::MUT_RANGE_BOUND;
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::{get_enclosing_block, higher, path_to_local};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
|
||||
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
@ -12,19 +11,17 @@ use rustc_middle::ty;
|
||||
use rustc_span::Span;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::Range {
|
||||
start: Some(start),
|
||||
end: Some(end),
|
||||
..
|
||||
}) = higher::Range::hir(arg);
|
||||
let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
|
||||
if mut_id_start.is_some() || mut_id_end.is_some();
|
||||
then {
|
||||
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
|
||||
mut_warn_with_span(cx, span_low);
|
||||
mut_warn_with_span(cx, span_high);
|
||||
}
|
||||
if let Some(higher::Range {
|
||||
start: Some(start),
|
||||
end: Some(end),
|
||||
..
|
||||
}) = higher::Range::hir(arg)
|
||||
&& let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end))
|
||||
&& (mut_id_start.is_some() || mut_id_end.is_some())
|
||||
{
|
||||
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
|
||||
mut_warn_with_span(cx, span_low);
|
||||
mut_warn_with_span(cx, span_high);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,13 +39,11 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
|
||||
}
|
||||
|
||||
fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
|
||||
if_chain! {
|
||||
if let Some(hir_id) = path_to_local(bound);
|
||||
if let Node::Pat(pat) = cx.tcx.hir().get(hir_id);
|
||||
if let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind;
|
||||
then {
|
||||
return Some(hir_id);
|
||||
}
|
||||
if let Some(hir_id) = path_to_local(bound)
|
||||
&& let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
|
||||
&& let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind
|
||||
{
|
||||
return Some(hir_id);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::has_iter_method;
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
@ -187,15 +186,13 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
|
||||
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind;
|
||||
if method.ident.name == sym::len;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind;
|
||||
if path.segments.len() == 1;
|
||||
if path.segments[0].ident.name == var;
|
||||
then {
|
||||
return true;
|
||||
}
|
||||
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind
|
||||
&& method.ident.name == sym::len
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind
|
||||
&& path.segments.len() == 1
|
||||
&& path.segments[0].ident.name == var
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
@ -207,17 +204,15 @@ fn is_end_eq_array_len<'tcx>(
|
||||
limits: ast::RangeLimits,
|
||||
indexed_ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Lit(lit) = end.kind;
|
||||
if let ast::LitKind::Int(end_int, _) = lit.node;
|
||||
if let ty::Array(_, arr_len_const) = indexed_ty.kind();
|
||||
if let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env);
|
||||
then {
|
||||
return match limits {
|
||||
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
|
||||
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
|
||||
};
|
||||
}
|
||||
if let ExprKind::Lit(lit) = end.kind
|
||||
&& let ast::LitKind::Int(end_int, _) = lit.node
|
||||
&& let ty::Array(_, arr_len_const) = indexed_ty.kind()
|
||||
&& let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env)
|
||||
{
|
||||
return match limits {
|
||||
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
|
||||
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
|
||||
};
|
||||
}
|
||||
|
||||
false
|
||||
@ -248,51 +243,49 @@ struct VarVisitor<'a, 'tcx> {
|
||||
|
||||
impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
|
||||
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(ref seqpath) = seqexpr.kind
|
||||
// the indexed container is referenced by a name
|
||||
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
|
||||
if let QPath::Resolved(None, seqvar) = *seqpath;
|
||||
if seqvar.segments.len() == 1;
|
||||
if is_local_used(self.cx, idx, self.var);
|
||||
then {
|
||||
if self.prefer_mutable {
|
||||
self.indexed_mut.insert(seqvar.segments[0].ident.name);
|
||||
}
|
||||
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
|
||||
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
|
||||
match res {
|
||||
Res::Local(hir_id) => {
|
||||
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
|
||||
let extent = self
|
||||
.cx
|
||||
.tcx
|
||||
.region_scope_tree(parent_def_id)
|
||||
.var_scope(hir_id.local_id)
|
||||
.unwrap();
|
||||
if index_used_directly {
|
||||
self.indexed_directly.insert(
|
||||
seqvar.segments[0].ident.name,
|
||||
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
|
||||
);
|
||||
} else {
|
||||
self.indexed_indirectly
|
||||
.insert(seqvar.segments[0].ident.name, Some(extent));
|
||||
}
|
||||
return false; // no need to walk further *on the variable*
|
||||
},
|
||||
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
|
||||
if index_used_directly {
|
||||
self.indexed_directly.insert(
|
||||
seqvar.segments[0].ident.name,
|
||||
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
|
||||
);
|
||||
} else {
|
||||
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
|
||||
}
|
||||
return false; // no need to walk further *on the variable*
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
&& let QPath::Resolved(None, seqvar) = *seqpath
|
||||
&& seqvar.segments.len() == 1
|
||||
&& is_local_used(self.cx, idx, self.var)
|
||||
{
|
||||
if self.prefer_mutable {
|
||||
self.indexed_mut.insert(seqvar.segments[0].ident.name);
|
||||
}
|
||||
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
|
||||
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
|
||||
match res {
|
||||
Res::Local(hir_id) => {
|
||||
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
|
||||
let extent = self
|
||||
.cx
|
||||
.tcx
|
||||
.region_scope_tree(parent_def_id)
|
||||
.var_scope(hir_id.local_id)
|
||||
.unwrap();
|
||||
if index_used_directly {
|
||||
self.indexed_directly.insert(
|
||||
seqvar.segments[0].ident.name,
|
||||
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
|
||||
);
|
||||
} else {
|
||||
self.indexed_indirectly
|
||||
.insert(seqvar.segments[0].ident.name, Some(extent));
|
||||
}
|
||||
return false; // no need to walk further *on the variable*
|
||||
},
|
||||
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
|
||||
if index_used_directly {
|
||||
self.indexed_directly.insert(
|
||||
seqvar.segments[0].ident.name,
|
||||
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
|
||||
);
|
||||
} else {
|
||||
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
|
||||
}
|
||||
return false; // no need to walk further *on the variable*
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
true
|
||||
@ -301,42 +294,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind
|
||||
// a range index op
|
||||
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
|
||||
if let Some(trait_id) = self
|
||||
&& let Some(trait_id) = self
|
||||
.cx
|
||||
.typeck_results()
|
||||
.type_dependent_def_id(expr.hir_id)
|
||||
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id));
|
||||
if (meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
|
||||
|| (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id));
|
||||
if !self.check(args_1, args_0, expr);
|
||||
then {
|
||||
return;
|
||||
}
|
||||
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id))
|
||||
&& ((meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
|
||||
|| (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id)))
|
||||
&& !self.check(args_1, args_0, expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Index(seqexpr, idx, _) = expr.kind
|
||||
// an index op
|
||||
if let ExprKind::Index(seqexpr, idx, _) = expr.kind;
|
||||
if !self.check(idx, seqexpr, expr);
|
||||
then {
|
||||
return;
|
||||
}
|
||||
&& !self.check(idx, seqexpr, expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
|
||||
// directly using a variable
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
|
||||
if let Res::Local(local_id) = path.res;
|
||||
then {
|
||||
if local_id == self.var {
|
||||
self.nonindex = true;
|
||||
} else {
|
||||
// not the correct variable, but still a variable
|
||||
self.referenced.insert(path.segments[0].ident.name);
|
||||
}
|
||||
&& let Res::Local(local_id) = path.res
|
||||
{
|
||||
if local_id == self.var {
|
||||
self.nonindex = true;
|
||||
} else {
|
||||
// not the correct variable, but still a variable
|
||||
self.referenced.insert(path.segments[0].ident.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::path_to_local;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
@ -44,54 +43,50 @@ pub(super) fn check<'tcx>(
|
||||
// Determine whether it is safe to lint the body
|
||||
let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
|
||||
walk_expr(&mut same_item_push_visitor, body);
|
||||
if_chain! {
|
||||
if same_item_push_visitor.should_lint();
|
||||
if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push;
|
||||
let vec_ty = cx.typeck_results().expr_ty(vec);
|
||||
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
|
||||
if cx
|
||||
if same_item_push_visitor.should_lint()
|
||||
&& let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push
|
||||
&& let vec_ty = cx.typeck_results().expr_ty(vec)
|
||||
&& let ty = vec_ty.walk().nth(1).unwrap().expect_ty()
|
||||
&& cx
|
||||
.tcx
|
||||
.lang_items()
|
||||
.clone_trait()
|
||||
.map_or(false, |id| implements_trait(cx, ty, id, &[]));
|
||||
then {
|
||||
// Make sure that the push does not involve possibly mutating values
|
||||
match pushed_item.kind {
|
||||
ExprKind::Path(ref qpath) => {
|
||||
match cx.qpath_res(qpath, pushed_item.hir_id) {
|
||||
// immutable bindings that are initialized with literal or constant
|
||||
Res::Local(hir_id) => {
|
||||
let node = cx.tcx.hir().get(hir_id);
|
||||
if_chain! {
|
||||
if let Node::Pat(pat) = node;
|
||||
if let PatKind::Binding(bind_ann, ..) = pat.kind;
|
||||
if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut));
|
||||
let parent_node = cx.tcx.hir().parent_id(hir_id);
|
||||
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
|
||||
if let Some(init) = parent_let_expr.init;
|
||||
then {
|
||||
match init.kind {
|
||||
// immutable bindings that are initialized with literal
|
||||
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
// immutable bindings that are initialized with constant
|
||||
ExprKind::Path(ref path) => {
|
||||
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
|
||||
emit_lint(cx, vec, pushed_item, ctxt);
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
|
||||
{
|
||||
// Make sure that the push does not involve possibly mutating values
|
||||
match pushed_item.kind {
|
||||
ExprKind::Path(ref qpath) => {
|
||||
match cx.qpath_res(qpath, pushed_item.hir_id) {
|
||||
// immutable bindings that are initialized with literal or constant
|
||||
Res::Local(hir_id) => {
|
||||
let node = cx.tcx.hir().get(hir_id);
|
||||
if let Node::Pat(pat) = node
|
||||
&& let PatKind::Binding(bind_ann, ..) = pat.kind
|
||||
&& !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut))
|
||||
&& let parent_node = cx.tcx.hir().parent_id(hir_id)
|
||||
&& let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node)
|
||||
&& let Some(init) = parent_let_expr.init
|
||||
{
|
||||
match init.kind {
|
||||
// immutable bindings that are initialized with literal
|
||||
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
// immutable bindings that are initialized with constant
|
||||
ExprKind::Path(ref path) => {
|
||||
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
|
||||
emit_lint(cx, vec, pushed_item, ctxt);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
// constant
|
||||
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
// constant
|
||||
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,16 +113,14 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
fn should_lint(&self) -> bool {
|
||||
if_chain! {
|
||||
if !self.non_deterministic_expr;
|
||||
if !self.multiple_pushes;
|
||||
if let Some((vec, _, _)) = self.vec_push;
|
||||
if let Some(hir_id) = path_to_local(vec);
|
||||
then {
|
||||
!self.used_locals.contains(&hir_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if !self.non_deterministic_expr
|
||||
&& !self.multiple_pushes
|
||||
&& let Some((vec, _, _)) = self.vec_push
|
||||
&& let Some(hir_id) = path_to_local(vec)
|
||||
{
|
||||
!self.used_locals.contains(&hir_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,18 +173,16 @@ fn get_vec_push<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
stmt: &'tcx Stmt<'_>,
|
||||
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
|
||||
if_chain! {
|
||||
if let StmtKind::Semi(semi_stmt) = &stmt.kind
|
||||
// Extract method being called
|
||||
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
|
||||
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
|
||||
&& let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind
|
||||
// Figure out the parameters for the method call
|
||||
if let Some(pushed_item) = args.first();
|
||||
&& let Some(pushed_item) = args.first()
|
||||
// Check that the method being called is push() on a Vec
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
|
||||
if path.ident.name.as_str() == "push";
|
||||
then {
|
||||
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()))
|
||||
}
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec)
|
||||
&& path.ident.name.as_str() == "push"
|
||||
{
|
||||
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use super::SINGLE_ELEMENT_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{indent_of, snippet_with_applicability};
|
||||
use clippy_utils::visitors::contains_break_or_continue;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::util::parser::PREC_PREFIX;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_errors::Applicability;
|
||||
@ -66,36 +65,36 @@ pub(super) fn check<'tcx>(
|
||||
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
|
||||
_ => return,
|
||||
};
|
||||
if_chain! {
|
||||
if let ExprKind::Block(block, _) = body.kind;
|
||||
if !block.stmts.is_empty();
|
||||
if !contains_break_or_continue(body);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
|
||||
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
|
||||
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
|
||||
block_str.remove(0);
|
||||
block_str.pop();
|
||||
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
|
||||
if let ExprKind::Block(block, _) = body.kind
|
||||
&& !block.stmts.is_empty()
|
||||
&& !contains_break_or_continue(body)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
|
||||
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
|
||||
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
|
||||
block_str.remove(0);
|
||||
block_str.pop();
|
||||
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
|
||||
|
||||
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
|
||||
if !prefix.is_empty() && (
|
||||
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
|
||||
if !prefix.is_empty()
|
||||
&& (
|
||||
// Precedence of internal expression is less than or equal to precedence of `&expr`.
|
||||
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
|
||||
) {
|
||||
arg_snip = format!("({arg_snip})").into();
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_ELEMENT_LOOP,
|
||||
expr.span,
|
||||
"for loop over a single element",
|
||||
"try",
|
||||
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
|
||||
applicability,
|
||||
)
|
||||
{
|
||||
arg_snip = format!("({arg_snip})").into();
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_ELEMENT_LOOP,
|
||||
expr.span,
|
||||
"for loop over a single element",
|
||||
"try",
|
||||
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use rustc_middle::ty;
|
||||
|
||||
/// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
|
||||
let PatKind::Tuple(tuple, _) = pat.kind else {
|
||||
let PatKind::Tuple([index, elem], _) = pat.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
|
||||
|
||||
let ty = cx.typeck_results().expr_ty(arg);
|
||||
|
||||
if !pat_is_wild(cx, &tuple[0].kind, body) {
|
||||
if !pat_is_wild(cx, &index.kind, body) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
|
||||
diag,
|
||||
"remove the `.enumerate()` call",
|
||||
vec![
|
||||
(pat.span, snippet(cx, tuple[1].span, "..").into_owned()),
|
||||
(pat.span, snippet(cx, elem.span, "..").into_owned()),
|
||||
(arg.span, base_iter.to_string()),
|
||||
],
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::ty::{has_iter_method, implements_trait};
|
||||
use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
|
||||
@ -145,20 +144,18 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx Local<'_>) {
|
||||
// Look for declarations of the variable
|
||||
if_chain! {
|
||||
if l.pat.hir_id == self.var_id;
|
||||
if let PatKind::Binding(.., ident, _) = l.pat.kind;
|
||||
then {
|
||||
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
|
||||
if l.pat.hir_id == self.var_id
|
||||
&& let PatKind::Binding(.., ident, _) = l.pat.kind
|
||||
{
|
||||
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
|
||||
|
||||
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
|
||||
InitializeVisitorState::Initialized {
|
||||
initializer: init,
|
||||
ty,
|
||||
name: ident.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
|
||||
InitializeVisitorState::Initialized {
|
||||
initializer: init,
|
||||
ty,
|
||||
name: ident.name,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
walk_local(self, l);
|
||||
|
@ -2,7 +2,6 @@ use super::WHILE_IMMUTABLE_CONDITION;
|
||||
use clippy_utils::consts::constant;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
@ -95,20 +94,18 @@ struct VarCollectorVisitor<'a, 'tcx> {
|
||||
|
||||
impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
|
||||
fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(ref qpath) = ex.kind;
|
||||
if let QPath::Resolved(None, _) = *qpath;
|
||||
then {
|
||||
match self.cx.qpath_res(qpath, ex.hir_id) {
|
||||
Res::Local(hir_id) => {
|
||||
self.ids.insert(hir_id);
|
||||
},
|
||||
Res::Def(DefKind::Static(_), def_id) => {
|
||||
let mutable = self.cx.tcx.is_mutable_static(def_id);
|
||||
self.def_ids.insert(def_id, mutable);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
if let ExprKind::Path(ref qpath) = ex.kind
|
||||
&& let QPath::Resolved(None, _) = *qpath
|
||||
{
|
||||
match self.cx.qpath_res(qpath, ex.hir_id) {
|
||||
Res::Local(hir_id) => {
|
||||
self.ids.insert(hir_id);
|
||||
},
|
||||
Res::Def(DefKind::Static(_), def_id) => {
|
||||
let mutable = self.cx.tcx.is_mutable_static(def_id);
|
||||
self.def_ids.insert(def_id, mutable);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::visitors::is_res_used;
|
||||
use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable, is_res_lang_ctor, is_trait_method};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
@ -15,59 +14,53 @@ use rustc_span::symbol::sym;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
|
||||
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
|
||||
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr)
|
||||
// check for `Some(..)` pattern
|
||||
if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind;
|
||||
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
|
||||
&& let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
|
||||
// check for call to `Iterator::next`
|
||||
if let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind;
|
||||
if method_name.ident.name == sym::next;
|
||||
if is_trait_method(cx, let_expr, sym::Iterator);
|
||||
if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr);
|
||||
&& let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind
|
||||
&& method_name.ident.name == sym::next
|
||||
&& is_trait_method(cx, let_expr, sym::Iterator)
|
||||
&& let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr)
|
||||
// get the loop containing the match expression
|
||||
if !uses_iter(cx, &iter_expr_struct, if_then);
|
||||
then {
|
||||
(let_expr, iter_expr_struct, iter_expr, some_pat, expr)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let loop_var = if let Some(some_pat) = some_pat.first() {
|
||||
if is_refutable(cx, some_pat) {
|
||||
// Refutable patterns don't work with for loops.
|
||||
return;
|
||||
}
|
||||
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
|
||||
} else {
|
||||
"_".into()
|
||||
};
|
||||
|
||||
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
|
||||
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
|
||||
// afterwards a mutable borrow of a field isn't necessary.
|
||||
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|
||||
|| !iter_expr_struct.can_move
|
||||
|| !iter_expr_struct.fields.is_empty()
|
||||
|| needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
|
||||
&& !uses_iter(cx, &iter_expr_struct, if_then)
|
||||
{
|
||||
".by_ref()"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let loop_var = if let Some(some_pat) = some_pat.first() {
|
||||
if is_refutable(cx, some_pat) {
|
||||
// Refutable patterns don't work with for loops.
|
||||
return;
|
||||
}
|
||||
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
|
||||
} else {
|
||||
"_".into()
|
||||
};
|
||||
|
||||
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
WHILE_LET_ON_ITERATOR,
|
||||
expr.span.with_hi(scrutinee_expr.span.hi()),
|
||||
"this loop could be written as a `for` loop",
|
||||
"try",
|
||||
format!("for {loop_var} in {iterator}{by_ref}"),
|
||||
applicability,
|
||||
);
|
||||
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
|
||||
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
|
||||
// afterwards a mutable borrow of a field isn't necessary.
|
||||
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|
||||
|| !iter_expr_struct.can_move
|
||||
|| !iter_expr_struct.fields.is_empty()
|
||||
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
|
||||
{
|
||||
".by_ref()"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
WHILE_LET_ON_ITERATOR,
|
||||
expr.span.with_hi(let_expr.span.hi()),
|
||||
"this loop could be written as a `for` loop",
|
||||
"try",
|
||||
format!("for {loop_var} in {iterator}{by_ref}"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use hir::def::{DefKind, Res};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_errors::Applicability;
|
||||
@ -89,30 +88,26 @@ impl MacroUseImports {
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
|
||||
if_chain! {
|
||||
if cx.sess().opts.edition >= Edition::Edition2018;
|
||||
if let hir::ItemKind::Use(path, _kind) = &item.kind;
|
||||
let hir_id = item.hir_id();
|
||||
let attrs = cx.tcx.hir().attrs(hir_id);
|
||||
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
|
||||
if let Some(id) = path.res.iter().find_map(|res| match res {
|
||||
if cx.sess().opts.edition >= Edition::Edition2018
|
||||
&& let hir::ItemKind::Use(path, _kind) = &item.kind
|
||||
&& let hir_id = item.hir_id()
|
||||
&& let attrs = cx.tcx.hir().attrs(hir_id)
|
||||
&& let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use))
|
||||
&& let Some(id) = path.res.iter().find_map(|res| match res {
|
||||
Res::Def(DefKind::Mod, id) => Some(id),
|
||||
_ => None,
|
||||
});
|
||||
if !id.is_local();
|
||||
then {
|
||||
for kid in cx.tcx.module_children(id) {
|
||||
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
|
||||
let span = mac_attr.span;
|
||||
let def_path = cx.tcx.def_path_str(mac_id);
|
||||
self.imports.push((def_path, span, hir_id));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if item.span.from_expansion() {
|
||||
self.push_unique_macro_pat_ty(cx, item.span);
|
||||
})
|
||||
&& !id.is_local()
|
||||
{
|
||||
for kid in cx.tcx.module_children(id) {
|
||||
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
|
||||
let span = mac_attr.span;
|
||||
let def_path = cx.tcx.def_path_str(mac_id);
|
||||
self.imports.push((def_path, span, hir_id));
|
||||
}
|
||||
}
|
||||
} else if item.span.from_expansion() {
|
||||
self.push_unique_macro_pat_ty(cx, item.span);
|
||||
}
|
||||
}
|
||||
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
@ -43,21 +42,19 @@ impl LateLintPass<'_> for MainRecursion {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Call(func, _) = &expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind;
|
||||
if let Some(def_id) = path.res.opt_def_id();
|
||||
if is_entrypoint_fn(cx, def_id);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
MAIN_RECURSION,
|
||||
func.span,
|
||||
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
|
||||
None,
|
||||
"consider using another function for this recursion"
|
||||
)
|
||||
}
|
||||
if let ExprKind::Call(func, _) = &expr.kind
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
&& is_entrypoint_fn(cx, def_id)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
MAIN_RECURSION,
|
||||
func.span,
|
||||
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
|
||||
None,
|
||||
"consider using another function for this recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
@ -47,61 +46,57 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
|
||||
span: Span,
|
||||
def_id: LocalDefId,
|
||||
) {
|
||||
if_chain! {
|
||||
if let Some(header) = kind.header();
|
||||
if !header.asyncness.is_async();
|
||||
if let Some(header) = kind.header()
|
||||
&& !header.asyncness.is_async()
|
||||
// Check that this function returns `impl Future`
|
||||
if let FnRetTy::Return(ret_ty) = decl.output;
|
||||
if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty);
|
||||
if let Some(output) = future_output_ty(trait_ref);
|
||||
if captures_all_lifetimes(decl.inputs, &output_lifetimes);
|
||||
&& let FnRetTy::Return(ret_ty) = decl.output
|
||||
&& let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty)
|
||||
&& let Some(output) = future_output_ty(trait_ref)
|
||||
&& captures_all_lifetimes(decl.inputs, &output_lifetimes)
|
||||
// Check that the body of the function consists of one async block
|
||||
if let ExprKind::Block(block, _) = body.value.kind;
|
||||
if block.stmts.is_empty();
|
||||
if let Some(closure_body) = desugared_async_block(cx, block);
|
||||
if let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
|
||||
cx.tcx.hir().get_by_def_id(def_id);
|
||||
then {
|
||||
let header_span = span.with_hi(ret_ty.span.hi());
|
||||
&& let ExprKind::Block(block, _) = body.value.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& let Some(closure_body) = desugared_async_block(cx, block)
|
||||
&& let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
|
||||
cx.tcx.hir().get_by_def_id(def_id)
|
||||
{
|
||||
let header_span = span.with_hi(ret_ty.span.hi());
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_ASYNC_FN,
|
||||
header_span,
|
||||
"this function can be simplified using the `async fn` syntax",
|
||||
|diag| {
|
||||
if_chain! {
|
||||
if let Some(vis_snip) = snippet_opt(cx, *vis_span);
|
||||
if let Some(header_snip) = snippet_opt(cx, header_span);
|
||||
if let Some(ret_pos) = position_before_rarrow(&header_snip);
|
||||
if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
|
||||
then {
|
||||
let header_snip = if vis_snip.is_empty() {
|
||||
format!("async {}", &header_snip[..ret_pos])
|
||||
} else {
|
||||
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
|
||||
};
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_ASYNC_FN,
|
||||
header_span,
|
||||
"this function can be simplified using the `async fn` syntax",
|
||||
|diag| {
|
||||
if let Some(vis_snip) = snippet_opt(cx, *vis_span)
|
||||
&& let Some(header_snip) = snippet_opt(cx, header_span)
|
||||
&& let Some(ret_pos) = position_before_rarrow(&header_snip)
|
||||
&& let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
|
||||
{
|
||||
let header_snip = if vis_snip.is_empty() {
|
||||
format!("async {}", &header_snip[..ret_pos])
|
||||
} else {
|
||||
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
|
||||
};
|
||||
|
||||
let help = format!("make the function `async` and {ret_sugg}");
|
||||
diag.span_suggestion(
|
||||
header_span,
|
||||
help,
|
||||
format!("{header_snip}{ret_snip}"),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
let help = format!("make the function `async` and {ret_sugg}");
|
||||
diag.span_suggestion(
|
||||
header_span,
|
||||
help,
|
||||
format!("{header_snip}{ret_snip}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
|
||||
diag.span_suggestion(
|
||||
block.span,
|
||||
"move the body of the async block to the enclosing function",
|
||||
body_snip,
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
|
||||
diag.span_suggestion(
|
||||
block.span,
|
||||
"move the body of the async block to the enclosing function",
|
||||
body_snip,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,48 +105,44 @@ fn future_trait_ref<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ty: &'tcx Ty<'tcx>,
|
||||
) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
|
||||
if_chain! {
|
||||
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind;
|
||||
let item = cx.tcx.hir().item(item_id);
|
||||
if let ItemKind::OpaqueTy(opaque) = &item.kind;
|
||||
if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
|
||||
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind
|
||||
&& let item = cx.tcx.hir().item(item_id)
|
||||
&& let ItemKind::OpaqueTy(opaque) = &item.kind
|
||||
&& let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
|
||||
if let GenericBound::Trait(poly, _) = bound {
|
||||
Some(&poly.trait_ref)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
|
||||
then {
|
||||
let output_lifetimes = bounds
|
||||
.iter()
|
||||
.filter_map(|bound| {
|
||||
if let GenericArg::Lifetime(lt) = bound {
|
||||
Some(lt.res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
})
|
||||
&& trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
|
||||
{
|
||||
let output_lifetimes = bounds
|
||||
.iter()
|
||||
.filter_map(|bound| {
|
||||
if let GenericArg::Lifetime(lt) = bound {
|
||||
Some(lt.res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Some((trait_ref, output_lifetimes));
|
||||
}
|
||||
return Some((trait_ref, output_lifetimes));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
|
||||
if_chain! {
|
||||
if let Some(segment) = trait_ref.path.segments.last();
|
||||
if let Some(args) = segment.args;
|
||||
if args.bindings.len() == 1;
|
||||
let binding = &args.bindings[0];
|
||||
if binding.ident.name == sym::Output;
|
||||
if let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind;
|
||||
then {
|
||||
return Some(output);
|
||||
}
|
||||
if let Some(segment) = trait_ref.path.segments.last()
|
||||
&& let Some(args) = segment.args
|
||||
&& args.bindings.len() == 1
|
||||
&& let binding = &args.bindings[0]
|
||||
&& binding.ident.name == sym::Output
|
||||
&& let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind
|
||||
{
|
||||
return Some(output);
|
||||
}
|
||||
|
||||
None
|
||||
@ -181,17 +172,15 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName])
|
||||
}
|
||||
|
||||
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
|
||||
if_chain! {
|
||||
if let Some(block_expr) = block.expr;
|
||||
if let Expr {
|
||||
if let Some(block_expr) = block.expr
|
||||
&& let Expr {
|
||||
kind: ExprKind::Closure(&Closure { body, .. }),
|
||||
..
|
||||
} = block_expr;
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
if closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block));
|
||||
then {
|
||||
return Some(closure_body);
|
||||
}
|
||||
} = block_expr
|
||||
&& let closure_body = cx.tcx.hir().body(body)
|
||||
&& closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block))
|
||||
{
|
||||
return Some(closure_body);
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -53,32 +53,30 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind;
|
||||
if let BinOpKind::Mul = &bin_op.node;
|
||||
if !in_external_macro(cx.sess(), expr.span);
|
||||
let ctxt = expr.span.ctxt();
|
||||
if left_expr.span.ctxt() == ctxt;
|
||||
if right_expr.span.ctxt() == ctxt;
|
||||
if let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr);
|
||||
if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_));
|
||||
if let ExprKind::Lit(lit) = &other_expr.kind;
|
||||
if let LitKind::Int(8, _) = lit.node;
|
||||
then {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
|
||||
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
|
||||
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
|
||||
&& let BinOpKind::Mul = &bin_op.node
|
||||
&& !in_external_macro(cx.sess(), expr.span)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& left_expr.span.ctxt() == ctxt
|
||||
&& right_expr.span.ctxt() == ctxt
|
||||
&& let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr)
|
||||
&& matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_))
|
||||
&& let ExprKind::Lit(lit) = &other_expr.kind
|
||||
&& let LitKind::Int(8, _) = lit.node
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
|
||||
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_BITS,
|
||||
expr.span,
|
||||
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
|
||||
"consider using",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_BITS,
|
||||
expr.span,
|
||||
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
|
||||
"consider using",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,22 +96,22 @@ fn get_one_size_of_ty<'tcx>(
|
||||
}
|
||||
|
||||
fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>)> {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(count_func, _func_args) = expr.kind;
|
||||
if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
|
||||
|
||||
if let QPath::Resolved(_, count_func_path) = count_func_qpath;
|
||||
if let Some(segment_zero) = count_func_path.segments.first();
|
||||
if let Some(args) = segment_zero.args;
|
||||
if let Some(GenericArg::Type(real_ty)) = args.args.first();
|
||||
|
||||
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);
|
||||
then {
|
||||
cx.typeck_results().node_args(count_func.hir_id).types().next().map(|resolved_ty| (*real_ty, resolved_ty))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let ExprKind::Call(count_func, _func_args) = expr.kind
|
||||
&& let ExprKind::Path(ref count_func_qpath) = count_func.kind
|
||||
&& let QPath::Resolved(_, count_func_path) = count_func_qpath
|
||||
&& let Some(segment_zero) = count_func_path.segments.first()
|
||||
&& let Some(args) = segment_zero.args
|
||||
&& let Some(GenericArg::Type(real_ty)) = args.args.first()
|
||||
&& let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
|
||||
{
|
||||
cx.typeck_results()
|
||||
.node_args(count_func.hir_id)
|
||||
.types()
|
||||
.next()
|
||||
.map(|resolved_ty| (*real_ty, resolved_ty))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,15 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher::IfLetOrMatch;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::{Descend, Visitable};
|
||||
use clippy_utils::{is_lint_allowed, pat_and_expr_can_be_question_mark, peel_blocks};
|
||||
use clippy_utils::{is_lint_allowed, is_never_expr, pat_and_expr_can_be_question_mark, peel_blocks};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty};
|
||||
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::declare_tool_lint;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_span::Span;
|
||||
use std::ops::ControlFlow;
|
||||
use std::slice;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -51,7 +48,7 @@ declare_clippy_lint! {
|
||||
}
|
||||
|
||||
impl<'tcx> QuestionMark {
|
||||
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
|
||||
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
|
||||
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
|
||||
return;
|
||||
}
|
||||
@ -67,7 +64,7 @@ impl<'tcx> QuestionMark {
|
||||
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => {
|
||||
if let Some(ident_map) = expr_simple_identity_map(local.pat, let_pat, if_then)
|
||||
&& let Some(if_else) = if_else
|
||||
&& expr_diverges(cx, if_else)
|
||||
&& is_never_expr(cx, if_else).is_some()
|
||||
&& let qm_allowed = is_lint_allowed(cx, QUESTION_MARK, stmt.hir_id)
|
||||
&& (qm_allowed || pat_and_expr_can_be_question_mark(cx, let_pat, if_else).is_none())
|
||||
{
|
||||
@ -91,10 +88,9 @@ impl<'tcx> QuestionMark {
|
||||
return;
|
||||
}
|
||||
let check_types = self.matches_behaviour == MatchLintBehaviour::WellKnownTypes;
|
||||
let diverging_arm_opt = arms
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types));
|
||||
let diverging_arm_opt = arms.iter().enumerate().find(|(_, arm)| {
|
||||
is_never_expr(cx, arm.body).is_some() && pat_allowed_for_else(cx, arm.pat, check_types)
|
||||
});
|
||||
let Some((idx, diverging_arm)) = diverging_arm_opt else {
|
||||
return;
|
||||
};
|
||||
@ -272,104 +268,6 @@ fn replace_in_pattern(
|
||||
sn_pat.into_owned()
|
||||
}
|
||||
|
||||
/// Check whether an expression is divergent. May give false negatives.
|
||||
fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
struct V<'cx, 'tcx> {
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
res: ControlFlow<(), Descend>,
|
||||
}
|
||||
impl<'tcx> Visitor<'tcx> for V<'_, '_> {
|
||||
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
|
||||
fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
|
||||
if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) {
|
||||
return ty.is_never();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
if self.res.is_break() {
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't just call is_never on expr and be done, because the type system
|
||||
// sometimes coerces the ! type to something different before we can get
|
||||
// our hands on it. So instead, we do a manual search. We do fall back to
|
||||
// is_never in some places when there is no better alternative.
|
||||
self.res = match e.kind {
|
||||
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()),
|
||||
ExprKind::Call(call, _) => {
|
||||
if is_never(self.cx, e) || is_never(self.cx, call) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(..) => {
|
||||
if is_never(self.cx, e) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
},
|
||||
ExprKind::If(if_expr, if_then, if_else) => {
|
||||
let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex));
|
||||
let diverges =
|
||||
expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then));
|
||||
if diverges {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
},
|
||||
ExprKind::Match(match_expr, match_arms, _) => {
|
||||
let diverges = expr_diverges(self.cx, match_expr)
|
||||
|| match_arms.iter().all(|arm| {
|
||||
let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body()));
|
||||
guard_diverges || expr_diverges(self.cx, arm.body)
|
||||
});
|
||||
if diverges {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
},
|
||||
|
||||
// Don't continue into loops or labeled blocks, as they are breakable,
|
||||
// and we'd have to start checking labels.
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
|
||||
|
||||
// Default: descend
|
||||
_ => ControlFlow::Continue(Descend::Yes),
|
||||
};
|
||||
if let ControlFlow::Continue(Descend::Yes) = self.res {
|
||||
walk_expr(self, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, local: &'tcx Local<'_>) {
|
||||
// Don't visit the else block of a let/else statement as it will not make
|
||||
// the statement divergent even though the else block is divergent.
|
||||
if let Some(init) = local.init {
|
||||
self.visit_expr(init);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid unnecessary `walk_*` calls.
|
||||
fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {}
|
||||
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
|
||||
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
|
||||
// Avoid monomorphising all `visit_*` functions.
|
||||
fn visit_nested_item(&mut self, _: ItemId) {}
|
||||
}
|
||||
|
||||
let mut v = V {
|
||||
cx,
|
||||
res: ControlFlow::Continue(Descend::Yes),
|
||||
};
|
||||
expr.visit(&mut v);
|
||||
v.res.is_break()
|
||||
}
|
||||
|
||||
fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool {
|
||||
// Check whether the pattern contains any bindings, as the
|
||||
// binding might potentially be used in the body.
|
||||
|
@ -4,7 +4,6 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
@ -71,55 +70,61 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
|
||||
return;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
|
||||
if let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind;
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
|
||||
if let ExprKind::Path(target_path) = &target_arg.kind;
|
||||
then {
|
||||
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
|
||||
StripKind::Prefix
|
||||
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
|
||||
StripKind::Suffix
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
|
||||
if target_res == Res::Err {
|
||||
return;
|
||||
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
|
||||
&& let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
|
||||
&& let ExprKind::Path(target_path) = &target_arg.kind
|
||||
{
|
||||
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
|
||||
StripKind::Prefix
|
||||
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
|
||||
StripKind::Suffix
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
|
||||
if target_res == Res::Err {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Res::Local(hir_id) = target_res
|
||||
&& let Some(used_mutably) = mutated_variables(then, cx)
|
||||
&& used_mutably.contains(&hir_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
|
||||
if !strippings.is_empty() {
|
||||
let kind_word = match strip_kind {
|
||||
StripKind::Prefix => "prefix",
|
||||
StripKind::Suffix => "suffix",
|
||||
};
|
||||
|
||||
if_chain! {
|
||||
if let Res::Local(hir_id) = target_res;
|
||||
if let Some(used_mutably) = mutated_variables(then, cx);
|
||||
if used_mutably.contains(&hir_id);
|
||||
then {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
|
||||
if !strippings.is_empty() {
|
||||
|
||||
let kind_word = match strip_kind {
|
||||
StripKind::Prefix => "prefix",
|
||||
StripKind::Suffix => "suffix",
|
||||
};
|
||||
|
||||
let test_span = expr.span.until(then.span);
|
||||
span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
|
||||
let test_span = expr.span.until(then.span);
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_STRIP,
|
||||
strippings[0],
|
||||
&format!("stripping a {kind_word} manually"),
|
||||
|diag| {
|
||||
diag.span_note(test_span, format!("the {kind_word} was tested here"));
|
||||
multispan_sugg(
|
||||
diag,
|
||||
&format!("try using the `strip_{kind_word}` method"),
|
||||
vec![(test_span,
|
||||
format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
|
||||
snippet(cx, target_arg.span, ".."),
|
||||
snippet(cx, pattern.span, "..")))]
|
||||
.into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
|
||||
vec![(
|
||||
test_span,
|
||||
format!(
|
||||
"if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
|
||||
snippet(cx, target_arg.span, ".."),
|
||||
snippet(cx, pattern.span, "..")
|
||||
),
|
||||
)]
|
||||
.into_iter()
|
||||
.chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,15 +134,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
|
||||
|
||||
// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
|
||||
fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind;
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if match_def_path(cx, method_def_id, &paths::STR_LEN);
|
||||
then {
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& match_def_path(cx, method_def_id, &paths::STR_LEN)
|
||||
{
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,36 +204,38 @@ fn find_stripping<'tcx>(
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if is_ref_str(self.cx, ex);
|
||||
let unref = peel_ref(ex);
|
||||
if let ExprKind::Index(indexed, index, _) = &unref.kind;
|
||||
if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
|
||||
if let ExprKind::Path(path) = &indexed.kind;
|
||||
if self.cx.qpath_res(path, ex.hir_id) == self.target;
|
||||
then {
|
||||
match (self.strip_kind, start, end) {
|
||||
(StripKind::Prefix, Some(start), None) => {
|
||||
if eq_pattern_length(self.cx, self.pattern, start) {
|
||||
self.results.push(ex.span);
|
||||
return;
|
||||
}
|
||||
},
|
||||
(StripKind::Suffix, None, Some(end)) => {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
|
||||
if let Some(left_arg) = len_arg(self.cx, left);
|
||||
if let ExprKind::Path(left_path) = &left_arg.kind;
|
||||
if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
|
||||
if eq_pattern_length(self.cx, self.pattern, right);
|
||||
then {
|
||||
self.results.push(ex.span);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
if is_ref_str(self.cx, ex)
|
||||
&& let unref = peel_ref(ex)
|
||||
&& let ExprKind::Index(indexed, index, _) = &unref.kind
|
||||
&& let Some(higher::Range { start, end, .. }) = higher::Range::hir(index)
|
||||
&& let ExprKind::Path(path) = &indexed.kind
|
||||
&& self.cx.qpath_res(path, ex.hir_id) == self.target
|
||||
{
|
||||
match (self.strip_kind, start, end) {
|
||||
(StripKind::Prefix, Some(start), None) => {
|
||||
if eq_pattern_length(self.cx, self.pattern, start) {
|
||||
self.results.push(ex.span);
|
||||
return;
|
||||
}
|
||||
},
|
||||
(StripKind::Suffix, None, Some(end)) => {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub, ..
|
||||
},
|
||||
left,
|
||||
right,
|
||||
) = end.kind
|
||||
&& let Some(left_arg) = len_arg(self.cx, left)
|
||||
&& let ExprKind::Path(left_path) = &left_arg.kind
|
||||
&& self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
|
||||
&& eq_pattern_length(self.cx, self.pattern, right)
|
||||
{
|
||||
self.results.push(ex.span);
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{iter_input_pats, method_chain_args};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -163,16 +162,14 @@ fn unit_closure<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &hir::Expr<'_>,
|
||||
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind;
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let body_expr = &body.value;
|
||||
if fn_decl.inputs.len() == 1;
|
||||
if is_unit_expression(cx, body_expr);
|
||||
if let Some(binding) = iter_input_pats(fn_decl, body).next();
|
||||
then {
|
||||
return Some((binding, body_expr));
|
||||
}
|
||||
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind
|
||||
&& let body = cx.tcx.hir().body(body)
|
||||
&& let body_expr = &body.value
|
||||
&& fn_decl.inputs.len() == 1
|
||||
&& is_unit_expression(cx, body_expr)
|
||||
&& let Some(binding) = iter_input_pats(fn_decl, body).next()
|
||||
{
|
||||
return Some((binding, body_expr));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{higher, is_res_lang_ctor};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -56,33 +55,31 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
|
||||
return;
|
||||
};
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
|
||||
if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation
|
||||
if ok_path.ident.as_str() == "ok";
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
|
||||
let ctxt = expr.span.ctxt();
|
||||
if let_expr.span.ctxt() == ctxt;
|
||||
if let_pat.span.ctxt() == ctxt;
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
|
||||
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
|
||||
let sugg = format!(
|
||||
"{ifwhile} let Ok({some_expr_string}) = {}",
|
||||
trimmed_ok.trim().trim_end_matches('.'),
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MATCH_RESULT_OK,
|
||||
expr.span.with_hi(let_expr.span.hi()),
|
||||
"matching on `Some` with `ok()` is redundant",
|
||||
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result<T,E>.ok(, _)
|
||||
&& let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation
|
||||
&& ok_path.ident.as_str() == "ok"
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let_expr.span.ctxt() == ctxt
|
||||
&& let_pat.span.ctxt() == ctxt
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
|
||||
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
|
||||
let sugg = format!(
|
||||
"{ifwhile} let Ok({some_expr_string}) = {}",
|
||||
trimmed_ok.trim().trim_end_matches('.'),
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MATCH_RESULT_OK,
|
||||
expr.span.with_hi(let_expr.span.hi()),
|
||||
"matching on `Some` with `ok()` is redundant",
|
||||
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::{
|
||||
is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::MultiSpan;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
|
||||
@ -40,76 +39,73 @@ fn check_arm<'tcx>(
|
||||
outer_else_body: Option<&'tcx Expr<'tcx>>,
|
||||
) {
|
||||
let inner_expr = peel_blocks_with_stmt(outer_then_body);
|
||||
if_chain! {
|
||||
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
|
||||
if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
|
||||
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr)
|
||||
&& let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
|
||||
IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
|
||||
IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
|
||||
IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none())
|
||||
// if there are more than two arms, collapsing would be non-trivial
|
||||
if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
|
||||
// one of the arms must be "wild-like"
|
||||
if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
|
||||
then {
|
||||
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
|
||||
Some((scrutinee, then.pat, Some(els.body)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a))
|
||||
{
|
||||
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
|
||||
Some((scrutinee, then.pat, Some(els.body)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
};
|
||||
if outer_pat.span.eq_ctxt(inner_scrutinee.span);
|
||||
}
|
||||
&& outer_pat.span.eq_ctxt(inner_scrutinee.span)
|
||||
// match expression must be a local binding
|
||||
// match <local> { .. }
|
||||
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
|
||||
if !pat_contains_or(inner_then_pat);
|
||||
&& let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee))
|
||||
&& !pat_contains_or(inner_then_pat)
|
||||
// the binding must come from the pattern of the containing match arm
|
||||
// ..<local>.. => match <local> { .. }
|
||||
if let (Some(binding_span), is_innermost_parent_pat_struct)
|
||||
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id);
|
||||
&& let (Some(binding_span), is_innermost_parent_pat_struct)
|
||||
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id)
|
||||
// the "else" branches must be equal
|
||||
if match (outer_else_body, inner_else_body) {
|
||||
&& match (outer_else_body, inner_else_body) {
|
||||
(None, None) => true,
|
||||
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
|
||||
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
|
||||
};
|
||||
}
|
||||
// the binding must not be used in the if guard
|
||||
if outer_guard.map_or(
|
||||
&& outer_guard.map_or(
|
||||
true,
|
||||
|(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
|
||||
);
|
||||
)
|
||||
// ...or anywhere in the inner expression
|
||||
if match inner {
|
||||
&& match inner {
|
||||
IfLetOrMatch::IfLet(_, _, body, els) => {
|
||||
!is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
|
||||
},
|
||||
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
|
||||
};
|
||||
then {
|
||||
let msg = format!(
|
||||
"this `{}` can be collapsed into the outer `{}`",
|
||||
if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
|
||||
if outer_is_match { "match" } else { "if let" },
|
||||
);
|
||||
// collapsing patterns need an explicit field name in struct pattern matching
|
||||
// ex: Struct {x: Some(1)}
|
||||
let replace_msg = if is_innermost_parent_pat_struct {
|
||||
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_MATCH,
|
||||
inner_expr.span,
|
||||
&msg,
|
||||
|diag| {
|
||||
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
|
||||
help_span.push_span_label(binding_span, "replace this binding");
|
||||
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
|
||||
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
|
||||
},
|
||||
);
|
||||
}
|
||||
{
|
||||
let msg = format!(
|
||||
"this `{}` can be collapsed into the outer `{}`",
|
||||
if matches!(inner, IfLetOrMatch::Match(..)) {
|
||||
"match"
|
||||
} else {
|
||||
"if let"
|
||||
},
|
||||
if outer_is_match { "match" } else { "if let" },
|
||||
);
|
||||
// collapsing patterns need an explicit field name in struct pattern matching
|
||||
// ex: Struct {x: Some(1)}
|
||||
let replace_msg = if is_innermost_parent_pat_struct {
|
||||
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
span_lint_and_then(cx, COLLAPSIBLE_MATCH, inner_expr.span, &msg, |diag| {
|
||||
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
|
||||
help_span.push_span_label(binding_span, "replace this binding");
|
||||
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
|
||||
diag.span_help(
|
||||
help_span,
|
||||
"the outer pattern can be modified to include the inner pattern",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,38 +8,35 @@ use rustc_lint::LateContext;
|
||||
use super::INFALLIBLE_DESTRUCTURING_MATCH;
|
||||
|
||||
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
|
||||
if_chain! {
|
||||
if !local.span.from_expansion();
|
||||
if let Some(expr) = local.init;
|
||||
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
|
||||
if arms.len() == 1 && arms[0].guard.is_none();
|
||||
if let PatKind::TupleStruct(
|
||||
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
|
||||
if args.len() == 1;
|
||||
if let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind;
|
||||
let body = peel_blocks(arms[0].body);
|
||||
if path_to_local_id(body, arg);
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INFALLIBLE_DESTRUCTURING_MATCH,
|
||||
local.span,
|
||||
"you seem to be trying to use `match` to destructure a single infallible pattern. \
|
||||
Consider using `let`",
|
||||
"try",
|
||||
format!(
|
||||
"let {}({}{}) = {};",
|
||||
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
|
||||
if binding.0 == ByRef::Yes { "ref " } else { "" },
|
||||
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, target.span, "..", &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if !local.span.from_expansion()
|
||||
&& let Some(expr) = local.init
|
||||
&& let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind
|
||||
&& arms.len() == 1
|
||||
&& arms[0].guard.is_none()
|
||||
&& let PatKind::TupleStruct(QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind
|
||||
&& args.len() == 1
|
||||
&& let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind
|
||||
&& let body = peel_blocks(arms[0].body)
|
||||
&& path_to_local_id(body, arg)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
INFALLIBLE_DESTRUCTURING_MATCH,
|
||||
local.span,
|
||||
"you seem to be trying to use `match` to destructure a single infallible pattern. \
|
||||
Consider using `let`",
|
||||
"try",
|
||||
format!(
|
||||
"let {}({}{}) = {};",
|
||||
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
|
||||
if binding.0 == ByRef::Yes { "ref " } else { "" },
|
||||
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, target.span, "..", &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
@ -21,19 +21,19 @@ fn get_cond_expr<'tcx>(
|
||||
expr: &'tcx Expr<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
) -> Option<SomeExpr<'tcx>> {
|
||||
if_chain! {
|
||||
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
|
||||
if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
|
||||
if let PatKind::Binding(_,target, ..) = pat.kind;
|
||||
if is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|
||||
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr); // check that one expr resolves to `Some(x)`, the other to `None`
|
||||
then {
|
||||
return Some(SomeExpr {
|
||||
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
|
||||
needs_unsafe_block: contains_unsafe_block(cx, expr),
|
||||
needs_negated: is_none_expr(cx, then_expr) // if the `then_expr` resolves to `None`, need to negate the cond
|
||||
})
|
||||
}
|
||||
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr)
|
||||
&& let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind
|
||||
&& let PatKind::Binding(_, target, ..) = pat.kind
|
||||
&& (is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|
||||
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr))
|
||||
// check that one expr resolves to `Some(x)`, the other to `None`
|
||||
{
|
||||
return Some(SomeExpr {
|
||||
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
|
||||
needs_unsafe_block: contains_unsafe_block(cx, expr),
|
||||
needs_negated: is_none_expr(cx, then_expr), /* if the `then_expr` resolves to `None`, need to negate the
|
||||
* cond */
|
||||
});
|
||||
};
|
||||
None
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user