Merge commit '7c21f91b15b7604f818565646b686d90f99d1baf' into clippyup

This commit is contained in:
flip1995 2022-05-05 15:12:52 +01:00
parent 82f469f81b
commit 7cd86aa1be
293 changed files with 6318 additions and 2753 deletions

View File

@ -9,7 +9,7 @@ body:
attributes:
label: Description
description: >
Please provide a discription of the issue, along with any information
Please provide a description of the issue, along with any information
you feel relevant to replicate it.
validations:
required: true

View File

@ -23,7 +23,7 @@ body:
id: reproducer
attributes:
label: Reproducer
description: Please provide the code and steps to repoduce the bug
description: Please provide the code and steps to reproduce the bug
value: |
I tried this code:

View File

@ -24,7 +24,7 @@ body:
attributes:
label: Reproducer
description: >
Please provide the code and steps to repoduce the bug together with the
Please provide the code and steps to reproduce the bug together with the
output from Clippy.
value: |
I tried this code:

View File

@ -6,14 +6,14 @@ on:
branches-ignore:
- auto
- try
# Don't run Clippy tests, when only textfiles were modified
# Don't run Clippy tests, when only text files were modified
paths-ignore:
- 'COPYRIGHT'
- 'LICENSE-*'
- '**.md'
- '**.txt'
pull_request:
# Don't run Clippy tests, when only textfiles were modified
# Don't run Clippy tests, when only text files were modified
paths-ignore:
- 'COPYRIGHT'
- 'LICENSE-*'
@ -37,7 +37,7 @@ jobs:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Install toolchain
run: rustup show active-toolchain

View File

@ -25,7 +25,7 @@ jobs:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
with:
ref: ${{ github.ref }}
@ -88,7 +88,7 @@ jobs:
if: matrix.host == 'i686-unknown-linux-gnu'
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Install toolchain
run: rustup show active-toolchain
@ -154,7 +154,7 @@ jobs:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Install toolchain
run: rustup show active-toolchain
@ -212,7 +212,7 @@ jobs:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Install toolchain
run: rustup show active-toolchain

View File

@ -23,7 +23,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
# Run
- name: Build

View File

@ -21,10 +21,10 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
with:
ref: ${{ env.TARGET_BRANCH }}
path: 'out'

View File

@ -16,7 +16,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v2.3.3
uses: actions/checkout@v3.0.2
- name: Setup Node.js
uses: actions/setup-node@v1.4.4

View File

@ -6,7 +6,108 @@ document.
## Unreleased / In Rust Nightly
[57b3c4b...master](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...master)
[d0cf3481...master](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...master)
## Rust 1.61 (beta)
Current beta, released 2022-05-19
[57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481)
### New Lints
* [`only_used_in_recursion`]
[#8422](https://github.com/rust-lang/rust-clippy/pull/8422)
* [`cast_enum_truncation`]
[#8381](https://github.com/rust-lang/rust-clippy/pull/8381)
* [`missing_spin_loop`]
[#8174](https://github.com/rust-lang/rust-clippy/pull/8174)
* [`deref_by_slicing`]
[#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
* [`needless_match`]
[#8471](https://github.com/rust-lang/rust-clippy/pull/8471)
* [`allow_attributes_without_reason`] (Requires `#![feature(lint_reasons)]`)
[#8504](https://github.com/rust-lang/rust-clippy/pull/8504)
* [`print_in_format_impl`]
[#8253](https://github.com/rust-lang/rust-clippy/pull/8253)
* [`unnecessary_find_map`]
[#8489](https://github.com/rust-lang/rust-clippy/pull/8489)
* [`or_then_unwrap`]
[#8561](https://github.com/rust-lang/rust-clippy/pull/8561)
* [`unnecessary_join`]
[#8579](https://github.com/rust-lang/rust-clippy/pull/8579)
* [`iter_with_drain`]
[#8483](https://github.com/rust-lang/rust-clippy/pull/8483)
* [`cast_enum_constructor`]
[#8562](https://github.com/rust-lang/rust-clippy/pull/8562)
* [`cast_slice_different_sizes`]
[#8445](https://github.com/rust-lang/rust-clippy/pull/8445)
### Moves and Deprecations
* Moved [`transmute_undefined_repr`] to `nursery` (now allow-by-default)
[#8432](https://github.com/rust-lang/rust-clippy/pull/8432)
* Moved [`try_err`] to `restriction`
[#8544](https://github.com/rust-lang/rust-clippy/pull/8544)
* Move [`iter_with_drain`] to `nursery`
[#8541](https://github.com/rust-lang/rust-clippy/pull/8541)
* Renamed `to_string_in_display` to [`recursive_format_impl`]
[#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
### Enhancements
* [`dbg_macro`]: The lint level can now be set with crate attributes and works inside macros
[#8411](https://github.com/rust-lang/rust-clippy/pull/8411)
* [`ptr_as_ptr`]: Now works inside macros
[#8442](https://github.com/rust-lang/rust-clippy/pull/8442)
* [`use_self`]: Now works for variants in match expressions
[#8456](https://github.com/rust-lang/rust-clippy/pull/8456)
* [`await_holding_lock`]: Now lints for `parking_lot::{Mutex, RwLock}`
[#8419](https://github.com/rust-lang/rust-clippy/pull/8419)
* [`recursive_format_impl`]: Now checks for format calls on `self`
[#8188](https://github.com/rust-lang/rust-clippy/pull/8188)
### False Positive Fixes
* [`new_without_default`]: No longer lints for `new()` methods with `#[doc(hidden)]`
[#8472](https://github.com/rust-lang/rust-clippy/pull/8472)
* [`transmute_undefined_repr`]: No longer lints for single field structs with `#[repr(C)]`,
generic parameters, wide pointers, unions, tuples and allow several forms of type erasure
[#8425](https://github.com/rust-lang/rust-clippy/pull/8425)
[#8553](https://github.com/rust-lang/rust-clippy/pull/8553)
[#8440](https://github.com/rust-lang/rust-clippy/pull/8440)
[#8547](https://github.com/rust-lang/rust-clippy/pull/8547)
* [`match_single_binding`], [`match_same_arms`], [`match_as_ref`], [`match_bool`]: No longer
lint `match` expressions with `cfg`ed arms
[#8443](https://github.com/rust-lang/rust-clippy/pull/8443)
* [`single_component_path_imports`]: No longer lint on macros
[#8537](https://github.com/rust-lang/rust-clippy/pull/8537)
* [`ptr_arg`]: Allow `&mut` arguments for `Cow<_>`
[#8552](https://github.com/rust-lang/rust-clippy/pull/8552)
* [`needless_borrow`]: No longer lints for method calls
[#8441](https://github.com/rust-lang/rust-clippy/pull/8441)
* [`match_same_arms`]: Now ensures that interposing arm patterns don't overlap
[#8232](https://github.com/rust-lang/rust-clippy/pull/8232)
* [`default_trait_access`]: Now allows `Default::default` in update expressions
[#8433](https://github.com/rust-lang/rust-clippy/pull/8433)
### Suggestion Fixes/Improvements
* [`redundant_slicing`]: Fixed suggestion for a method calls
[#8218](https://github.com/rust-lang/rust-clippy/pull/8218)
* [`map_flatten`]: Long suggestions will now be split up into two help messages
[#8520](https://github.com/rust-lang/rust-clippy/pull/8520)
* [`unnecessary_lazy_evaluations`]: Now shows suggestions for longer code snippets
[#8543](https://github.com/rust-lang/rust-clippy/pull/8543)
* [`unnecessary_sort_by`]: Now suggests `Reverse` including the path
[#8462](https://github.com/rust-lang/rust-clippy/pull/8462)
* [`search_is_some`]: More suggestions are now `MachineApplicable`
[#8536](https://github.com/rust-lang/rust-clippy/pull/8536)
### Documentation Improvements
* [`new_without_default`]: Document `pub` requirement for the struct and fields
[#8429](https://github.com/rust-lang/rust-clippy/pull/8429)
## Rust 1.60
@ -3182,6 +3283,7 @@ Released 2018-09-13
[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
[`await_holding_invalid_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type
[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
@ -3198,6 +3300,7 @@ Released 2018-09-13
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
@ -3262,6 +3365,7 @@ Released 2018-09-13
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
@ -3314,6 +3418,7 @@ Released 2018-09-13
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
@ -3356,6 +3461,7 @@ Released 2018-09-13
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
@ -3372,6 +3478,7 @@ Released 2018-09-13
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
@ -3472,6 +3579,7 @@ Released 2018-09-13
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
@ -3527,6 +3635,7 @@ Released 2018-09-13
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
@ -3627,6 +3736,7 @@ Released 2018-09-13
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
[`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
@ -3650,6 +3760,7 @@ Released 2018-09-13
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned

View File

@ -4,6 +4,7 @@ version = "0.0.1"
edition = "2021"
[dependencies]
aho-corasick = "0.7"
clap = "2.33"
indoc = "1.0"
itertools = "0.10.1"

View File

@ -1,3 +1,4 @@
#![feature(let_chains)]
#![feature(let_else)]
#![feature(once_cell)]
#![feature(rustc_private)]

View File

@ -19,9 +19,9 @@ fn main() {
if matches.is_present("print-only") {
update_lints::print_lints();
} else if matches.is_present("check") {
update_lints::run(update_lints::UpdateMode::Check);
update_lints::update(update_lints::UpdateMode::Check);
} else {
update_lints::run(update_lints::UpdateMode::Change);
update_lints::update(update_lints::UpdateMode::Change);
}
},
("new_lint", Some(matches)) => {
@ -31,18 +31,36 @@ fn main() {
matches.value_of("category"),
matches.is_present("msrv"),
) {
Ok(_) => update_lints::run(update_lints::UpdateMode::Change),
Ok(_) => update_lints::update(update_lints::UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {}", e),
}
},
("setup", Some(sub_command)) => match sub_command.subcommand() {
("intellij", Some(matches)) => setup::intellij::setup_rustc_src(
matches
.value_of("rustc-repo-path")
.expect("this field is mandatory and therefore always valid"),
),
("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")),
("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")),
("intellij", Some(matches)) => {
if matches.is_present("remove") {
setup::intellij::remove_rustc_src();
} else {
setup::intellij::setup_rustc_src(
matches
.value_of("rustc-repo-path")
.expect("this field is mandatory and therefore always valid"),
);
}
},
("git-hook", Some(matches)) => {
if matches.is_present("remove") {
setup::git_hook::remove_hook();
} else {
setup::git_hook::install_hook(matches.is_present("force-override"));
}
},
("vscode-tasks", Some(matches)) => {
if matches.is_present("remove") {
setup::vscode::remove_tasks();
} else {
setup::vscode::install_tasks(matches.is_present("force-override"));
}
},
_ => {},
},
("remove", Some(sub_command)) => match sub_command.subcommand() {
@ -60,6 +78,12 @@ fn main() {
let path = matches.value_of("path").unwrap();
lint::run(path);
},
("rename_lint", Some(matches)) => {
let old_name = matches.value_of("old_name").unwrap();
let new_name = matches.value_of("new_name").unwrap_or(old_name);
let uplift = matches.is_present("uplift");
update_lints::rename(old_name, new_name, uplift);
},
_ => {},
}
}
@ -167,6 +191,12 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.subcommand(
SubCommand::with_name("intellij")
.about("Alter dependencies so Intellij Rust can find rustc internals")
.arg(
Arg::with_name("remove")
.long("remove")
.help("Remove the dependencies added with 'cargo dev setup intellij'")
.required(false),
)
.arg(
Arg::with_name("rustc-repo-path")
.long("repo-path")
@ -174,12 +204,19 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.help("The path to a rustc repo that will be used for setting the dependencies")
.takes_value(true)
.value_name("path")
.conflicts_with("remove")
.required(true),
),
)
.subcommand(
SubCommand::with_name("git-hook")
.about("Add a pre-commit git hook that formats your code to make it look pretty")
.arg(
Arg::with_name("remove")
.long("remove")
.help("Remove the pre-commit hook added with 'cargo dev setup git-hook'")
.required(false),
)
.arg(
Arg::with_name("force-override")
.long("force-override")
@ -191,6 +228,12 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.subcommand(
SubCommand::with_name("vscode-tasks")
.about("Add several tasks to vscode for formatting, validation and testing")
.arg(
Arg::with_name("remove")
.long("remove")
.help("Remove the tasks added with 'cargo dev setup vscode-tasks'")
.required(false),
)
.arg(
Arg::with_name("force-override")
.long("force-override")
@ -242,5 +285,26 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.help("The path to a file or package directory to lint"),
),
)
.subcommand(
SubCommand::with_name("rename_lint")
.about("Renames the given lint")
.arg(
Arg::with_name("old_name")
.index(1)
.required(true)
.help("The name of the lint to rename"),
)
.arg(
Arg::with_name("new_name")
.index(2)
.required_unless("uplift")
.help("The new name of the lint"),
)
.arg(
Arg::with_name("uplift")
.long("uplift")
.help("This lint will be uplifted into rustc"),
),
)
.get_matches()
}

View File

@ -1,5 +1,6 @@
use crate::clippy_project_root;
use indoc::indoc;
use std::fmt::Write as _;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
@ -232,7 +233,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
)
});
result.push_str(&format!(
let _ = write!(
result,
indoc! {r#"
declare_clippy_lint! {{
/// ### What it does
@ -256,7 +258,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
version = version,
name_upper = name_upper,
category = category,
));
);
result.push_str(&if enable_msrv {
format!(

View File

@ -7,7 +7,7 @@ use std::path::Path;
const CLIPPY_DEV_DIR: &str = "clippy_dev";
/// This function verifies that the tool is being executed in the clippy directory.
/// This is useful to ensure that setups only modify Clippys resources. The verification
/// This is useful to ensure that setups only modify Clippy's resources. The verification
/// is done by checking that `clippy_dev` is a sub directory of the current directory.
///
/// It will print an error message and return `false` if the directory could not be
@ -17,7 +17,7 @@ fn verify_inside_clippy_dir() -> bool {
if path.exists() && path.is_dir() {
true
} else {
eprintln!("error: unable to verify that the working directory is clippys directory");
eprintln!("error: unable to verify that the working directory is clippy's directory");
false
}
}

View File

@ -1,11 +1,13 @@
use core::fmt::Write;
use aho_corasick::AhoCorasickBuilder;
use core::fmt::Write as _;
use itertools::Itertools;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use walkdir::WalkDir;
use std::io::{self, Read as _, Seek as _, Write as _};
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
use crate::clippy_project_root;
@ -30,12 +32,19 @@ pub enum UpdateMode {
/// # Panics
///
/// Panics if a file path could not read from or then written to
#[allow(clippy::too_many_lines)]
pub fn run(update_mode: UpdateMode) {
let (lints, deprecated_lints) = gather_all();
pub fn update(update_mode: UpdateMode) {
let (lints, deprecated_lints, renamed_lints) = gather_all();
generate_lint_files(update_mode, &lints, &deprecated_lints, &renamed_lints);
}
let internal_lints = Lint::internal_lints(&lints);
let usable_lints = Lint::usable_lints(&lints);
fn generate_lint_files(
update_mode: UpdateMode,
lints: &[Lint],
deprecated_lints: &[DeprecatedLint],
renamed_lints: &[RenamedLint],
) {
let internal_lints = Lint::internal_lints(lints);
let usable_lints = Lint::usable_lints(lints);
let mut sorted_usable_lints = usable_lints.clone();
sorted_usable_lints.sort_by_key(|lint| lint.name.clone());
@ -87,7 +96,7 @@ pub fn run(update_mode: UpdateMode) {
process_file(
"clippy_lints/src/lib.deprecated.rs",
update_mode,
&gen_deprecated(&deprecated_lints),
&gen_deprecated(deprecated_lints),
);
let all_group_lints = usable_lints.iter().filter(|l| {
@ -107,10 +116,16 @@ pub fn run(update_mode: UpdateMode) {
&content,
);
}
let content = gen_deprecated_lints_test(deprecated_lints);
process_file("tests/ui/deprecated.rs", update_mode, &content);
let content = gen_renamed_lints_test(renamed_lints);
process_file("tests/ui/rename.rs", update_mode, &content);
}
pub fn print_lints() {
let (lint_list, _) = gather_all();
let (lint_list, _, _) = gather_all();
let usable_lints = Lint::usable_lints(&lint_list);
let usable_lint_count = usable_lints.len();
let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
@ -128,6 +143,209 @@ pub fn print_lints() {
println!("there are {} lints", usable_lint_count);
}
/// Runs the `rename_lint` command.
///
/// This does the following:
/// * Adds an entry to `renamed_lints.rs`.
/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`).
/// * Renames the lint struct to the new name.
/// * Renames the module containing the lint struct to the new name if it shares a name with the
/// lint.
///
/// # Panics
/// Panics for the following conditions:
/// * If a file path could not read from or then written to
/// * If either lint name has a prefix
/// * If `old_name` doesn't name an existing lint.
/// * If `old_name` names a deprecated or renamed lint.
#[allow(clippy::too_many_lines)]
pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
if let Some((prefix, _)) = old_name.split_once("::") {
panic!("`{}` should not contain the `{}` prefix", old_name, prefix);
}
if let Some((prefix, _)) = new_name.split_once("::") {
panic!("`{}` should not contain the `{}` prefix", new_name, prefix);
}
let (mut lints, deprecated_lints, mut renamed_lints) = gather_all();
let mut old_lint_index = None;
let mut found_new_name = false;
for (i, lint) in lints.iter().enumerate() {
if lint.name == old_name {
old_lint_index = Some(i);
} else if lint.name == new_name {
found_new_name = true;
}
}
let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name));
let lint = RenamedLint {
old_name: format!("clippy::{}", old_name),
new_name: if uplift {
new_name.into()
} else {
format!("clippy::{}", new_name)
},
};
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
// case.
assert!(
!renamed_lints.iter().any(|l| lint.old_name == l.old_name),
"`{}` has already been renamed",
old_name
);
assert!(
!deprecated_lints.iter().any(|l| lint.old_name == l.name),
"`{}` has already been deprecated",
old_name
);
// Update all lint level attributes. (`clippy::lint_name`)
for file in WalkDir::new(clippy_project_root())
.into_iter()
.map(Result::unwrap)
.filter(|f| {
let name = f.path().file_name();
let ext = f.path().extension();
(ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
&& name != Some(OsStr::new("rename.rs"))
&& name != Some(OsStr::new("renamed_lints.rs"))
})
{
rewrite_file(file.path(), |s| {
replace_ident_like(s, &[(&lint.old_name, &lint.new_name)])
});
}
renamed_lints.push(lint);
renamed_lints.sort_by(|lhs, rhs| {
lhs.new_name
.starts_with("clippy::")
.cmp(&rhs.new_name.starts_with("clippy::"))
.reverse()
.then_with(|| lhs.old_name.cmp(&rhs.old_name))
});
write_file(
Path::new("clippy_lints/src/renamed_lints.rs"),
&gen_renamed_lints_list(&renamed_lints),
);
if uplift {
write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
println!(
"`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.",
old_name
);
} else if found_new_name {
write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
println!(
"`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.",
new_name
);
} else {
// Rename the lint struct and source files sharing a name with the lint.
let lint = &mut lints[old_lint_index];
let old_name_upper = old_name.to_uppercase();
let new_name_upper = new_name.to_uppercase();
lint.name = new_name.into();
// Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
if try_rename_file(
Path::new(&format!("tests/ui/{}.rs", old_name)),
Path::new(&format!("tests/ui/{}.rs", new_name)),
) {
try_rename_file(
Path::new(&format!("tests/ui/{}.stderr", old_name)),
Path::new(&format!("tests/ui/{}.stderr", new_name)),
);
try_rename_file(
Path::new(&format!("tests/ui/{}.fixed", old_name)),
Path::new(&format!("tests/ui/{}.fixed", new_name)),
);
}
// Try to rename the file containing the lint if the file name matches the lint's name.
let replacements;
let replacements = if lint.module == old_name
&& try_rename_file(
Path::new(&format!("clippy_lints/src/{}.rs", old_name)),
Path::new(&format!("clippy_lints/src/{}.rs", new_name)),
) {
// Edit the module name in the lint list. Note there could be multiple lints.
for lint in lints.iter_mut().filter(|l| l.module == old_name) {
lint.module = new_name.into();
}
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
replacements.as_slice()
} else if !lint.module.contains("::")
// Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
&& try_rename_file(
Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, old_name)),
Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)),
)
{
// Edit the module name in the lint list. Note there could be multiple lints, or none.
let renamed_mod = format!("{}::{}", lint.module, old_name);
for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
lint.module = format!("{}::{}", lint.module, new_name);
}
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
replacements.as_slice()
} else {
replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
&replacements[0..1]
};
// Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
// renamed.
for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) {
rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
}
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("{} has been successfully renamed", old_name);
}
println!("note: `cargo uitest` still needs to be run to update the test results");
}
/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
/// were no replacements.
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
fn is_ident_char(c: u8) -> bool {
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
}
let searcher = AhoCorasickBuilder::new()
.dfa(true)
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
.build_with_size::<u16, _, _>(replacements.iter().map(|&(x, _)| x.as_bytes()))
.unwrap();
let mut result = String::with_capacity(contents.len() + 1024);
let mut pos = 0;
let mut edited = false;
for m in searcher.find_iter(contents) {
let (old, new) = replacements[m.pattern()];
result.push_str(&contents[pos..m.start()]);
result.push_str(
if !is_ident_char(contents.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
&& !is_ident_char(contents.as_bytes().get(m.end()).copied().unwrap_or(0))
{
edited = true;
new
} else {
old
},
);
pos = m.end();
}
result.push_str(&contents[pos..]);
edited.then(|| result)
}
fn round_to_fifty(count: usize) -> usize {
count / 50 * 50
}
@ -210,6 +428,19 @@ impl DeprecatedLint {
}
}
struct RenamedLint {
old_name: String,
new_name: String,
}
impl RenamedLint {
fn new(old_name: &str, new_name: &str) -> Self {
Self {
old_name: remove_line_splices(old_name),
new_name: remove_line_splices(new_name),
}
}
}
/// Generates the code for registering a group
fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
@ -217,12 +448,13 @@ fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lin
let mut output = GENERATED_FILE_COMMENT.to_string();
output.push_str(&format!(
"store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![\n",
let _ = writeln!(
output,
"store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![",
group_name
));
);
for (module, name) in details {
output.push_str(&format!(" LintId::of({}::{}),\n", module, name));
let _ = writeln!(output, " LintId::of({}::{}),", module, name);
}
output.push_str("])\n");
@ -235,7 +467,8 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
let mut output = GENERATED_FILE_COMMENT.to_string();
output.push_str("{\n");
for lint in lints {
output.push_str(&format!(
let _ = write!(
output,
concat!(
" store.register_removed(\n",
" \"clippy::{}\",\n",
@ -243,7 +476,7 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
" );\n"
),
lint.name, lint.reason,
));
);
}
output.push_str("}\n");
@ -269,25 +502,62 @@ fn gen_register_lint_list<'a>(
if !is_public {
output.push_str(" #[cfg(feature = \"internal\")]\n");
}
output.push_str(&format!(" {}::{},\n", module_name, lint_name));
let _ = writeln!(output, " {}::{},", module_name, lint_name);
}
output.push_str("])\n");
output
}
fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
let mut res: String = GENERATED_FILE_COMMENT.into();
for lint in lints {
writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap();
}
res.push_str("\nfn main() {}\n");
res
}
fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
let mut seen_lints = HashSet::new();
let mut res: String = GENERATED_FILE_COMMENT.into();
res.push_str("// run-rustfix\n\n");
for lint in lints {
if seen_lints.insert(&lint.new_name) {
writeln!(res, "#![allow({})]", lint.new_name).unwrap();
}
}
seen_lints.clear();
for lint in lints {
if seen_lints.insert(&lint.old_name) {
writeln!(res, "#![warn({})]", lint.old_name).unwrap();
}
}
res.push_str("\nfn main() {}\n");
res
}
fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
const HEADER: &str = "\
// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
#[rustfmt::skip]\n\
pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
let mut res = String::from(HEADER);
for lint in lints {
writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
}
res.push_str("];\n");
res
}
/// Gathers all lints defined in `clippy_lints/src`
fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>) {
fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
let mut lints = Vec::with_capacity(1000);
let mut deprecated_lints = Vec::with_capacity(50);
let root_path = clippy_project_root().join("clippy_lints/src");
let mut renamed_lints = Vec::with_capacity(50);
for (rel_path, file) in WalkDir::new(&root_path)
.into_iter()
.map(Result::unwrap)
.filter(|f| f.path().extension() == Some(OsStr::new("rs")))
.map(|f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
{
for (rel_path, file) in clippy_lints_src_files() {
let path = file.path();
let contents =
fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
@ -305,13 +575,21 @@ fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>) {
module.strip_suffix(".rs").unwrap_or(&module)
};
if module == "deprecated_lints" {
parse_deprecated_contents(&contents, &mut deprecated_lints);
} else {
parse_contents(&contents, module, &mut lints);
match module {
"deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints),
"renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints),
_ => parse_contents(&contents, module, &mut lints),
}
}
(lints, deprecated_lints)
(lints, deprecated_lints, renamed_lints)
}
fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
let root_path = clippy_project_root().join("clippy_lints/src");
let iter = WalkDir::new(&root_path).into_iter();
iter.map(Result::unwrap)
.filter(|f| f.path().extension() == Some(OsStr::new("rs")))
.map(move |f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
}
macro_rules! match_tokens {
@ -394,6 +672,25 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
}
}
fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
for line in contents.lines() {
let mut offset = 0usize;
let mut iter = tokenize(line).map(|t| {
let range = offset..offset + t.len;
offset = range.end;
(t.kind, &line[range])
});
let (old_name, new_name) = match_tokens!(
iter,
// ("old_name",
Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(old_name) Comma
// "new_name"),
Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
);
lints.push(RenamedLint::new(old_name, new_name));
}
}
/// Removes the line splices and surrounding quotes from a string literal
fn remove_line_splices(s: &str) -> String {
let s = s
@ -462,6 +759,52 @@ fn replace_region_in_text<'a>(
Ok(res)
}
fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
match fs::OpenOptions::new().create_new(true).write(true).open(new_name) {
Ok(file) => drop(file),
Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
Err(e) => panic_file(e, new_name, "create"),
};
match fs::rename(old_name, new_name) {
Ok(()) => true,
Err(e) => {
drop(fs::remove_file(new_name));
if e.kind() == io::ErrorKind::NotFound {
false
} else {
panic_file(e, old_name, "rename");
}
},
}
}
#[allow(clippy::needless_pass_by_value)]
fn panic_file(error: io::Error, name: &Path, action: &str) -> ! {
panic!("failed to {} file `{}`: {}", action, name.display(), error)
}
fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
let mut file = fs::OpenOptions::new()
.write(true)
.read(true)
.open(path)
.unwrap_or_else(|e| panic_file(e, path, "open"));
let mut buf = String::new();
file.read_to_string(&mut buf)
.unwrap_or_else(|e| panic_file(e, path, "read"));
if let Some(new_contents) = f(&buf) {
file.rewind().unwrap_or_else(|e| panic_file(e, path, "write"));
file.write_all(new_contents.as_bytes())
.unwrap_or_else(|e| panic_file(e, path, "write"));
file.set_len(new_contents.len() as u64)
.unwrap_or_else(|e| panic_file(e, path, "write"));
}
}
fn write_file(path: &Path, contents: &str) {
fs::write(path, contents).unwrap_or_else(|e| panic_file(e, path, "write"));
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -6,7 +6,7 @@ use clippy_utils::msrvs;
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use clippy_utils::{extract_msrv_attr, meets_msrv};
use if_chain::if_chain;
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MacArgs, MacArgsEq, MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_hir::{
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
@ -335,9 +335,6 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
}
if let Some(lint_list) = &attr.meta_item_list() {
if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
// permit `unused_imports`, `deprecated`, `unreachable_pub`,
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
// and `unused_imports` for `extern crate` items with `macro_use`
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
@ -345,10 +342,12 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|| is_word(lint, sym::deprecated)
|| is_word(lint, sym!(unreachable_pub))
|| is_word(lint, sym!(unused))
|| extract_clippy_lint(lint)
.map_or(false, |s| s.as_str() == "wildcard_imports")
|| extract_clippy_lint(lint)
.map_or(false, |s| s.as_str() == "enum_glob_use")
|| extract_clippy_lint(lint).map_or(false, |s| {
matches!(
s.as_str(),
"wildcard_imports" | "enum_glob_use" | "redundant_pub_crate",
)
})
{
return;
}
@ -594,6 +593,10 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
};
if attr.style == AttrStyle::Outer {
if let MacArgs::Eq(_, MacArgsEq::Ast(expr)) = &attr_item.args
&& !matches!(expr.kind, rustc_ast::ExprKind::Lit(..)) {
return;
}
if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
return;
}

View File

@ -1,12 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GeneratorInteriorTypeCause;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use crate::utils::conf::DisallowedType;
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to await while holding a non-async-aware MutexGuard.
@ -127,9 +130,75 @@ declare_clippy_lint! {
"inside an async function, holding a `RefCell` ref while calling `await`"
}
declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
declare_clippy_lint! {
/// ### What it does
/// Allows users to configure types which should not be held across `await`
/// suspension points.
///
/// ### Why is this bad?
/// There are some types which are perfectly "safe" to be used concurrently
/// from a memory access perspective but will cause bugs at runtime if they
/// are held in such a way.
///
/// ### Known problems
///
/// ### Example
///
/// ```toml
/// await-holding-invalid-types = [
/// # You can specify a type name
/// "CustomLockType",
/// # You can (optionally) specify a reason
/// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
/// ]
/// ```
///
/// ```rust
/// # async fn baz() {}
/// struct CustomLockType;
/// struct OtherCustomLockType;
/// async fn foo() {
/// let _x = CustomLockType;
/// let _y = OtherCustomLockType;
/// baz().await; // Lint violation
/// }
/// ```
#[clippy::version = "1.49.0"]
pub AWAIT_HOLDING_INVALID_TYPE,
suspicious,
"holding a type across an await point which is not allowed to be held as per the configuration"
}
impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
#[derive(Debug)]
pub struct AwaitHolding {
conf_invalid_types: Vec<DisallowedType>,
def_ids: FxHashMap<DefId, DisallowedType>,
}
impl AwaitHolding {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
Self {
conf_invalid_types,
def_ids: FxHashMap::default(),
}
}
}
impl LateLintPass<'_> for AwaitHolding {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for conf in &self.conf_invalid_types {
let path = match conf {
DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
};
let segs: Vec<_> = path.split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
self.def_ids.insert(id, conf.clone());
}
}
}
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
use AsyncGeneratorKind::{Block, Closure, Fn};
if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
@ -137,7 +206,7 @@ impl LateLintPass<'_> for AwaitHolding {
hir_id: body.value.hir_id,
};
let typeck_results = cx.tcx.typeck_body(body_id);
check_interior_types(
self.check_interior_types(
cx,
typeck_results.generator_interior_types.as_ref().skip_binder(),
body.value.span,
@ -146,46 +215,68 @@ impl LateLintPass<'_> for AwaitHolding {
}
}
fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
for ty_cause in ty_causes {
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
if is_mutex_guard(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_LOCK,
ty_cause.span,
"this `MutexGuard` is held across an `await` point",
|diag| {
diag.help(
"consider using an async-aware `Mutex` type or ensuring the \
impl AwaitHolding {
fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
for ty_cause in ty_causes {
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
if is_mutex_guard(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_LOCK,
ty_cause.span,
"this `MutexGuard` is held across an `await` point",
|diag| {
diag.help(
"consider using an async-aware `Mutex` type or ensuring the \
`MutexGuard` is dropped before calling await",
);
diag.span_note(
ty_cause.scope_span.unwrap_or(span),
"these are all the `await` points this lock is held through",
);
},
);
}
if is_refcell_ref(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_REFCELL_REF,
ty_cause.span,
"this `RefCell` reference is held across an `await` point",
|diag| {
diag.help("ensure the reference is dropped before calling `await`");
diag.span_note(
ty_cause.scope_span.unwrap_or(span),
"these are all the `await` points this reference is held through",
);
},
);
);
diag.span_note(
ty_cause.scope_span.unwrap_or(span),
"these are all the `await` points this lock is held through",
);
},
);
} else if is_refcell_ref(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_REFCELL_REF,
ty_cause.span,
"this `RefCell` reference is held across an `await` point",
|diag| {
diag.help("ensure the reference is dropped before calling `await`");
diag.span_note(
ty_cause.scope_span.unwrap_or(span),
"these are all the `await` points this reference is held through",
);
},
);
} else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
emit_invalid_type(cx, ty_cause.span, disallowed);
}
}
}
}
}
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
let (type_name, reason) = match disallowed {
DisallowedType::Simple(path) => (path, &None),
DisallowedType::WithReason { path, reason } => (path, reason),
};
span_lint_and_then(
cx,
AWAIT_HOLDING_INVALID_TYPE,
span,
&format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
|diag| {
if let Some(reason) = reason {
diag.note(reason.clone());
}
},
);
}
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
match_def_path(cx, def_id, &paths::MUTEX_GUARD)
|| match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)

View File

@ -0,0 +1,70 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// It checks for `str::bytes().count()` and suggests replacing it with
/// `str::len()`.
///
/// ### Why is this bad?
/// `str::bytes().count()` is longer and may not be as performant as using
/// `str::len()`.
///
/// ### Example
/// ```rust
/// "hello".bytes().count();
/// String::from("hello").bytes().count();
/// ```
/// Use instead:
/// ```rust
/// "hello".len();
/// String::from("hello").len();
/// ```
#[clippy::version = "1.62.0"]
pub BYTES_COUNT_TO_LEN,
complexity,
"Using `bytes().count()` when `len()` performs the same functionality"
}
declare_lint_pass!(BytesCountToLen => [BYTES_COUNT_TO_LEN]);
impl<'tcx> LateLintPass<'tcx> for BytesCountToLen {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if let hir::ExprKind::MethodCall(_, expr_args, _) = &expr.kind;
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if match_def_path(cx, expr_def_id, &paths::ITER_COUNT);
if let [bytes_expr] = &**expr_args;
if let hir::ExprKind::MethodCall(_, bytes_args, _) = &bytes_expr.kind;
if let Some(bytes_def_id) = cx.typeck_results().type_dependent_def_id(bytes_expr.hir_id);
if match_def_path(cx, bytes_def_id, &paths::STR_BYTES);
if let [str_expr] = &**bytes_args;
let ty = cx.typeck_results().expr_ty(str_expr).peel_refs();
if is_type_diagnostic_item(cx, ty, sym::String) || ty.kind() == &ty::Str;
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
BYTES_COUNT_TO_LEN,
expr.span,
"using long and hard to read `.bytes().count()`",
"consider calling `.len()` instead",
format!("{}.len()", snippet_with_applicability(cx, str_expr.span, "..", &mut applicability)),
applicability
);
}
};
}
}

View File

@ -42,8 +42,8 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &
if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind;
if (2..=6).contains(&ext_literal.as_str().len());
if ext_literal.as_str().starts_with('.');
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10))
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10));
if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|| ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit());
then {
let mut ty = ctx.typeck_results().expr_ty(obj);
ty = match ty.kind() {

View File

@ -29,21 +29,19 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
ExprKind::Binary(op, left, right) => match op.node {
BinOpKind::Div => {
apply_reductions(cx, nbits, left, signed)
- (if signed {
0 // let's be conservative here
} else {
// by dividing by 1, we remove 0 bits, etc.
get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
})
apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
// let's be conservative here
0
} else {
// by dividing by 1, we remove 0 bits, etc.
get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
})
},
BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
.unwrap_or(u64::max_value())
.min(apply_reductions(cx, nbits, left, signed)),
BinOpKind::Shr => {
apply_reductions(cx, nbits, left, signed)
- constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))
},
BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
.saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
_ => nbits,
},
ExprKind::MethodCall(method, [left, right], _) => {

View File

@ -1,4 +1,4 @@
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt};
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
@ -8,6 +8,63 @@ use rustc_semver::RustcVersion;
use super::CAST_SLICE_DIFFERENT_SIZES;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Option<RustcVersion>) {
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
return;
}
// if this cast is the child of another cast expression then don't emit something for it, the full
// chain will be analyzed
if is_child_of_cast(cx, expr) {
return;
}
if let Some(CastChainInfo {
left_cast,
start_ty,
end_ty,
}) = expr_cast_chain_tys(cx, expr)
{
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
&format!(
"casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
start_ty.ty, from_size, end_ty.ty, to_size,
),
|diag| {
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
// T`, extract just the `T`
end_ty.ty
);
diag.span_suggestion(
expr.span,
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
}
}
}
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let map = cx.tcx.hir();
if_chain! {
@ -33,59 +90,6 @@ fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
return;
}
// if this cast is the child of another cast expression then don't emit something for it, the full
// chain will be analyzed
if is_child_of_cast(cx, expr) {
return;
}
if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) {
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) {
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
&format!(
"casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
from_slice_ty, from_size, to_slice_ty, to_size,
),
|diag| {
let cast_expr = match expr.kind {
ExprKind::Cast(cast_expr, ..) => cast_expr,
_ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"),
};
let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap();
let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)"
);
diag.span_suggestion(
expr.span,
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
}
}
}
/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
/// the type is one of those slices
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
@ -98,18 +102,40 @@ fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
}
}
/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
struct CastChainInfo<'tcx> {
/// The left most part of the cast chain, or in other words, the first cast in the chain
/// Used for diagnostics
left_cast: &'tcx Expr<'tcx>,
/// The starting type of the cast chain
start_ty: TypeAndMut<'tcx>,
/// The final type of the cast chain
end_ty: TypeAndMut<'tcx>,
}
/// Returns a `CastChainInfo` with the left-most cast in the chain and the original ptr T and final
/// ptr U if the expression is composed of casts.
/// Returns None if the expr is not a Cast
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<CastChainInfo<'tcx>> {
if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
let cast_to = cx.typeck_results().expr_ty(expr);
let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
Some((inner_from_ty, to_slice_ty))
// If the expression that makes up the source of this cast is itself a cast, recursively
// call `expr_cast_chain_tys` and update the end type with the final tartet type.
// Otherwise, this cast is not immediately nested, just construct the info for this cast
if let Some(prev_info) = expr_cast_chain_tys(cx, cast_expr) {
Some(CastChainInfo {
end_ty: to_slice_ty,
..prev_info
})
} else {
let cast_from = cx.typeck_results().expr_ty(cast_expr);
let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
Some((from_slice_ty, to_slice_ty))
Some(CastChainInfo {
left_cast: cast_expr,
start_ty: from_slice_ty,
end_ty: to_slice_ty,
})
}
} else {
None

View File

@ -270,7 +270,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
/// Casting a function pointer to an integer can have surprising results and can occur
/// accidentally if parantheses are omitted from a function call. If you aren't doing anything
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything
/// low-level with function pointers then you can opt-out of casting functions to integers in
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
/// pointer casts in your code.
@ -487,6 +487,7 @@ declare_clippy_lint! {
/// let y: u32 = x.abs() as u32;
/// ```
/// Use instead:
/// ```rust
/// let x: i32 = -42;
/// let y: u32 = x.unsigned_abs();
/// ```

View File

@ -13,13 +13,14 @@
//! This lint is **warn** by default
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet_block, snippet_block_with_applicability};
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};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -102,7 +103,7 @@ impl EarlyLintPass for CollapsibleIf {
fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ast::ExprKind::If(check, then, else_) = &expr.kind {
if let Some(else_) = else_ {
check_collapsible_maybe_if_let(cx, else_);
check_collapsible_maybe_if_let(cx, then.span, else_);
} else if let ast::ExprKind::Let(..) = check.kind {
// Prevent triggering on `if let a = b { if c { .. } }`.
} else {
@ -119,7 +120,7 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
}
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
if_chain! {
if let ast::ExprKind::Block(ref block, _) = else_.kind;
if !block_starts_with_comment(cx, block);
@ -128,6 +129,11 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
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 };
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
@ -135,7 +141,11 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability).into_owned(),
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
),
applicability,
);
}

View File

@ -240,7 +240,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
}
},
hir::ItemKind::Impl(ref impl_) => {
hir::ItemKind::Impl(impl_) => {
self.in_trait_impl = impl_.of_trait.is_some();
},
hir::ItemKind::Trait(_, unsafety, ..) => {
@ -621,10 +621,8 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
let filename = FileName::anon_source_code(&code);
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
rustc_errors::DEFAULT_LOCALE_RESOURCES,
false
);
let fallback_bundle =
rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
let emitter = EmitterWriter::new(
Box::new(io::sink()),
None,
@ -741,7 +739,7 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
/// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
/// Plurals are also excluded (`IDs` is ok).
fn is_camel_case(s: &str) -> bool {
if s.starts_with(|c: char| c.is_digit(10)) {
if s.starts_with(|c: char| c.is_ascii_digit()) {
return false;
}

View File

@ -180,7 +180,7 @@ const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that imp
const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
Forgetting a copy leaves the original intact";
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
Dropping such a type only extends it's contained lifetimes";
Dropping such a type only extends its contained lifetimes";
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
Forgetting such a type is the same as dropping it";

View File

@ -0,0 +1,65 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, 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};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for empty `Drop` implementations.
///
/// ### Why is this bad?
/// Empty `Drop` implementations have no effect when dropping an instance of the type. They are
/// most likely useless. However, an empty `Drop` implementation prevents a type from being
/// destructured, which might be the intention behind adding the implementation as a marker.
///
/// ### Example
/// ```rust
/// struct S;
///
/// impl Drop for S {
/// fn drop(&mut self) {}
/// }
/// ```
/// Use instead:
/// ```rust
/// struct S;
/// ```
#[clippy::version = "1.61.0"]
pub EMPTY_DROP,
restriction,
"empty `Drop` implementations"
}
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
);
}
}
}
}

View File

@ -66,7 +66,7 @@ fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Spa
}
// there might still be field declarations hidden from the AST
// (conditionaly compiled code using #[cfg(..)])
// (conditionally compiled code using #[cfg(..)])
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
return false;

View File

@ -3,7 +3,7 @@ use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id};
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
@ -103,6 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
let closure_ty = cx.typeck_results().expr_ty(expr);
if_chain!(
if !is_adjusted(cx, &body.value);
if let ExprKind::Call(callee, args) = body.value.kind;
if let ExprKind::Path(_) = callee.kind;
if check_inputs(cx, body.params, args);
@ -125,8 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
if_chain! {
if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
if substs.as_closure().kind() == ClosureKind::FnMut;
if get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
|| path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee));
if path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr));
then {
// Mutable closure is used after current expr; we cannot consume it.
@ -145,6 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
);
if_chain!(
if !is_adjusted(cx, &body.value);
if let ExprKind::MethodCall(path, args, _) = body.value.kind;
if check_inputs(cx, body.params, args);
let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();

View File

@ -0,0 +1,77 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_def_path, paths, peel_hir_expr_refs};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects cases where the result of a `format!` call is
/// appended to an existing `String`.
///
/// ### Why is this bad?
/// Introduces an extra, avoidable heap allocation.
///
/// ### Example
/// ```rust
/// let mut s = String::new();
/// s += &format!("0x{:X}", 1024);
/// s.push_str(&format!("0x{:X}", 1024));
/// ```
/// Use instead:
/// ```rust
/// use std::fmt::Write as _; // import without risk of name clashing
///
/// let mut s = String::new();
/// let _ = write!(s, "0x{:X}", 1024);
/// ```
#[clippy::version = "1.61.0"]
pub FORMAT_PUSH_STRING,
perf,
"`format!(..)` appended to existing `String`"
}
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);
fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
}
fn is_format(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Some(macro_def_id) = e.span.ctxt().outer_expn_data().macro_def_id {
cx.tcx.get_diagnostic_name(macro_def_id) == Some(sym::format_macro)
} else {
false
}
}
impl<'tcx> LateLintPass<'tcx> for FormatPushString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let arg = match expr.kind {
ExprKind::MethodCall(_, [_, arg], _) => {
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) &&
match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
arg
} else {
return;
}
}
ExprKind::AssignOp(op, left, arg)
if op.node == BinOpKind::Add && is_string(cx, left) => {
arg
},
_ => return,
};
let (arg, _) = peel_hir_expr_refs(arg);
if is_format(cx, arg) {
span_lint_and_help(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
None,
"consider using `write!` to avoid the extra allocation",
);
}
}
}

View File

@ -20,7 +20,7 @@ use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
let is_public = cx.access_levels.is_exported(item.def_id);
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr {
@ -105,12 +105,7 @@ fn check_needless_must_use(
fn_header_span,
"this unit-returning function has a `#[must_use]` attribute",
|diag| {
diag.span_suggestion(
attr.span,
"remove the attribute",
"",
Applicability::MachineApplicable,
);
diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable);
},
);
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {

View File

@ -14,7 +14,7 @@ use clippy_utils::ty::is_type_diagnostic_item;
use super::RESULT_UNIT_ERR;
pub(super) fn check_item(cx: &LateContext<'_>, item: &hir::Item<'_>) {
if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind {
if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind {
let is_public = cx.access_levels.is_exported(item.def_id);
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public {

View File

@ -1,3 +1,4 @@
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -22,6 +23,11 @@ declare_clippy_lint! {
/// # let x = 1;
/// x / 1 + 0 * 1 - 0 | 0;
/// ```
///
/// ### Known problems
/// False negatives: `f(0 + if b { 1 } else { 2 } + 3);` is reducible to
/// `f(if b { 1 } else { 2 } + 3);`. But the lint doesn't trigger for the code.
/// See [#8724](https://github.com/rust-lang/rust-clippy/issues/8724)
#[clippy::version = "pre 1.29.0"]
pub IDENTITY_OP,
complexity,
@ -31,36 +37,66 @@ declare_clippy_lint! {
declare_lint_pass!(IdentityOp => [IDENTITY_OP]);
impl<'tcx> LateLintPass<'tcx> for IdentityOp {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Binary(cmp, left, right) = e.kind {
if is_allowed(cx, cmp, left, right) {
return;
}
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
check(cx, left, 0, e.span, right.span);
check(cx, right, 0, e.span, left.span);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => check(cx, right, 0, e.span, left.span),
BinOpKind::Mul => {
check(cx, left, 1, e.span, right.span);
check(cx, right, 1, e.span, left.span);
},
BinOpKind::Div => check(cx, right, 1, e.span, left.span),
BinOpKind::BitAnd => {
check(cx, left, -1, e.span, right.span);
check(cx, right, -1, e.span, left.span);
},
BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
_ => (),
if let ExprKind::Binary(cmp, left, right) = &expr.kind {
if !is_allowed(cx, *cmp, left, right) {
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 0, expr.span, right.span);
}
check(cx, right, 0, expr.span, left.span);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check(cx, right, 0, expr.span, left.span);
},
BinOpKind::Mul => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 1, expr.span, right.span);
}
check(cx, right, 1, expr.span, left.span);
},
BinOpKind::Div => check(cx, right, 1, expr.span, left.span),
BinOpKind::BitAnd => {
if reducible_to_right(cx, expr, right) {
check(cx, left, -1, expr.span, right.span);
}
check(cx, right, -1, expr.span, left.span);
},
BinOpKind::Rem => {
// Don't call reducible_to_right because N % N is always reducible to 1
check_remainder(cx, left, right, expr.span, left.span);
},
_ => (),
}
}
}
}
}
/// Checks if `left op ..right` can be actually reduced to `right`
/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// cannot be reduced to `if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// See #8724
fn reducible_to_right(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> bool {
if let ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) = right.kind {
is_toplevel_binary(cx, binary)
} else {
true
}
}
fn is_toplevel_binary(cx: &LateContext<'_>, must_be_binary: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, must_be_binary) && let ExprKind::Binary(..) = &parent.kind {
false
} else {
true
}
}
fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
// This lint applies to integers
!cx.typeck_results().expr_ty(left).peel_refs().is_integral()

View File

@ -117,7 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
}
match item.kind {
ItemKind::Impl(ref impl_) => {
ItemKind::Impl(impl_) => {
let mut vis = ImplicitHasherTypeVisitor::new(cx);
vis.visit_ty(impl_.self_ty);
@ -155,7 +155,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
);
}
},
ItemKind::Fn(ref sig, ref generics, body_id) => {
ItemKind::Fn(ref sig, generics, body_id) => {
let body = cx.tcx.hir().body(body_id);
for ty in sig.decl.inputs {

View File

@ -7,6 +7,7 @@ use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use std::fmt::Write as _;
declare_clippy_lint! {
/// ### What it does
@ -89,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
fields_snippet.push_str(&format!("{}, ", ident));
let _ = write!(fields_snippet, "{}, ", ident);
}
fields_snippet.push_str(&last_ident.to_string());

View File

@ -116,7 +116,7 @@ fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap<hir
let bound_ty = cx.typeck_results().node_type(pat.hir_id);
if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() {
// The values need to use the `ref` keyword if they can't be copied.
// This will need to be adjusted if the lint want to support multable access in the future
// This will need to be adjusted if the lint want to support mutable access in the future
let src_is_ref = bound_ty.is_ref() && binding != hir::BindingAnnotation::Ref;
let needs_ref = !(src_is_ref || is_copy(cx, *inner_ty));

View File

@ -119,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> {
let id = cx.tcx.hir().local_def_id_to_hir_id(id);
if let Node::Item(&Item {
kind: ItemKind::Impl(ref impl_item),
kind: ItemKind::Impl(impl_item),
span,
..
}) = cx.tcx.hir().get(id)

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -49,6 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields {
&& fields
.iter()
.all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit))
&& !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..))
{
let expr_spans = fields
.iter()

View File

@ -0,0 +1,86 @@
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_lint_allowed;
use clippy_utils::macros::root_macro_call_first_node;
use rustc_ast::LitKind;
use rustc_hir::Expr;
use rustc_hir::ExprKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for the inclusion of large files via `include_bytes!()`
/// and `include_str!()`
///
/// ### Why is this bad?
/// Including large files can increase the size of the binary
///
/// ### Example
/// ```rust,ignore
/// let included_str = include_str!("very_large_file.txt");
/// let included_bytes = include_bytes!("very_large_file.txt");
/// ```
///
/// Instead, you can load the file at runtime:
/// ```rust,ignore
/// use std::fs;
///
/// let string = fs::read_to_string("very_large_file.txt")?;
/// let bytes = fs::read("very_large_file.txt")?;
/// ```
#[clippy::version = "1.62.0"]
pub LARGE_INCLUDE_FILE,
restriction,
"including a large file"
}
pub struct LargeIncludeFile {
max_file_size: u64,
}
impl LargeIncludeFile {
#[must_use]
pub fn new(max_file_size: u64) -> Self {
Self { max_file_size }
}
}
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 len as u64 <= self.max_file_size {
return;
}
span_lint_and_note(
cx,
LARGE_INCLUDE_FILE,
expr.span,
"attempted to include a large file",
None,
&format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
),
);
}
}
}
}

View File

@ -14,6 +14,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(attrs::DEPRECATED_SEMVER),
LintId::of(attrs::MISMATCHED_TARGET_OS),
LintId::of(attrs::USELESS_ATTRIBUTE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(bit_mask::BAD_BIT_MASK),
@ -23,6 +24,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
@ -77,6 +79,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
@ -165,6 +168,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::FLAT_MAP_IDENTITY),
LintId::of(methods::INSPECT_FOR_EACH),
LintId::of(methods::INTO_ITER_ON_REF),
LintId::of(methods::IS_DIGIT_ASCII_RADIX),
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
LintId::of(methods::ITER_CLONED_COLLECT),
LintId::of(methods::ITER_COUNT),
@ -182,6 +186,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::MAP_FLATTEN),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
LintId::of(methods::NEEDLESS_OPTION_TAKE),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::NEW_RET_NO_SELF),
LintId::of(methods::OK_EXPECT),
@ -243,7 +248,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
@ -275,8 +279,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
LintId::of(strings::TRIM_SPLIT_WHITESPACE),
LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
@ -306,10 +310,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(uninit_vec::UNINIT_VEC),
LintId::of(unit_hash::UNIT_HASH),
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
LintId::of(unit_types::LET_UNIT_VALUE),
LintId::of(unit_types::UNIT_ARG),
LintId::of(unit_types::UNIT_CMP),
LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),

View File

@ -5,6 +5,7 @@
store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
LintId::of(attrs::DEPRECATED_CFG_ATTR),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
@ -45,6 +46,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(methods::MAP_FLATTEN),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
LintId::of(methods::NEEDLESS_OPTION_TAKE),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP),
@ -66,7 +68,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(no_effect::NO_EFFECT),
LintId::of(no_effect::UNNECESSARY_OPERATION),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(precedence::PRECEDENCE),

View File

@ -52,6 +52,7 @@ store.register_lints(&[
attrs::INLINE_ALWAYS,
attrs::MISMATCHED_TARGET_OS,
attrs::USELESS_ATTRIBUTE,
await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
await_holding_invalid::AWAIT_HOLDING_LOCK,
await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
bit_mask::BAD_BIT_MASK,
@ -64,6 +65,7 @@ store.register_lints(&[
booleans::NONMINIMAL_BOOL,
borrow_as_ptr::BORROW_AS_PTR,
bytecount::NAIVE_BYTECOUNT,
bytes_count_to_len::BYTES_COUNT_TO_LEN,
cargo::CARGO_COMMON_METADATA,
cargo::MULTIPLE_CRATE_VERSIONS,
cargo::NEGATIVE_FEATURE_NAMES,
@ -132,6 +134,7 @@ store.register_lints(&[
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
duration_subsec::DURATION_SUBSEC,
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
empty_drop::EMPTY_DROP,
empty_enum::EMPTY_ENUM,
empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
entry::MAP_ENTRY,
@ -165,6 +168,7 @@ store.register_lints(&[
format_args::TO_STRING_IN_FORMAT_ARGS,
format_impl::PRINT_IN_FORMAT_IMPL,
format_impl::RECURSIVE_FORMAT_IMPL,
format_push_string::FORMAT_PUSH_STRING,
formatting::POSSIBLE_MISSING_COMMA,
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,
@ -205,6 +209,7 @@ store.register_lints(&[
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
large_const_arrays::LARGE_CONST_ARRAYS,
large_enum_variant::LARGE_ENUM_VARIANT,
large_include_file::LARGE_INCLUDE_FILE,
large_stack_arrays::LARGE_STACK_ARRAYS,
len_zero::COMPARISON_TO_EMPTY,
len_zero::LEN_WITHOUT_IS_EMPTY,
@ -302,6 +307,7 @@ store.register_lints(&[
methods::INEFFICIENT_TO_STRING,
methods::INSPECT_FOR_EACH,
methods::INTO_ITER_ON_REF,
methods::IS_DIGIT_ASCII_RADIX,
methods::ITERATOR_STEP_BY_ZERO,
methods::ITER_CLONED_COLLECT,
methods::ITER_COUNT,
@ -321,6 +327,7 @@ store.register_lints(&[
methods::MAP_IDENTITY,
methods::MAP_UNWRAP_OR,
methods::NEEDLESS_OPTION_AS_DEREF,
methods::NEEDLESS_OPTION_TAKE,
methods::NEEDLESS_SPLITN,
methods::NEW_RET_NO_SELF,
methods::OK_EXPECT,
@ -432,6 +439,7 @@ store.register_lints(&[
ptr::PTR_ARG,
ptr_eq::PTR_EQ,
ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
pub_use::PUB_USE,
question_mark::QUESTION_MARK,
ranges::MANUAL_RANGE_CONTAINS,
ranges::RANGE_MINUS_ONE,
@ -474,6 +482,7 @@ store.register_lints(&[
strings::STRING_SLICE,
strings::STRING_TO_STRING,
strings::STR_TO_STRING,
strings::TRIM_SPLIT_WHITESPACE,
strlen_on_c_strings::STRLEN_ON_C_STRINGS,
suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
@ -523,6 +532,7 @@ store.register_lints(&[
unit_types::UNIT_CMP,
unnamed_address::FN_ADDRESS_COMPARISONS,
unnamed_address::VTABLE_ADDRESS_COMPARISONS,
unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS,
unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
unnecessary_sort_by::UNNECESSARY_SORT_BY,
unnecessary_wraps::UNNECESSARY_WRAPS,

View File

@ -20,6 +20,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(mutex_atomic::MUTEX_INTEGER),
LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
@ -27,6 +28,8 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(strings::STRING_LIT_AS_BYTES),
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(use_self::USE_SELF),

View File

@ -82,14 +82,12 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(ref_option_ref::REF_OPTION_REF),
LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE),
LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(strings::STRING_ADD_ASSIGN),
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
LintId::of(types::LINKEDLIST),
LintId::of(types::OPTION_OPTION),
LintId::of(unicode::UNICODE_NOT_NFC),
LintId::of(unit_types::LET_UNIT_VALUE),
LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(unused_async::UNUSED_ASYNC),

View File

@ -7,6 +7,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(escape::BOXED_LOCAL),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(loops::MANUAL_MEMCPY),
@ -23,7 +24,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(misc::CMP_OWNED),
LintId::of(redundant_clone::REDUNDANT_CLONE),
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(types::BOX_COLLECTION),
LintId::of(types::REDUNDANT_ALLOCATION),
LintId::of(vec::USELESS_VEC),

View File

@ -16,6 +16,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
LintId::of(empty_drop::EMPTY_DROP),
LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS),
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
@ -26,6 +27,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(indexing_slicing::INDEXING_SLICING),
LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
LintId::of(integer_division::INTEGER_DIVISION),
LintId::of(large_include_file::LARGE_INCLUDE_FILE),
LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
LintId::of(map_err_ignore::MAP_ERR_IGNORE),
@ -53,6 +55,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(panic_unimplemented::UNIMPLEMENTED),
LintId::of(panic_unimplemented::UNREACHABLE),
LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH),
LintId::of(pub_use::PUB_USE),
LintId::of(redundant_slicing::DEREF_BY_SLICING),
LintId::of(same_name_method::SAME_NAME_METHOD),
LintId::of(shadow::SHADOW_REUSE),

View File

@ -61,6 +61,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(methods::CHARS_NEXT_CMP),
LintId::of(methods::ERR_EXPECT),
LintId::of(methods::INTO_ITER_ON_REF),
LintId::of(methods::IS_DIGIT_ASCII_RADIX),
LintId::of(methods::ITER_CLONED_COLLECT),
LintId::of(methods::ITER_NEXT_SLICE),
LintId::of(methods::ITER_NTH_ZERO),
@ -104,8 +105,11 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(returns::NEEDLESS_RETURN),
LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(strings::TRIM_SPLIT_WHITESPACE),
LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
LintId::of(unit_types::LET_UNIT_VALUE),
LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(unused_unit::UNUSED_UNIT),
LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),

View File

@ -5,6 +5,7 @@
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(casts::CAST_ABS_TO_UNSIGNED),

View File

@ -163,6 +163,8 @@ mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils;
mod renamed_lints;
// begin lints modules, do not remove this comment, its used in `update_lints`
mod absurd_extreme_comparisons;
mod approx_const;
@ -181,6 +183,7 @@ mod bool_assert_comparison;
mod booleans;
mod borrow_as_ptr;
mod bytecount;
mod bytes_count_to_len;
mod cargo;
mod case_sensitive_file_extension_comparisons;
mod casts;
@ -209,6 +212,7 @@ mod double_parens;
mod drop_forget_ref;
mod duration_subsec;
mod else_if_without_else;
mod empty_drop;
mod empty_enum;
mod empty_structs_with_brackets;
mod entry;
@ -231,6 +235,7 @@ mod floating_point_arithmetic;
mod format;
mod format_args;
mod format_impl;
mod format_push_string;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
@ -259,6 +264,7 @@ mod items_after_statements;
mod iter_not_returning_iterator;
mod large_const_arrays;
mod large_enum_variant;
mod large_include_file;
mod large_stack_arrays;
mod len_zero;
mod let_if_seq;
@ -336,6 +342,7 @@ mod precedence;
mod ptr;
mod ptr_eq;
mod ptr_offset_with_cast;
mod pub_use;
mod question_mark;
mod ranges;
mod redundant_clone;
@ -383,6 +390,7 @@ mod unit_hash;
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
mod unnecessary_sort_by;
mod unnecessary_wraps;
@ -499,7 +507,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
{
store.register_early_pass(|| Box::new(utils::internal_lints::ClippyLintsInternal));
store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce));
store.register_late_pass(|| Box::new(utils::inspector::DeepCodeInspector));
store.register_late_pass(|| Box::new(utils::internal_lints::CollapsibleCalls));
store.register_late_pass(|| Box::new(utils::internal_lints::CompilerLintFunctions::new()));
store.register_late_pass(|| Box::new(utils::internal_lints::IfChainStyle));
@ -511,8 +518,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
}
store.register_late_pass(|| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|| Box::new(utils::author::Author));
store.register_late_pass(|| Box::new(await_holding_invalid::AwaitHolding));
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;
@ -572,7 +585,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(approx_const::ApproxConstant::new(msrv)));
store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv)));
store.register_late_pass(move || Box::new(matches::Matches::new(msrv)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
store.register_late_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
store.register_late_pass(move || Box::new(manual_strip::ManualStrip::new(msrv)));
store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
@ -812,6 +826,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
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));
store.register_late_pass(|| Box::new(empty_drop::EmptyDrop));
store.register_late_pass(|| Box::new(strings::StrToString));
store.register_late_pass(|| Box::new(strings::StringToString));
store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues));
@ -868,6 +883,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
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));
store.register_late_pass(|| Box::new(bytes_count_to_len::BytesCountToLen));
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));
// add lints here, do not remove this comment, it's used in `new_lint`
}
@ -919,43 +941,9 @@ fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
///
/// Used in `./src/driver.rs`.
pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
// NOTE: when renaming a lint, add a corresponding test to tests/ui/rename.rs
ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions");
ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default");
ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity");
ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes");
ls.register_renamed("clippy::option_and_then_some", "clippy::bind_instead_of_map");
ls.register_renamed("clippy::box_vec", "clippy::box_collection");
ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions");
ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions");
ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or");
ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or");
ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or");
ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used");
ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used");
ls.register_renamed("clippy::option_expect_used", "clippy::expect_used");
ls.register_renamed("clippy::result_expect_used", "clippy::expect_used");
ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles");
ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles");
ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion");
ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters");
ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str");
ls.register_renamed("clippy::if_let_some_result", "clippy::match_result_ok");
ls.register_renamed("clippy::disallowed_type", "clippy::disallowed_types");
ls.register_renamed("clippy::disallowed_method", "clippy::disallowed_methods");
ls.register_renamed("clippy::ref_in_deref", "clippy::needless_borrow");
ls.register_renamed("clippy::to_string_in_display", "clippy::recursive_format_impl");
// uplifted lints
ls.register_renamed("clippy::invalid_ref", "invalid_value");
ls.register_renamed("clippy::into_iter_on_array", "array_into_iter");
ls.register_renamed("clippy::unused_label", "unused_labels");
ls.register_renamed("clippy::drop_bounds", "drop_bounds");
ls.register_renamed("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr");
ls.register_renamed("clippy::panic_params", "non_fmt_panics");
ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints");
ls.register_renamed("clippy::invalid_atomic_ordering", "invalid_atomic_ordering");
ls.register_renamed("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums");
for (old_name, new_name) in renamed_lints::RENAMED_LINTS {
ls.register_renamed(old_name, new_name);
}
}
// only exists to let the dogfood integration test works.

View File

@ -1,16 +1,19 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::trait_ref_of_method;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
use rustc_hir::intravisit::{
walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty, Visitor,
walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor,
};
use rustc_hir::FnRetTy::Return;
use rustc_hir::{
BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem,
BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem,
ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier,
TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter as middle_nested_filter;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, Ident, Symbol};
@ -82,8 +85,10 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
impl<'tcx> LateLintPass<'tcx> for Lifetimes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Fn(ref sig, ref generics, id) = item.kind {
if let ItemKind::Fn(ref sig, generics, id) = item.kind {
check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
} else if let ItemKind::Impl(impl_) = item.kind {
report_extra_impl_lifetimes(cx, impl_);
}
}
@ -95,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
sig.decl,
Some(id),
None,
&item.generics,
item.generics,
item.span,
report_extra_lifetimes,
);
@ -108,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
TraitFn::Required(sig) => (None, Some(sig)),
TraitFn::Provided(id) => (Some(id), None),
};
check_fn_inner(cx, sig.decl, body, trait_sig, &item.generics, item.span, true);
check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true);
}
}
}
@ -201,8 +206,7 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident:
visitor.visit_ty(self_ty);
!visitor.all_lts().is_empty()
}
else {
} else {
false
}
}
@ -486,11 +490,29 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
false
}
struct LifetimeChecker {
struct LifetimeChecker<'cx, 'tcx, F> {
cx: &'cx LateContext<'tcx>,
map: FxHashMap<Symbol, Span>,
phantom: std::marker::PhantomData<F>,
}
impl<'tcx> Visitor<'tcx> for LifetimeChecker {
impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> {
fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap<Symbol, Span>) -> LifetimeChecker<'cx, 'tcx, F> {
Self {
cx,
map,
phantom: std::marker::PhantomData,
}
}
}
impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
where
F: NestedFilter<'tcx>,
{
type Map = rustc_middle::hir::map::Map<'tcx>;
type NestedFilter = F;
// for lifetimes as parameters of generics
fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
self.map.remove(&lifetime.name.ident().name);
@ -506,6 +528,10 @@ impl<'tcx> Visitor<'tcx> for LifetimeChecker {
walk_generic_param(self, param);
}
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}
fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
@ -517,7 +543,7 @@ fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>,
_ => None,
})
.collect();
let mut checker = LifetimeChecker { map: hs };
let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, hs);
walk_generics(&mut checker, generics);
walk_fn_decl(&mut checker, func);
@ -532,6 +558,32 @@ fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>,
}
}
fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) {
let hs = impl_
.generics
.params
.iter()
.filter_map(|par| match par.kind {
GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)),
_ => None,
})
.collect();
let mut checker = LifetimeChecker::<middle_nested_filter::All>::new(cx, hs);
walk_generics(&mut checker, impl_.generics);
if let Some(ref trait_ref) = impl_.of_trait {
walk_trait_ref(&mut checker, trait_ref);
}
walk_ty(&mut checker, impl_.self_ty);
for item in impl_.items {
walk_impl_item_ref(&mut checker, item);
}
for &v in checker.map.values() {
span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl");
}
}
struct BodyLifetimeChecker {
lifetimes_used_in_body: bool,
}

View File

@ -42,17 +42,12 @@ declare_clippy_lint! {
/// This is most probably a typo
///
/// ### Known problems
/// - Recommends a signed suffix, even though the number might be too big and an unsigned
/// suffix is required
/// - Does not match on integers too large to fit in the corresponding unsigned type
/// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
///
/// ### Example
/// ```rust
/// // Probably mistyped
/// 2_32;
///
/// // Good
/// 2_i32;
/// `2_32` => `2_i32`
/// `250_8 => `250_u8`
/// ```
#[clippy::version = "1.30.0"]
pub MISTYPED_LITERAL_SUFFIXES,
@ -310,18 +305,47 @@ impl LiteralDigitGrouping {
return true;
}
let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
(exponent, &["32", "64"][..], 'f')
let (part, mistyped_suffixes, is_float) = if let Some((_, exponent)) = &mut num_lit.exponent {
(exponent, &["32", "64"][..], true)
} else if num_lit.fraction.is_some() {
(&mut num_lit.integer, &["32", "64"][..], 'f')
return true;
} else {
(&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
(&mut num_lit.integer, &["8", "16", "32", "64"][..], false)
};
let mut split = part.rsplit('_');
let last_group = split.next().expect("At least one group");
if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
*part = &part[..part.len() - last_group.len()];
let main_part = &part[..part.len() - last_group.len()];
let missing_char;
if is_float {
missing_char = 'f';
} else {
let radix = match num_lit.radix {
Radix::Binary => 2,
Radix::Octal => 8,
Radix::Decimal => 10,
Radix::Hexadecimal => 16,
};
if let Ok(int) = u64::from_str_radix(&main_part.replace('_', ""), radix) {
missing_char = match (last_group, int) {
("8", i) if i8::try_from(i).is_ok() => 'i',
("16", i) if i16::try_from(i).is_ok() => 'i',
("32", i) if i32::try_from(i).is_ok() => 'i',
("64", i) if i64::try_from(i).is_ok() => 'i',
("8", u) if u8::try_from(u).is_ok() => 'u',
("16", u) if u16::try_from(u).is_ok() => 'u',
("32", u) if u32::try_from(u).is_ok() => 'u',
("64", _) => 'u',
_ => {
return true;
},
}
} else {
return true;
}
}
*part = main_part;
let mut sugg = num_lit.format();
sugg.push('_');
sugg.push(missing_char);

View File

@ -168,8 +168,9 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
.operands
.iter()
.map(|(o, _)| match o {
InlineAsmOperand::In { expr, .. }
| InlineAsmOperand::InOut { expr, .. } => never_loop_expr(expr, main_loop_id),
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
never_loop_expr(expr, main_loop_id)
},
InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id),
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id)

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_parent_expr, meets_msrv, msrvs};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
@ -24,7 +24,7 @@ declare_clippy_lint! {
/// ```
/// Use instead:
/// ```rust
/// usize::BITS;
/// usize::BITS as usize;
/// ```
#[clippy::version = "1.60.0"]
pub MANUAL_BITS,
@ -59,16 +59,19 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
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_applicability(cx, real_ty.span, "..", &mut app);
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",
format!("{}::BITS", snippet_opt(cx, real_ty.span).unwrap()),
Applicability::MachineApplicable,
sugg,
app,
);
}
}
@ -108,3 +111,36 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<
}
}
}
fn create_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, base_sugg: String) -> String {
if let Some(parent_expr) = get_parent_expr(cx, expr) {
if is_ty_conversion(parent_expr) {
return base_sugg;
}
// These expressions have precedence over casts, the suggestion therefore
// needs to be wrapped into parentheses
match parent_expr.kind {
ExprKind::Unary(..) | ExprKind::AddrOf(..) | ExprKind::MethodCall(..) => {
return format!("({base_sugg} as usize)");
},
_ => {},
}
}
format!("{base_sugg} as usize")
}
fn is_ty_conversion(expr: &Expr<'_>) -> bool {
if let ExprKind::Cast(..) = expr.kind {
true
} else if let ExprKind::MethodCall(path, [_], _) = expr.kind
&& path.ident.name == rustc_span::sym::try_into
{
// This is only called for `usize` which implements `TryInto`. Therefore,
// we don't have to check here if `self` implements the `TryInto` trait.
true
} else {
false
}
}

View File

@ -1,13 +1,17 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::{meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind};
use clippy_utils::{is_lint_allowed, meets_msrv, msrvs};
use rustc_ast::ast::{self, VisibilityKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::ty::DefIdTree;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::{sym, Span};
declare_clippy_lint! {
@ -58,129 +62,160 @@ declare_clippy_lint! {
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
}
#[derive(Clone)]
pub struct ManualNonExhaustive {
#[allow(clippy::module_name_repetitions)]
pub struct ManualNonExhaustiveStruct {
msrv: Option<RustcVersion>,
}
impl ManualNonExhaustive {
impl ManualNonExhaustiveStruct {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
impl EarlyLintPass for ManualNonExhaustive {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
#[allow(clippy::module_name_repetitions)]
pub struct ManualNonExhaustiveEnum {
msrv: Option<RustcVersion>,
constructed_enum_variants: FxHashSet<(DefId, DefId)>,
potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
}
impl ManualNonExhaustiveEnum {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self {
msrv,
constructed_enum_variants: FxHashSet::default(),
potential_enums: Vec::new(),
}
}
}
impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
impl EarlyLintPass for ManualNonExhaustiveStruct {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
return;
}
match &item.kind {
ItemKind::Enum(def, _) => {
check_manual_non_exhaustive_enum(cx, item, &def.variants);
},
ItemKind::Struct(variant_data, _) => {
if let VariantData::Unit(..) = variant_data {
return;
}
check_manual_non_exhaustive_struct(cx, item, variant_data);
},
_ => {},
if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
let (fields, delimiter) = match variant_data {
ast::VariantData::Struct(fields, _) => (&**fields, '{'),
ast::VariantData::Tuple(fields, _) => (&**fields, '('),
ast::VariantData::Unit(_) => return,
};
if fields.len() <= 1 {
return;
}
let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
VisibilityKind::Public => None,
VisibilityKind::Inherited => Some(Ok(f)),
_ => Some(Err(())),
});
if let Some(Ok(field)) = iter.next()
&& iter.next().is_none()
&& field.ty.kind.is_unit()
&& field.ident.map_or(true, |name| name.as_str().starts_with('_'))
{
span_lint_and_then(
cx,
MANUAL_NON_EXHAUSTIVE,
item.span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {
if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
&& let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
&& let Some(snippet) = snippet_opt(cx, header_span)
{
diag.span_suggestion(
header_span,
"add the attribute",
format!("#[non_exhaustive] {}", snippet),
Applicability::Unspecified,
);
}
diag.span_help(field.span, "remove this field");
}
);
}
}
}
extract_msrv_attr!(EarlyContext);
}
fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
fn is_non_exhaustive_marker(variant: &Variant) -> bool {
matches!(variant.data, VariantData::Unit(_))
&& variant.ident.as_str().starts_with('_')
&& is_doc_hidden(&variant.attrs)
impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
return;
}
if let hir::ItemKind::Enum(def, _) = &item.kind
&& def.variants.len() > 1
{
let mut iter = def.variants.iter().filter_map(|v| {
let id = cx.tcx.hir().local_def_id(v.id);
(matches!(v.data, hir::VariantData::Unit(_))
&& v.ident.as_str().starts_with('_')
&& is_doc_hidden(cx.tcx.get_attrs(id.to_def_id())))
.then(|| (id, v.span))
});
if let Some((id, span)) = iter.next()
&& iter.next().is_none()
{
self.potential_enums.push((item.def_id, id, item.span, span));
}
}
}
let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
if_chain! {
if let Some(marker) = markers.next();
if markers.count() == 0 && variants.len() > 1;
then {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
&& let [.., name] = p.segments
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
&& name.ident.as_str().starts_with('_')
{
let variant_id = cx.tcx.parent(id);
let enum_id = cx.tcx.parent(variant_id);
self.constructed_enum_variants.insert((enum_id, variant_id));
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for &(enum_id, _, enum_span, variant_span) in
self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
!self
.constructed_enum_variants
.contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
&& !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
})
{
span_lint_and_then(
cx,
MANUAL_NON_EXHAUSTIVE,
item.span,
enum_span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {
if_chain! {
if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
let header_span = cx.sess().source_map().span_until_char(item.span, '{');
if let Some(snippet) = snippet_opt(cx, header_span);
then {
if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
&& let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
&& let Some(snippet) = snippet_opt(cx, header_span)
{
diag.span_suggestion(
header_span,
"add the attribute",
format!("#[non_exhaustive] {}", snippet),
Applicability::Unspecified,
);
}
}
diag.span_help(marker.span, "remove this variant");
});
}
}
}
fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
fn is_private(field: &FieldDef) -> bool {
matches!(field.vis.kind, VisibilityKind::Inherited)
}
fn is_non_exhaustive_marker(field: &FieldDef) -> bool {
is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
}
fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
let delimiter = match data {
VariantData::Struct(..) => '{',
VariantData::Tuple(..) => '(',
VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
};
cx.sess().source_map().span_until_char(item.span, delimiter)
}
let fields = data.fields();
let private_fields = fields.iter().filter(|f| is_private(f)).count();
let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count();
if_chain! {
if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
then {
span_lint_and_then(
cx,
MANUAL_NON_EXHAUSTIVE,
item.span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {
if_chain! {
if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
let header_span = find_header_span(cx, item, data);
if let Some(snippet) = snippet_opt(cx, header_span);
then {
diag.span_suggestion(
header_span,
"add the attribute",
format!("#[non_exhaustive] {}", snippet),
Applicability::Unspecified,
);
}
}
diag.span_help(marker.span, "remove this field");
});
diag.span_help(variant_span, "remove this variant");
},
);
}
}
extract_msrv_attr!(LateContext);
}

View File

@ -143,15 +143,11 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
impl MapClone {
fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) {
let mut applicability = Applicability::MachineApplicable;
let message = if is_copy {
"you are using an explicit closure for copying elements"
let (message, sugg_method) = if is_copy && meets_msrv(self.msrv.as_ref(), &msrvs::ITERATOR_COPIED) {
("you are using an explicit closure for copying elements", "copied")
} else {
"you are using an explicit closure for cloning elements"
};
let sugg_method = if is_copy && meets_msrv(self.msrv.as_ref(), &msrvs::ITERATOR_COPIED) {
"copied"
} else {
"cloned"
("you are using an explicit closure for cloning elements", "cloned")
};
span_lint_and_sugg(

View File

@ -24,7 +24,7 @@ declare_clippy_lint! {
/// vec.push(value)
/// }
///
/// if let Some(valie) = iter.next().ok() {
/// if let Some(value) = iter.next().ok() {
/// vec.push(value)
/// }
/// ```
@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
if_chain! {
if let ExprKind::MethodCall(ok_path, [ref result_types_0, ..], _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() method use std::marker::Sized;
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result);
if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";

View File

@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
.map(|a| NormalizedPat::from_pat(cx, &arena, a.pat))
.collect();
// The furthast forwards a pattern can move without semantic changes
// The furthest forwards a pattern can move without semantic changes
let forwards_blocking_idxs: Vec<_> = normalized_pats
.iter()
.enumerate()
@ -43,7 +43,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
})
.collect();
// The furthast backwards a pattern can move without semantic changes
// The furthest backwards a pattern can move without semantic changes
let backwards_blocking_idxs: Vec<_> = normalized_pats
.iter()
.enumerate()

View File

@ -1,4 +1,4 @@
use clippy_utils::source::{snippet_opt, walk_span_to_context};
use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
use clippy_utils::{meets_msrv, msrvs};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::{tokenize, TokenKind};
@ -7,7 +7,7 @@ use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, SpanData, SyntaxContext};
mod infalliable_detructuring_match;
mod infallible_destructuring_match;
mod match_as_ref;
mod match_bool;
mod match_like_matches;
@ -653,6 +653,9 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
if let ExprKind::Match(ex, arms, source) = expr.kind {
if !span_starts_with(cx, expr.span, "match") {
return;
}
if !contains_cfg_arm(cx, expr, ex, arms) {
if source == MatchSource::Normal {
if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO)
@ -691,7 +694,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local);
self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local);
}
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {

View File

@ -83,7 +83,7 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
return false;
}
// Recurrsively check for each `else if let` phrase,
// Recursively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
return check_if_let(cx, nested_if_let);
}
@ -99,7 +99,7 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
}
return true;
return false;
}
return eq_expr_value(cx, if_let.let_expr, ret);
}
@ -118,7 +118,7 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
}
/// Manually check for coercion casting by checking if the type of the match operand or let expr
/// differs with the assigned local variable or the funtion return type.
/// differs with the assigned local variable or the function return type.
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
match p_node {

View File

@ -2,17 +2,16 @@ use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::{higher, match_def_path};
use clippy_utils::{is_lang_ctor, is_trait_method, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, PollPending};
use rustc_hir::{
intravisit::{walk_expr, Visitor},
Arm, Block, Expr, ExprKind, LangItem, Node, Pat, PatKind, QPath, UnOp,
Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
@ -32,59 +31,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
}
}
/// Checks if the drop order for a type matters. Some std types implement drop solely to
/// deallocate memory. For these types, and composites containing them, changing the drop order
/// won't result in any observable side effects.
fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
}
fn type_needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
if !seen.insert(ty) {
return false;
}
if !ty.needs_drop(cx.tcx, cx.param_env) {
false
} else if !cx
.tcx
.lang_items()
.drop_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
// This type doesn't implement drop, so no side effects here.
// Check if any component type has any.
match ty.kind() {
ty::Tuple(fields) => fields.iter().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, *ty, seen),
ty::Adt(adt, subs) => adt
.all_fields()
.map(|f| f.ty(cx.tcx, subs))
.any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
_ => true,
}
}
// Check for std types which implement drop, but only for memory allocation.
else if is_type_diagnostic_item(cx, ty, sym::Vec)
|| is_type_lang_item(cx, ty, LangItem::OwnedBox)
|| is_type_diagnostic_item(cx, ty, sym::Rc)
|| is_type_diagnostic_item(cx, ty, sym::Arc)
|| is_type_diagnostic_item(cx, ty, sym::cstring_type)
|| is_type_diagnostic_item(cx, ty, sym::BTreeMap)
|| is_type_diagnostic_item(cx, ty, sym::LinkedList)
|| match_type(cx, ty, &paths::WEAK_RC)
|| match_type(cx, ty, &paths::WEAK_ARC)
{
// Check all of the generic arguments.
if let ty::Adt(_, subs) = ty.kind() {
subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
} else {
true
}
} else {
true
}
}
// Extract the generic arguments out of a type
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
if_chain! {
@ -115,7 +61,7 @@ fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<
// e.g. In `(String::new(), 0).1` the string is a temporary value.
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
if !matches!(expr.kind, ExprKind::Path(_)) {
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
self.res = true;
} else {
self.visit_expr(expr);
@ -126,7 +72,7 @@ fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<
// e.g. In `(vec![0])[0]` the vector is a temporary value.
ExprKind::Index(base, index) => {
if !matches!(base.kind, ExprKind::Path(_)) {
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
self.res = true;
} else {
self.visit_expr(base);
@ -143,7 +89,7 @@ fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
if self_by_ref && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
self.res = true;
} else {
self.visit_expr(self_arg);
@ -243,7 +189,7 @@ fn find_sugg_for_if_let<'tcx>(
// scrutinee would be, so they have to be considered as well.
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
// for the duration if body.
let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
// check that `while_let_on_iterator` lint does not trigger
if_chain! {

View File

@ -14,6 +14,7 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
if let ty::Adt(def, _) = ty.kind();
if def.is_struct() || def.is_union();
if fields.len() == def.non_enum_variant().fields.len();
if !def.non_enum_variant().is_field_list_non_exhaustive();
then {
span_lint_and_help(

View File

@ -0,0 +1,50 @@
//! Lint for `c.is_digit(10)`
use super::IS_DIGIT_ASCII_RADIX;
use clippy_utils::{
consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
source::snippet_with_applicability,
};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_semver::RustcVersion;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
self_arg: &'tcx Expr<'_>,
radix: &'tcx Expr<'_>,
msrv: Option<&RustcVersion>,
) {
if !meets_msrv(msrv, &msrvs::IS_ASCII_DIGIT) {
return;
}
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
return;
}
if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) {
let (num, replacement) = match radix_val {
FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
IS_DIGIT_ASCII_RADIX,
expr.span,
&format!("use of `char::is_digit` with literal radix of {}", num),
"try",
format!(
"{}.{}()",
snippet_with_applicability(cx, self_arg.span, "..", &mut applicability),
replacement
),
applicability,
);
}
}

View File

@ -1,72 +1,47 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::Range;
use clippy_utils::is_integer_const;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
higher::{self, Range},
SpanlessEq,
};
use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::symbol::sym;
use rustc_span::Span;
use super::ITER_WITH_DRAIN;
const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
// Refuse to emit `into_iter` suggestion on draining struct fields due
// to the strong possibility of processing unmovable field.
if let ExprKind::Field(..) = recv.kind {
return;
}
if let Some(range) = higher::Range::hir(arg) {
let left_full = match range {
Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
Range { start: None, .. } => true,
_ => false,
};
let full = left_full
&& match range {
Range {
end: Some(end),
limits: RangeLimits::HalfOpen,
..
} => {
// `x.drain(..x.len())` call
if_chain! {
if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
if len_path.ident.name == sym::len && len_args.len() == 1;
if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
if SpanlessEq::new(cx).eq_path(drain_path, len_path);
then { true }
else { false }
}
},
Range {
end: None,
limits: RangeLimits::HalfOpen,
..
} => true,
_ => false,
};
if full {
span_lint_and_sugg(
cx,
ITER_WITH_DRAIN,
span.with_hi(expr.span.hi()),
&format!("`drain(..)` used on a `{}`", drained_type),
"try this",
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
if !matches!(recv.kind, ExprKind::Field(..))
&& let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
&& matches!(ty_name, sym::Vec | sym::VecDeque)
&& let Some(range) = Range::hir(arg)
&& is_full_range(cx, recv, range)
{
span_lint_and_sugg(
cx,
ITER_WITH_DRAIN,
span.with_hi(expr.span.hi()),
&format!("`drain(..)` used on a `{}`", ty_name),
"try this",
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
};
}
fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
range.start.map_or(true, |e| is_integer_const(cx, e, 0))
&& range.end.map_or(true, |e| {
if range.limits == RangeLimits::HalfOpen
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
&& let ExprKind::MethodCall(name, [self_arg], _) = e.kind
&& name.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
{
container_path.res == path.res
} else {
false
}
})
}

View File

@ -26,6 +26,7 @@ mod implicit_clone;
mod inefficient_to_string;
mod inspect_for_each;
mod into_iter_on_ref;
mod is_digit_ascii_radix;
mod iter_cloned_collect;
mod iter_count;
mod iter_next_slice;
@ -42,6 +43,7 @@ mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod needless_option_as_deref;
mod needless_option_take;
mod ok_expect;
mod option_as_ref_deref;
mod option_map_or_none;
@ -294,15 +296,15 @@ declare_clippy_lint! {
/// Checks for methods with certain name prefixes and which
/// doesn't match how self is taken. The actual rules are:
///
/// |Prefix |Postfix |`self` taken | `self` type |
/// |-------|------------|-----------------------|--------------|
/// |`as_` | none |`&self` or `&mut self` | any |
/// |`from_`| none | none | any |
/// |`into_`| none |`self` | any |
/// |`is_` | none |`&self` or none | any |
/// |`to_` | `_mut` |`&mut self` | any |
/// |`to_` | not `_mut` |`self` | `Copy` |
/// |`to_` | not `_mut` |`&self` | not `Copy` |
/// |Prefix |Postfix |`self` taken | `self` type |
/// |-------|------------|-------------------------------|--------------|
/// |`as_` | none |`&self` or `&mut self` | any |
/// |`from_`| none | none | any |
/// |`into_`| none |`self` | any |
/// |`is_` | none |`&mut self` or `&self` or none | any |
/// |`to_` | `_mut` |`&mut self` | any |
/// |`to_` | not `_mut` |`self` | `Copy` |
/// |`to_` | not `_mut` |`&self` | not `Copy` |
///
/// Note: Clippy doesn't trigger methods with `to_` prefix in:
/// - Traits definition.
@ -1265,7 +1267,7 @@ declare_clippy_lint! {
#[clippy::version = "1.55.0"]
pub EXTEND_WITH_DRAIN,
perf,
"using vec.append(&mut vec) to move the full range of a vecor to another"
"using vec.append(&mut vec) to move the full range of a vector to another"
}
declare_clippy_lint! {
@ -2007,13 +2009,27 @@ declare_clippy_lint! {
/// ### Example
/// ```rust,ignore
/// // Bad
/// let (key, value) = _.splitn(2, '=').next_tuple()?;
/// let value = _.splitn(2, '=').nth(1)?;
/// let s = "key=value=add";
/// let (key, value) = s.splitn(2, '=').next_tuple()?;
/// let value = s.splitn(2, '=').nth(1)?;
///
/// // Good
/// let (key, value) = _.split_once('=')?;
/// let value = _.split_once('=')?.1;
/// let mut parts = s.splitn(2, '=');
/// let key = parts.next()?;
/// let value = parts.next()?;
/// ```
/// Use instead:
/// ```rust,ignore
/// // Good
/// let s = "key=value=add";
/// let (key, value) = s.split_once('=')?;
/// let value = s.split_once('=')?.1;
///
/// let (key, value) = s.split_once('=')?;
/// ```
///
/// ### Limitations
/// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()`
/// in two separate `let` statements that immediately follow the `splitn()`
#[clippy::version = "1.57.0"]
pub MANUAL_SPLIT_ONCE,
complexity,
@ -2099,7 +2115,7 @@ declare_clippy_lint! {
/// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
/// will prevent loop unrolling and will result in a negative performance impact.
///
/// Additionlly, differences have been observed between aarch64 and x86_64 assembly output,
/// Additionally, differences have been observed between aarch64 and x86_64 assembly output,
/// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
#[clippy::version = "1.61.0"]
pub UNNECESSARY_JOIN,
@ -2131,6 +2147,56 @@ declare_clippy_lint! {
"no-op use of `deref` or `deref_mut` method to `Option`."
}
declare_clippy_lint! {
/// ### What it does
/// Finds usages of [`char::is_digit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
/// can be replaced with [`is_ascii_digit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
/// [`is_ascii_hexdigit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
///
/// ### Why is this bad?
/// `is_digit(..)` is slower and requires specifying the radix.
///
/// ### Example
/// ```rust
/// let c: char = '6';
/// c.is_digit(10);
/// c.is_digit(16);
/// ```
/// Use instead:
/// ```rust
/// let c: char = '6';
/// c.is_ascii_digit();
/// c.is_ascii_hexdigit();
/// ```
#[clippy::version = "1.61.0"]
pub IS_DIGIT_ASCII_RADIX,
style,
"use of `char::is_digit(..)` with literal radix of 10 or 16"
}
declare_clippy_lint! {
///
/// ### Why is this bad?
///
/// ### Example
/// ```rust
/// let x = Some(3);
/// x.as_ref().take();
/// ```
/// Use instead:
/// ```rust
/// let x = Some(3);
/// x.as_ref();
/// ```
#[clippy::version = "1.61.0"]
pub NEEDLESS_OPTION_TAKE,
complexity,
"using `.as_ref().take()` on a temporary value"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
@ -2219,6 +2285,8 @@ impl_lint_pass!(Methods => [
UNNECESSARY_JOIN,
ERR_EXPECT,
NEEDLESS_OPTION_AS_DEREF,
IS_DIGIT_ASCII_RADIX,
NEEDLESS_OPTION_TAKE,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -2254,7 +2322,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
single_char_add_str::check(cx, expr, args);
into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, args);
single_char_pattern::check(cx, expr, method_call.ident.name, args);
unnecessary_to_owned::check(cx, expr, method_call.ident.name, args);
unnecessary_to_owned::check(cx, expr, method_call.ident.name, args, self.msrv.as_ref());
},
hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
let mut info = BinaryExprInfo {
@ -2516,6 +2584,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
},
("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
("join", [join_arg]) => {
@ -2574,12 +2643,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg);
}
if count >= 2 {
str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count);
}
str_splitn::check(cx, name, expr, recv, pat_arg, count, msrv);
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
@ -2595,6 +2659,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
}
}
},
("take", []) => needless_option_take::check(cx, expr, recv),
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
implicit_clone::check(cx, name, expr, recv);
},

View File

@ -0,0 +1,41 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::match_def_path;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::NEEDLESS_OPTION_TAKE;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
// Checks if expression type is equal to sym::Option and if the expr is not a syntactic place
if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) && has_expr_as_ref_path(cx, recv) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_TAKE,
expr.span,
"called `Option::take()` on a temporary value",
"try",
format!(
"{}",
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
),
applicability,
);
}
}
fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_type = cx.typeck_results().expr_ty(expr);
is_type_diagnostic_item(cx, expr_type, sym::Option)
}
fn has_expr_as_ref_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(ref_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
return match_def_path(cx, ref_id, &["core", "option", "Option", "as_ref"]);
}
false
}

View File

@ -1,36 +1,83 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_context;
use clippy_utils::{is_diag_item_method, match_def_path, paths};
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::expr_visitor;
use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{
BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, adjustment::Adjust};
use rustc_span::{symbol::sym, Span, SyntaxContext};
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_span::{sym, Span, Symbol, SyntaxContext};
use super::MANUAL_SPLIT_ONCE;
use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
pub(super) fn check_manual_split_once(
pub(super) fn check(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
count: u128,
msrv: Option<&RustcVersion>,
) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
let ctxt = expr.span.ctxt();
let (method_name, msg, reverse) = if method_name == "splitn" {
("split_once", "manual implementation of `split_once`", false)
} else {
("rsplit_once", "manual implementation of `rsplit_once`", true)
let needless = |usage_kind| match usage_kind {
IterUsageKind::Nth(n) => count > n + 1,
IterUsageKind::NextTuple => count > 2,
};
let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
Some(x) => x,
None => return,
let manual = count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE);
match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage),
None if manual => {
check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
},
_ => {},
}
}
fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
let r = if method_name == "splitn" { "" } else { "r" };
span_lint_and_sugg(
cx,
NEEDLESS_SPLITN,
expr.span,
&format!("unnecessary use of `{r}splitn`"),
"try this",
format!(
"{}.{r}split({})",
snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0,
snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0,
),
app,
);
}
fn check_manual_split_once(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
usage: &IterUsage,
) {
let ctxt = expr.span.ctxt();
let (msg, reverse) = if method_name == "splitn" {
("manual implementation of `split_once`", false)
} else {
("manual implementation of `rsplit_once`", true)
};
let mut app = Applicability::MachineApplicable;
@ -39,84 +86,198 @@ pub(super) fn check_manual_split_once(
let sugg = match usage.kind {
IterUsageKind::NextTuple => {
format!("{}.{}({})", self_snip, method_name, pat_snip)
},
IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
IterUsageKind::Next | IterUsageKind::Second => {
let self_deref = {
let adjust = cx.typeck_results().expr_adjustments(self_arg);
if adjust.len() < 2 {
String::new()
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|| adjust
.iter()
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
{
format!("&{}", "*".repeat(adjust.len().saturating_sub(1)))
} else {
"*".repeat(adjust.len().saturating_sub(2))
}
};
if matches!(usage.kind, IterUsageKind::Next) {
match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => {
if reverse {
format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
} else {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
self_snip, method_name, pat_snip, self_deref, &self_snip
)
}
},
Some(UnwrapKind::QuestionMark) => {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
self_snip, method_name, pat_snip, self_deref, &self_snip
)
},
None => {
format!(
"Some({}.{}({}).map_or({}{}, |x| x.0))",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
},
}
if reverse {
format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
} else {
match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => {
if reverse {
// In this case, no better suggestion is offered.
return;
}
format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
},
Some(UnwrapKind::QuestionMark) => {
format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
},
None => {
format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
},
}
format!("{self_snip}.split_once({pat_snip})")
}
},
IterUsageKind::Nth(1) => {
let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => {
format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
},
Some(UnwrapKind::QuestionMark) => {
format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
},
None => {
format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
},
}
},
IterUsageKind::Nth(_) => return,
};
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
}
enum IterUsageKind {
Next,
Second,
NextTuple,
RNextTuple,
/// checks for
///
/// ```
/// let mut iter = "a.b.c".splitn(2, '.');
/// let a = iter.next();
/// let b = iter.next();
/// ```
fn check_manual_split_once_indirect(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
) -> Option<()> {
let ctxt = expr.span.ctxt();
let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
if let (_, Node::Local(local)) = parents.next()?
&& let PatKind::Binding(BindingAnnotation::Mutable, iter_binding_id, iter_ident, None) = local.pat.kind
&& let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
&& let (_, Node::Block(enclosing_block)) = parents.next()?
&& let mut stmts = enclosing_block
.stmts
.iter()
.skip_while(|stmt| stmt.hir_id != iter_stmt_id)
.skip(1)
&& let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
&& let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
&& first.unwrap_kind == second.unwrap_kind
&& first.name != second.name
&& !local_used_after_expr(cx, iter_binding_id, second.init_expr)
{
let (r, lhs, rhs) = if method_name == "splitn" {
("", first.name, second.name)
} else {
("r", second.name, first.name)
};
let msg = format!("manual implementation of `{r}split_once`");
let mut app = Applicability::MachineApplicable;
let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| {
diag.span_label(first.span, "first usage here");
diag.span_label(second.span, "second usage here");
let unwrap = match first.unwrap_kind {
UnwrapKind::Unwrap => ".unwrap()",
UnwrapKind::QuestionMark => "?",
};
diag.span_suggestion_verbose(
local.span,
&format!("try `{r}split_once`"),
format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
app,
);
let remove_msg = format!("remove the `{iter_ident}` usages");
diag.span_suggestion(
first.span,
&remove_msg,
String::new(),
app,
);
diag.span_suggestion(
second.span,
&remove_msg,
String::new(),
app,
);
});
}
Some(())
}
#[derive(Debug)]
struct IndirectUsage<'a> {
name: Symbol,
span: Span,
init_expr: &'a Expr<'a>,
unwrap_kind: UnwrapKind,
}
/// returns `Some(IndirectUsage)` for e.g.
///
/// ```ignore
/// let name = binding.next()?;
/// let name = binding.next().unwrap();
/// ```
fn indirect_usage<'tcx>(
cx: &LateContext<'tcx>,
stmt: &Stmt<'tcx>,
binding: HirId,
ctxt: SyntaxContext,
) -> Option<IndirectUsage<'tcx>> {
if let StmtKind::Local(Local {
pat:
Pat {
kind: PatKind::Binding(BindingAnnotation::Unannotated, _, ident, None),
..
},
init: Some(init_expr),
hir_id: local_hir_id,
..
}) = stmt.kind
{
let mut path_to_binding = None;
expr_visitor(cx, |expr| {
if path_to_local_id(expr, binding) {
path_to_binding = Some(expr);
}
path_to_binding.is_none()
})
.visit_expr(init_expr);
let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
let (parent_id, _) = parents.find(|(_, node)| {
!matches!(
node,
Node::Expr(Expr {
kind: ExprKind::Match(.., MatchSource::TryDesugar),
..
})
)
})?;
if let IterUsage {
kind: IterUsageKind::Nth(0),
unwrap_kind: Some(unwrap_kind),
..
} = iter_usage
{
if parent_id == *local_hir_id {
return Some(IndirectUsage {
name: ident.name,
span: stmt.span,
init_expr,
unwrap_kind,
});
}
}
}
None
}
#[derive(Debug, Clone, Copy)]
enum IterUsageKind {
Nth(u128),
NextTuple,
}
#[derive(Debug, PartialEq)]
enum UnwrapKind {
Unwrap,
QuestionMark,
}
#[derive(Debug)]
struct IterUsage {
kind: IterUsageKind,
unwrap_kind: Option<UnwrapKind>,
@ -128,7 +289,6 @@ fn parse_iter_usage<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
reverse: bool,
) -> Option<IterUsage> {
let (kind, span) = match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
@ -141,13 +301,7 @@ fn parse_iter_usage<'tcx>(
let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
match (name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if reverse {
(IterUsageKind::Second, e.span)
} else {
(IterUsageKind::Next, e.span)
}
},
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
("next_tuple", []) => {
return if_chain! {
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
@ -157,7 +311,7 @@ fn parse_iter_usage<'tcx>(
if subs.len() == 2;
then {
Some(IterUsage {
kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
kind: IterUsageKind::NextTuple,
span: e.span,
unwrap_kind: None
})
@ -185,11 +339,7 @@ fn parse_iter_usage<'tcx>(
}
}
};
match if reverse { idx ^ 1 } else { idx } {
0 => (IterUsageKind::Next, span),
1 => (IterUsageKind::Second, span),
_ => return None,
}
(IterUsageKind::Nth(idx), span)
} else {
return None;
}
@ -238,86 +388,3 @@ fn parse_iter_usage<'tcx>(
span,
})
}
use super::NEEDLESS_SPLITN;
pub(super) fn check_needless_splitn(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
count: u128,
) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
let ctxt = expr.span.ctxt();
let mut app = Applicability::MachineApplicable;
let (reverse, message) = if method_name == "splitn" {
(false, "unnecessary use of `splitn`")
} else {
(true, "unnecessary use of `rsplitn`")
};
if_chain! {
if count >= 2;
if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
then {
span_lint_and_sugg(
cx,
NEEDLESS_SPLITN,
expr.span,
message,
"try this",
format!(
"{}.{}({})",
snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
if reverse {"rsplit"} else {"split"},
snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0
),
app,
);
}
}
}
fn check_iter<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
count: u128,
) -> bool {
match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
let (name, args) = if let ExprKind::MethodCall(name, [_, args @ ..], _) = e.kind {
(name, args)
} else {
return false;
};
if_chain! {
if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
then {
match (name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
return true;
},
("next_tuple", []) if count > 2 => {
return true;
},
("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
if count > idx + 1 {
return true;
}
}
},
_ => return false,
}
}
}
},
_ => return false,
};
false
}

View File

@ -5,6 +5,8 @@ use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{
contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
};
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
use rustc_errors::Applicability;
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
@ -13,12 +15,19 @@ use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
use rustc_semver::RustcVersion;
use rustc_span::{sym, Symbol};
use std::cmp::max;
use super::UNNECESSARY_TO_OWNED;
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
pub fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
method_name: Symbol,
args: &'tcx [Expr<'tcx>],
msrv: Option<&RustcVersion>,
) {
if_chain! {
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let [receiver] = args;
@ -33,7 +42,7 @@ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name:
if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
return;
}
if check_into_iter_call_arg(cx, expr, method_name, receiver) {
if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver);
@ -178,7 +187,13 @@ fn check_addr_of_expr(
/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
/// call of a `to_owned`-like function is unnecessary.
fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
fn check_into_iter_call_arg(
cx: &LateContext<'_>,
expr: &Expr<'_>,
method_name: Symbol,
receiver: &Expr<'_>,
msrv: Option<&RustcVersion>,
) -> bool {
if_chain! {
if let Some(parent) = get_parent_expr(cx, expr);
if let Some(callee_def_id) = fn_def_id(cx, parent);
@ -192,7 +207,7 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name:
if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
return true;
}
let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" };
let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, &msrvs::ITERATOR_COPIED) { "copied" } else { "cloned" };
// The next suggestion may be incorrect because the removal of the `to_owned`-like
// function could cause the iterator to hold a reference to a resource that is used
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.

View File

@ -14,7 +14,7 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
(&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
(&[Convention::StartsWith("from_")], &[SelfKind::No]),
(&[Convention::StartsWith("into_")], &[SelfKind::Value]),
(&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]),
(&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]),
(&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
(&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),

View File

@ -361,7 +361,7 @@ impl MiscEarlyLints {
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
let lit_snip = match snippet_opt(cx, lit.span) {
Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
Some(snip) if snip.chars().next().map_or(false, |c| c.is_ascii_digit()) => snip,
_ => return,
};

View File

@ -44,7 +44,7 @@ declare_clippy_lint! {
/// pub struct PubBaz;
/// impl PubBaz {
/// fn private() {} // ok
/// pub fn not_ptrivate() {} // missing #[inline]
/// pub fn not_private() {} // missing #[inline]
/// }
///
/// impl Bar for PubBaz {
@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
let attrs = cx.tcx.hir().attrs(it.hir_id());
check_missing_inline_attrs(cx, attrs, it.span, desc);
},
hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, _bounds, trait_items) => {
hir::ItemKind::Trait(ref _is_auto, ref _unsafe, _generics, _bounds, trait_items) => {
// note: we need to check if the trait is exported so we can't use
// `LateLintPass::check_trait_item` here.
for tit in trait_items {

View File

@ -53,7 +53,7 @@ fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
false
}
fn suggession_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
fn suggesstion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
if let ExprKind::Binary(ref op, left, right) = expr.kind {
if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) {
let op_snippet = match op.node {
@ -75,7 +75,7 @@ impl LateLintPass<'_> for NeedlessBitwiseBool {
expr.span,
"use of bitwise operator instead of lazy operator between booleans",
|diag| {
if let Some(sugg) = suggession_snippet(cx, expr) {
if let Some(sugg) = suggesstion_snippet(cx, expr) {
diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable);
}
},

View File

@ -1,10 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{expr_visitor, is_local_used};
use rustc_errors::Applicability;
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::{expr_visitor, expr_visitor_no_bodies, is_local_used};
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind};
use rustc_hir::{
BindingAnnotation, Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt,
StmtKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
@ -73,6 +77,31 @@ fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) ->
seen
}
fn contains_let(cond: &Expr<'_>) -> bool {
let mut seen = false;
expr_visitor_no_bodies(|expr| {
if let ExprKind::Let(_) = expr.kind {
seen = true;
}
!seen
})
.visit_expr(cond);
seen
}
fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
let StmtKind::Local(local) = stmt.kind else { return false };
!local.pat.walk_short(|pat| {
if let PatKind::Binding(.., None) = pat.kind {
!needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat))
} else {
true
}
})
}
#[derive(Debug)]
struct LocalAssign {
lhs_id: HirId,
@ -187,11 +216,14 @@ fn first_usage<'tcx>(
local_stmt_id: HirId,
block: &'tcx Block<'tcx>,
) -> Option<Usage<'tcx>> {
let significant_drop = needs_ordered_drop(cx, cx.typeck_results().node_type(binding_id));
block
.stmts
.iter()
.skip_while(|stmt| stmt.hir_id != local_stmt_id)
.skip(1)
.take_while(|stmt| !significant_drop || !stmt_needs_ordered_drop(cx, stmt))
.find(|&stmt| is_local_used(cx, stmt, binding_id))
.and_then(|stmt| match stmt.kind {
StmtKind::Expr(expr) => Some(Usage {
@ -235,12 +267,15 @@ fn check<'tcx>(
match usage.expr.kind {
ExprKind::Assign(..) => {
let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
let mut msg_span = MultiSpan::from_spans(vec![local_stmt.span, assign.span]);
msg_span.push_span_label(local_stmt.span, "created here");
msg_span.push_span_label(assign.span, "initialised here");
span_lint_and_then(
cx,
NEEDLESS_LATE_INIT,
local_stmt.span,
"unneeded late initalization",
msg_span,
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(
local_stmt.span,
@ -258,14 +293,14 @@ fn check<'tcx>(
},
);
},
ExprKind::If(_, then_expr, Some(else_expr)) => {
ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => {
let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
span_lint_and_then(
cx,
NEEDLESS_LATE_INIT,
local_stmt.span,
"unneeded late initalization",
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
@ -296,7 +331,7 @@ fn check<'tcx>(
cx,
NEEDLESS_LATE_INIT,
local_stmt.span,
"unneeded late initalization",
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
@ -333,12 +368,11 @@ fn check<'tcx>(
impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
if_chain! {
if let Local {
init: None,
pat: &Pat {
kind: PatKind::Binding(_, binding_id, _, None),
kind: PatKind::Binding(BindingAnnotation::Unannotated, binding_id, _, None),
..
},
source: LocalSource::Normal,

View File

@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if let hir::ItemKind::Impl(hir::Impl {
of_trait: None,
ref generics,
generics,
self_ty: impl_self_ty,
items,
..

View File

@ -197,7 +197,7 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
if interned_name.chars().any(char::is_uppercase) {
return;
}
if interned_name.chars().all(|c| c.is_digit(10) || c == '_') {
if interned_name.chars().all(|c| c.is_ascii_digit() || c == '_') {
span_lint(
self.0.cx,
JUST_UNDERSCORES_AND_DIGITS,

View File

@ -25,7 +25,7 @@ declare_clippy_lint! {
///
/// ### Known problems
/// The actual meaning can be the intended one. `\x00` can be used in these
/// cases to be unambigious.
/// cases to be unambiguous.
///
/// The lint does not trigger for format strings in `print!()`, `write!()`
/// and friends since the string is already preprocessed when Clippy lints

View File

@ -1,6 +1,7 @@
use std::collections::VecDeque;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lint_allowed;
use itertools::{izip, Itertools};
use rustc_ast::{walk_list, Label, Mutability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -8,7 +9,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor};
use rustc_hir::{
Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
QPath, Stmt, StmtKind, TyKind, UnOp,
@ -33,6 +34,9 @@ declare_clippy_lint! {
/// and the assigned variables are also only in recursion, it is useless.
///
/// ### Known problems
/// Too many code paths in the linting code are currently untested and prone to produce false
/// positives or are prone to have performance implications.
///
/// In some cases, this would not catch all useless arguments.
///
/// ```rust
@ -85,7 +89,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.60.0"]
pub ONLY_USED_IN_RECURSION,
complexity,
nursery,
"arguments that is only used in recursion can be removed"
}
declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
@ -100,6 +104,9 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
_: Span,
id: HirId,
) {
if is_lint_allowed(cx, ONLY_USED_IN_RECURSION, id) {
return;
}
if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind {
let def_id = id.owner.to_def_id();
let data = cx.tcx.def_path(def_id).data;
@ -145,7 +152,8 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
is_method: matches!(kind, FnKind::Method(..)),
has_self,
ty_res,
ty_ctx: cx.tcx,
tcx: cx.tcx,
visited_exprs: FxHashSet::default(),
};
visitor.visit_expr(&body.value);
@ -206,19 +214,13 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
}
pub fn is_primitive(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
ty::Ref(_, t, _) => is_primitive(*t),
_ => false,
}
let ty = ty.peel_refs();
ty.is_primitive() || ty.is_str()
}
pub fn is_array(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Array(..) | ty::Slice(..) => true,
ty::Ref(_, t, _) => is_array(*t),
_ => false,
}
let ty = ty.peel_refs();
ty.is_array() || ty.is_array_slice()
}
/// This builds the graph of side effect.
@ -250,40 +252,30 @@ pub struct SideEffectVisit<'tcx> {
is_method: bool,
has_self: bool,
ty_res: &'tcx TypeckResults<'tcx>,
ty_ctx: TyCtxt<'tcx>,
tcx: TyCtxt<'tcx>,
visited_exprs: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
fn visit_block(&mut self, b: &'tcx Block<'tcx>) {
b.stmts.iter().for_each(|stmt| {
self.visit_stmt(stmt);
self.ret_vars.clear();
});
walk_list!(self, visit_expr, b.expr);
}
fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
match s.kind {
StmtKind::Local(Local {
pat, init: Some(init), ..
}) => {
self.visit_pat_expr(pat, init, false);
self.ret_vars.clear();
},
StmtKind::Item(i) => {
let item = self.ty_ctx.hir().item(i);
self.visit_item(item);
self.ret_vars.clear();
},
StmtKind::Expr(e) | StmtKind::Semi(e) => {
self.visit_expr(e);
self.ret_vars.clear();
StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => {
walk_stmt(self, s);
},
StmtKind::Local(_) => {},
}
self.ret_vars.clear();
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if !self.visited_exprs.insert(ex.hir_id) {
return;
}
match ex.kind {
ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
self.ret_vars = exprs
@ -307,7 +299,7 @@ impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms),
// since analysing the closure is not easy, just set all variables in it to side-effect
ExprKind::Closure(_, _, body_id, _, _) => {
let body = self.ty_ctx.hir().body(body_id);
let body = self.tcx.hir().body(body_id);
self.visit_body(body);
let vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(vars);

View File

@ -78,7 +78,7 @@ fn is_result_ok(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool {
/// A struct containing information about occurrences of the
/// `if let Some(..) = .. else` construct that this lint detects.
struct OptionIfLetElseOccurence {
struct OptionIfLetElseOccurrence {
option: String,
method_sugg: String,
some_expr: String,
@ -100,9 +100,9 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
}
/// If this expression is the option if let/else construct we're detecting, then
/// this function returns an `OptionIfLetElseOccurence` struct with details if
/// this function returns an `OptionIfLetElseOccurrence` struct with details if
/// this construct is found, or None if this construct is not found.
fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurence> {
fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurrence> {
if_chain! {
if !expr.span.from_expansion(); // Don't lint macros, because it behaves weirdly
if !in_constant(cx, expr.hir_id);
@ -154,7 +154,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
}
}
}
Some(OptionIfLetElseOccurence {
Some(OptionIfLetElseOccurrence {
option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
method_sugg: method_sugg.to_string(),
some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir_with_macro_callsite(cx, some_body, "..")),

View File

@ -3,6 +3,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::expr_sig;
use clippy_utils::visitors::contains_unsafe_block;
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
use if_chain::if_chain;
use rustc_errors::{Applicability, MultiSpan};
@ -10,9 +11,9 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirIdMap;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{
self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg,
self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg,
ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
TraitItem, TraitItemKind, TyKind,
TraitItem, TraitItemKind, TyKind, Unsafety,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
@ -88,19 +89,26 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// This lint checks for functions that take immutable
/// references and return mutable ones.
/// This lint checks for functions that take immutable references and return
/// mutable ones. This will not trigger if no unsafe code exists as there
/// are multiple safe functions which will do this transformation
///
/// To be on the conservative side, if there's at least one mutable
/// reference with the output lifetime, this lint will not trigger.
///
/// ### Why is this bad?
/// This is trivially unsound, as one can create two
/// mutable references from the same (immutable!) source.
/// This [error](https://github.com/rust-lang/rust/issues/39465)
/// actually lead to an interim Rust release 1.15.1.
/// Creating a mutable reference which can be repeatably derived from an
/// immutable reference is unsound as it allows creating multiple live
/// mutable references to the same object.
///
/// This [error](https://github.com/rust-lang/rust/issues/39465) actually
/// lead to an interim Rust release 1.15.1.
///
/// ### Known problems
/// To be on the conservative side, if there's at least one
/// mutable reference with the output lifetime, this lint will not trigger.
/// In practice, this case is unlikely anyway.
/// This pattern is used by memory allocators to allow allocating multiple
/// objects while returning mutable references to each one. So long as
/// different mutable references are returned each time such a function may
/// be safe.
///
/// ### Example
/// ```ignore
@ -145,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
return;
}
check_mut_from_ref(cx, sig.decl);
check_mut_from_ref(cx, sig, None);
for arg in check_fn_args(
cx,
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
@ -170,10 +178,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
let hir = cx.tcx.hir();
let mut parents = hir.parent_iter(body.value.hir_id);
let (item_id, decl, is_trait_item) = match parents.next() {
let (item_id, sig, is_trait_item) = match parents.next() {
Some((_, Node::Item(i))) => {
if let ItemKind::Fn(sig, ..) = &i.kind {
(i.def_id, sig.decl, false)
(i.def_id, sig, false)
} else {
return;
}
@ -185,14 +193,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
return;
}
if let ImplItemKind::Fn(sig, _) = &i.kind {
(i.def_id, sig.decl, false)
(i.def_id, sig, false)
} else {
return;
}
},
Some((_, Node::TraitItem(i))) => {
if let TraitItemKind::Fn(sig, _) = &i.kind {
(i.def_id, sig.decl, true)
(i.def_id, sig, true)
} else {
return;
}
@ -200,7 +208,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
_ => return,
};
check_mut_from_ref(cx, decl);
check_mut_from_ref(cx, sig, Some(body));
let decl = sig.decl;
let sig = cx.tcx.fn_sig(item_id).skip_binder();
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
@ -473,31 +482,31 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
})
}
fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
if let FnRetTy::Return(ty) = decl.output {
if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
let mut immutables = vec![];
for (_, mutbl, argspan) in decl
.inputs
.iter()
.filter_map(get_rptr_lm)
.filter(|&(lt, _, _)| lt.name == out.name)
{
if mutbl == Mutability::Mut {
return;
}
immutables.push(argspan);
}
if immutables.is_empty() {
return;
}
fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
if let FnRetTy::Return(ty) = sig.decl.output
&& let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
{
let args: Option<Vec<_>> = sig
.decl
.inputs
.iter()
.filter_map(get_rptr_lm)
.filter(|&(lt, _, _)| lt.name == out.name)
.map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
.collect();
if let Some(args) = args
&& !args.is_empty()
&& body.map_or(true, |body| {
sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
})
{
span_lint_and_then(
cx,
MUT_FROM_REF,
ty.span,
"mutable borrow from immutable input(s)",
|diag| {
let ms = MultiSpan::from_spans(immutables);
let ms = MultiSpan::from_spans(args);
diag.span_note(ms, "immutable borrow here");
},
);

View File

@ -0,0 +1,56 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Item, ItemKind, VisibilityKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
///
/// Restricts the usage of `pub use ...`
///
/// ### Why is this bad?
///
/// `pub use` is usually fine, but a project may wish to limit `pub use` instances to prevent
/// unintentional exports or to encourage placing exported items directly in public modules
///
/// ### Example
/// ```rust
/// pub mod outer {
/// mod inner {
/// pub struct Test {}
/// }
/// pub use inner::Test;
/// }
///
/// use outer::Test;
/// ```
/// Use instead:
/// ```rust
/// pub mod outer {
/// pub struct Test {}
/// }
///
/// use outer::Test;
/// ```
#[clippy::version = "1.62.0"]
pub PUB_USE,
restriction,
"restricts the usage of `pub use`"
}
declare_lint_pass!(PubUse => [PUB_USE]);
impl EarlyLintPass for PubUse {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if let ItemKind::Use(_) = item.kind &&
let VisibilityKind::Public = item.vis.kind {
span_lint_and_help(
cx,
PUB_USE,
item.span,
"using `pub use`",
None,
"move the exported item to a public module instead",
);
}
}
}

View File

@ -1,10 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::hygiene::MacroKind;
declare_clippy_lint! {
/// ### What it does
@ -43,8 +45,11 @@ impl_lint_pass!(RedundantPubCrate => [REDUNDANT_PUB_CRATE]);
impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if cx.tcx.visibility(item.def_id) == ty::Visibility::Restricted(CRATE_DEF_ID.to_def_id()) {
if !cx.access_levels.is_exported(item.def_id) && self.is_exported.last() == Some(&false) {
if_chain! {
if cx.tcx.visibility(item.def_id) == ty::Visibility::Restricted(CRATE_DEF_ID.to_def_id());
if !cx.access_levels.is_exported(item.def_id) && self.is_exported.last() == Some(&false);
if is_not_macro_export(item);
then {
let span = item.span.with_hi(item.ident.span.hi());
let descr = cx.tcx.def_kind(item.def_id).descr(item.def_id.to_def_id());
span_lint_and_then(
@ -75,3 +80,15 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
}
}
}
fn is_not_macro_export<'tcx>(item: &'tcx Item<'tcx>) -> bool {
if let ItemKind::Use(path, _) = item.kind {
if let Res::Def(DefKind::Macro(MacroKind::Bang), _) = path.res {
return false;
}
} else if let ItemKind::Macro(..) = item.kind {
return false;
}
true
}

View File

@ -0,0 +1,39 @@
// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.
#[rustfmt::skip]
pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"),
("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"),
("clippy::box_vec", "clippy::box_collection"),
("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"),
("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"),
("clippy::disallowed_method", "clippy::disallowed_methods"),
("clippy::disallowed_type", "clippy::disallowed_types"),
("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles"),
("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"),
("clippy::identity_conversion", "clippy::useless_conversion"),
("clippy::if_let_some_result", "clippy::match_result_ok"),
("clippy::new_without_default_derive", "clippy::new_without_default"),
("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
("clippy::option_expect_used", "clippy::expect_used"),
("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"),
("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"),
("clippy::option_unwrap_used", "clippy::unwrap_used"),
("clippy::ref_in_deref", "clippy::needless_borrow"),
("clippy::result_expect_used", "clippy::expect_used"),
("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
("clippy::result_unwrap_used", "clippy::unwrap_used"),
("clippy::single_char_push_str", "clippy::single_char_add_str"),
("clippy::stutter", "clippy::module_name_repetitions"),
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
("clippy::zero_width_space", "clippy::invisible_characters"),
("clippy::drop_bounds", "drop_bounds"),
("clippy::into_iter_on_array", "array_into_iter"),
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
("clippy::invalid_ref", "invalid_value"),
("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
("clippy::panic_params", "non_fmt_panics"),
("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"),
("clippy::unknown_clippy_lints", "unknown_lints"),
("clippy::unused_label", "unused_labels"),
];

View File

@ -51,114 +51,110 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
let mut map = FxHashMap::<Res, ExistingName>::default();
for id in cx.tcx.hir().items() {
if !matches!(cx.tcx.hir().def_kind(id.def_id), DefKind::Impl) {
continue;
}
let item = cx.tcx.hir().item(id);
if let ItemKind::Impl(Impl {
items,
of_trait,
self_ty,
..
}) = &item.kind
if matches!(cx.tcx.hir().def_kind(id.def_id), DefKind::Impl)
&& let item = cx.tcx.hir().item(id)
&& let ItemKind::Impl(Impl {
items,
of_trait,
self_ty,
..
}) = &item.kind
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
{
if let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind {
if !map.contains_key(res) {
map.insert(
*res,
ExistingName {
impl_methods: BTreeMap::new(),
trait_methods: BTreeMap::new(),
},
);
}
let existing_name = map.get_mut(res).unwrap();
match of_trait {
Some(trait_ref) => {
let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
if let Some(Node::TraitRef(TraitRef { path, .. })) =
cx.tcx.hir().find(trait_ref.hir_ref_id);
if let Res::Def(DefKind::Trait, did) = path.res;
then{
// FIXME: if
// `rustc_middle::ty::assoc::AssocItems::items` is public,
// we can iterate its keys instead of `in_definition_order`,
// which's more efficient
cx.tcx
.associated_items(did)
.in_definition_order()
.filter(|assoc_item| {
matches!(assoc_item.kind, AssocKind::Fn)
})
.map(|assoc_item| assoc_item.name)
.collect()
}else{
BTreeSet::new()
}
};
let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
if let Some(impl_span) = existing_name.impl_methods.get(&method_name) {
span_lint_and_then(
cx,
SAME_NAME_METHOD,
*impl_span,
"method's name is the same as an existing method in a trait",
|diag| {
diag.span_note(
trait_method_span,
&format!("existing `{}` defined here", method_name),
);
},
);
}
if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
v.push(trait_method_span);
} else {
existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
}
};
for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
}) {
let method_name = impl_item_ref.ident.name;
methods_in_trait.remove(&method_name);
check_trait_method(method_name, impl_item_ref.span);
}
for method_name in methods_in_trait {
check_trait_method(method_name, item.span);
}
if !map.contains_key(res) {
map.insert(
*res,
ExistingName {
impl_methods: BTreeMap::new(),
trait_methods: BTreeMap::new(),
},
None => {
for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
}) {
let method_name = impl_item_ref.ident.name;
let impl_span = impl_item_ref.span;
if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
span_lint_and_then(
cx,
SAME_NAME_METHOD,
impl_span,
"method's name is the same as an existing method in a trait",
|diag| {
// TODO should we `span_note` on every trait?
// iterate on trait_spans?
diag.span_note(
trait_spans[0],
&format!("existing `{}` defined here", method_name),
);
},
);
}
existing_name.impl_methods.insert(method_name, impl_span);
);
}
let existing_name = map.get_mut(res).unwrap();
match of_trait {
Some(trait_ref) => {
let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
if let Some(Node::TraitRef(TraitRef { path, .. })) =
cx.tcx.hir().find(trait_ref.hir_ref_id);
if let Res::Def(DefKind::Trait, did) = path.res;
then{
// FIXME: if
// `rustc_middle::ty::assoc::AssocItems::items` is public,
// we can iterate its keys instead of `in_definition_order`,
// which's more efficient
cx.tcx
.associated_items(did)
.in_definition_order()
.filter(|assoc_item| {
matches!(assoc_item.kind, AssocKind::Fn)
})
.map(|assoc_item| assoc_item.name)
.collect()
}else{
BTreeSet::new()
}
},
}
};
let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
if let Some(impl_span) = existing_name.impl_methods.get(&method_name) {
span_lint_and_then(
cx,
SAME_NAME_METHOD,
*impl_span,
"method's name is the same as an existing method in a trait",
|diag| {
diag.span_note(
trait_method_span,
&format!("existing `{}` defined here", method_name),
);
},
);
}
if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
v.push(trait_method_span);
} else {
existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
}
};
for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
}) {
let method_name = impl_item_ref.ident.name;
methods_in_trait.remove(&method_name);
check_trait_method(method_name, impl_item_ref.span);
}
for method_name in methods_in_trait {
check_trait_method(method_name, item.span);
}
},
None => {
for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
}) {
let method_name = impl_item_ref.ident.name;
let impl_span = impl_item_ref.span;
if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
span_lint_and_then(
cx,
SAME_NAME_METHOD,
impl_span,
"method's name is the same as an existing method in a trait",
|diag| {
// TODO should we `span_note` on every trait?
// iterate on trait_spans?
diag.span_note(
trait_spans[0],
&format!("existing `{}` defined here", method_name),
);
},
);
}
existing_name.impl_methods.insert(method_name, impl_span);
}
},
}
}
}

View File

@ -9,15 +9,25 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// When sorting primitive values (integers, bools, chars, as well
/// as arrays, slices, and tuples of such items), it is better to
/// as arrays, slices, and tuples of such items), it is typically better to
/// use an unstable sort than a stable sort.
///
/// ### Why is this bad?
/// Using a stable sort consumes more memory and cpu cycles. Because
/// values which compare equal are identical, preserving their
/// Typically, using a stable sort consumes more memory and cpu cycles.
/// Because values which compare equal are identical, preserving their
/// relative order (the guarantee that a stable sort provides) means
/// nothing, while the extra costs still apply.
///
/// ### Known problems
///
/// As pointed out in
/// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241),
/// a stable sort can instead be significantly faster for certain scenarios
/// (eg. when a sorted vector is extended with new data and resorted).
///
/// For more information and benchmarking results, please refer to the
/// issue linked above.
///
/// ### Example
/// ```rust
/// let mut vec = vec![2, 1, 3];
@ -30,7 +40,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.47.0"]
pub STABLE_SORT_PRIMITIVE,
perf,
pedantic,
"use of sort() when sort_unstable() is equivalent"
}
@ -126,7 +136,7 @@ impl LateLintPass<'_> for StableSortPrimitive {
Applicability::MachineApplicable,
);
diag.note(
"an unstable sort would perform faster without any observable difference for this data type",
"an unstable sort typically performs faster without any observable difference for this data type",
);
},
);

View File

@ -5,6 +5,7 @@ use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method
use clippy_utils::{peel_blocks, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -451,3 +452,58 @@ impl<'tcx> LateLintPass<'tcx> for StringToString {
}
}
}
declare_clippy_lint! {
/// ### What it does
/// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.
///
/// ### Why is this bad?
/// `split_whitespace` already ignores leading and trailing whitespace.
///
/// ### Example
/// ```rust
/// " A B C ".trim().split_whitespace();
/// ```
/// Use instead:
/// ```rust
/// " A B C ".split_whitespace();
/// ```
#[clippy::version = "1.62.0"]
pub TRIM_SPLIT_WHITESPACE,
style,
"using `str::trim()` or alike before `str::split_whitespace`"
}
declare_lint_pass!(TrimSplitWhitespace => [TRIM_SPLIT_WHITESPACE]);
impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
let tyckres = cx.typeck_results();
if_chain! {
if let ExprKind::MethodCall(path, [split_recv], split_ws_span) = expr.kind;
if path.ident.name == sym!(split_whitespace);
if let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id);
if cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id);
if let ExprKind::MethodCall(path, [_trim_recv], trim_span) = split_recv.kind;
if let trim_fn_name @ ("trim" | "trim_start" | "trim_end") = path.ident.name.as_str();
if let Some(trim_def_id) = tyckres.type_dependent_def_id(split_recv.hir_id);
if is_one_of_trim_diagnostic_items(cx, trim_def_id);
then {
span_lint_and_sugg(
cx,
TRIM_SPLIT_WHITESPACE,
trim_span.with_hi(split_ws_span.lo()),
&format!("found call to `str::{}` before `str::split_whitespace`", trim_fn_name),
&format!("remove `{}()`", trim_fn_name),
String::new(),
Applicability::MachineApplicable,
);
}
}
}
}
fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool {
cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id)
|| cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id)
|| cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id)
}

View File

@ -550,7 +550,7 @@ fn ident_difference_expr_with_base_location(
// IdentIter, then the output of this function will be almost always be correct
// in practice.
//
// If it turns out that problematic cases are more prelavent than we assume,
// If it turns out that problematic cases are more prevalent than we assume,
// then we should be able to change this function to do the correct traversal,
// without needing to change the rest of the code.

View File

@ -13,6 +13,7 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use std::fmt::Write as _;
declare_clippy_lint! {
/// ### What it does
@ -34,7 +35,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.38.0"]
pub TYPE_REPETITION_IN_BOUNDS,
pedantic,
nursery,
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
}
@ -64,7 +65,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.47.0"]
pub TRAIT_DUPLICATION_IN_BOUNDS,
pedantic,
nursery,
"Check if the same trait bounds are specified twice during a function declaration"
}
@ -182,19 +183,19 @@ impl TraitBounds {
for b in v.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
hint_string.push_str(&format!(
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
));
);
}
}
for b in p.bounds.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
hint_string.push_str(&format!(
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
));
);
}
}
hint_string.truncate(hint_string.len() - 2);
@ -241,7 +242,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
);
}
else {
trait_resolutions_direct.push((res_where, span_where))
trait_resolutions_direct.push((res_where, span_where));
}
}
}

View File

@ -1,10 +1,9 @@
use clippy_utils::last_path_segment;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_normalizable;
use if_chain::if_chain;
use rustc_hir::{Expr, GenericArg, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, cast::CastKind, Ty};
use rustc_middle::ty::{cast::CastKind, Ty};
use rustc_span::DUMMY_SP;
use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited};
@ -34,15 +33,12 @@ pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty
// check if the component types of the transmuted collection and the result have different ABI,
// size or alignment
pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {
let empty_param_env = ty::ParamEnv::empty();
// check if `from` and `to` are normalizable to avoid ICE (#4968)
if !(is_normalizable(cx, empty_param_env, from) && is_normalizable(cx, empty_param_env, to)) {
return false;
}
let from_ty_layout = cx.tcx.layout_of(empty_param_env.and(from));
let to_ty_layout = cx.tcx.layout_of(empty_param_env.and(to));
if let (Ok(from_layout), Ok(to_layout)) = (from_ty_layout, to_ty_layout) {
from_layout.size != to_layout.size || from_layout.align != to_layout.align || from_layout.abi != to_layout.abi
if let Ok(from) = cx.tcx.try_normalize_erasing_regions(cx.param_env, from)
&& let Ok(to) = cx.tcx.try_normalize_erasing_regions(cx.param_env, to)
&& let Ok(from_layout) = cx.tcx.layout_of(cx.param_env.and(from))
&& let Ok(to_layout) = cx.tcx.layout_of(cx.param_env.and(to))
{
from_layout.size != to_layout.size || from_layout.align.abi != to_layout.align.abi
} else {
// no idea about layout, so don't lint
false
@ -91,7 +87,7 @@ fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>
let res = check.do_check(&fn_ctxt);
// do_check's documentation says that it might return Ok and create
// errors in the fcx instead of returing Err in some cases. Those cases
// errors in the fcx instead of returning Err in some cases. Those cases
// should be filtered out before getting here.
assert!(
!fn_ctxt.errors_reported_since_creation(),

View File

@ -432,8 +432,8 @@ impl Types {
fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, context: CheckTyContext) {
// Ignore functions in trait implementations as they are usually forced by the trait definition.
//
// FIXME: idially we would like to warn *if the compicated type can be simplified*, but it's hard to
// check.
// FIXME: ideally we would like to warn *if the complicated type can be simplified*, but it's hard
// to check.
if context.is_in_trait_impl {
return;
}

View File

@ -156,8 +156,9 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
.array_windows::<2>()
.rev()
.map_while(|[start, end]| {
src.get(start.to_usize() - offset..end.to_usize() - offset)
.map(|text| (start.to_usize(), text.trim_start()))
let start = start.to_usize() - offset;
let end = end.to_usize() - offset;
src.get(start..end).map(|text| (start, text.trim_start()))
})
.filter(|(_, text)| !text.is_empty());
@ -182,7 +183,7 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
let (mut line_start, mut line) = (line_start, line);
loop {
if line.starts_with("/*") {
let src = src[line_start..line_starts.last().unwrap().to_usize()].trim_start();
let src = src[line_start..line_starts.last().unwrap().to_usize() - offset].trim_start();
let mut tokens = tokenize(src);
return src[..tokens.next().unwrap().len]
.to_ascii_uppercase()

View File

@ -1,18 +1,46 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::visitors::for_each_value_source;
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{Stmt, StmtKind};
use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty, TypeFoldable, TypeVisitor};
use super::LET_UNIT_VALUE;
pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
if let StmtKind::Local(local) = stmt.kind {
if cx.typeck_results().pat_ty(local.pat).is_unit() {
if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() {
return;
if let StmtKind::Local(local) = stmt.kind
&& let Some(init) = local.init
&& !local.pat.span.from_expansion()
&& !in_external_macro(cx.sess(), stmt.span)
&& cx.typeck_results().pat_ty(local.pat).is_unit()
{
let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}).is_continue();
if needs_inferred {
if !matches!(local.pat.kind, PatKind::Wild) {
span_lint_and_then(
cx,
LET_UNIT_VALUE,
stmt.span,
"this let-binding has unit value",
|diag| {
diag.span_suggestion(
local.pat.span,
"use a wild (`_`) binding",
"_",
Applicability::MaybeIncorrect, // snippet
);
},
);
}
} else {
span_lint_and_then(
cx,
LET_UNIT_VALUE,
@ -33,3 +61,45 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
}
}
}
fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
let id = match e.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(ref path),
hir_id,
..
},
_,
) => cx.qpath_res(path, *hir_id).opt_def_id(),
ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(e.hir_id),
_ => return false,
};
if let Some(id) = id
&& let sig = cx.tcx.fn_sig(id).skip_binder()
&& let ty::Param(output_ty) = *sig.output().kind()
{
sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
} else {
false
}
}
fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
struct Visitor(u32);
impl<'tcx> TypeVisitor<'tcx> for Visitor {
type BreakTy = ();
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::Param(ty) = *ty.kind() {
if ty.index == self.0 {
ControlFlow::BREAK
} else {
ControlFlow::CONTINUE
}
} else {
ty.super_visit_with(self)
}
}
}
ty.visit_with(&mut Visitor(index)).is_break()
}

View File

@ -23,7 +23,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub LET_UNIT_VALUE,
pedantic,
style,
"creating a `let` binding to a value of unit type, which usually can't be used afterwards"
}

View File

@ -0,0 +1,81 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item};
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
///
/// Detects cases of owned empty strings being passed as an argument to a function expecting `&str`
///
/// ### Why is this bad?
///
/// This results in longer and less readable code
///
/// ### Example
/// ```rust
/// vec!["1", "2", "3"].join(&String::new());
/// ```
/// Use instead:
/// ```rust
/// vec!["1", "2", "3"].join("");
/// ```
#[clippy::version = "1.62.0"]
pub UNNECESSARY_OWNED_EMPTY_STRINGS,
style,
"detects cases of references to owned empty strings being passed as an argument to a function expecting `&str`"
}
declare_lint_pass!(UnnecessaryOwnedEmptyStrings => [UNNECESSARY_OWNED_EMPTY_STRINGS]);
impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner_expr) = expr.kind;
if let ExprKind::Call(fun, args) = inner_expr.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 ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
if inner_str.is_str();
then {
if match_def_path(cx, fun_def_id, &paths::STRING_NEW) {
span_lint_and_sugg(
cx,
UNNECESSARY_OWNED_EMPTY_STRINGS,
expr.span,
"usage of `&String::new()` for a function expecting a `&str` argument",
"try",
"\"\"".to_owned(),
Applicability::MachineApplicable,
);
} else {
if_chain! {
if match_def_path(cx, fun_def_id, &paths::FROM_FROM);
if let [.., last_arg] = args;
if let ExprKind::Lit(spanned) = &last_arg.kind;
if let LitKind::Str(symbol, _) = spanned.node;
if symbol.is_empty();
let inner_expr_type = cx.typeck_results().expr_ty(inner_expr);
if is_type_diagnostic_item(cx, inner_expr_type, sym::String);
then {
span_lint_and_sugg(
cx,
UNNECESSARY_OWNED_EMPTY_STRINGS,
expr.span,
"usage of `&String::from(\"\")` for a function expecting a `&str` argument",
"try",
"\"\"".to_owned(),
Applicability::MachineApplicable,
);
}
}
}
}
}
}
}

View File

@ -5,7 +5,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{meets_msrv, msrvs, over};
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast::{self as ast, Mutability, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -25,7 +25,7 @@ declare_clippy_lint! {
/// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*.
///
/// ### Why is this bad?
/// In the example above, `Some` is repeated, which unncessarily complicates the pattern.
/// In the example above, `Some` is repeated, which unnecessarily complicates the pattern.
///
/// ### Example
/// ```rust
@ -230,6 +230,10 @@ fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize)
// with which a pattern `C(p_0)` may be formed,
// which we would want to join with other `C(p_j)`s.
Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_)
// Skip immutable refs, as grouping them saves few characters,
// and almost always requires adding parens (increasing noisiness).
// In the case of only two patterns, replacement adds net characters.
| Ref(_, Mutability::Not)
// Dealt with elsewhere.
| Or(_) | Paren(_) => false,
// Transform `box x | ... | box y` into `box (x | y)`.
@ -241,10 +245,10 @@ fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize)
|k| matches!(k, Box(_)),
|k| always_pat!(k, Box(p) => p),
),
// Transform `&m x | ... | &m y` into `&m (x | y)`.
Ref(target, m1) => extend_with_matching(
// Transform `&mut x | ... | &mut y` into `&mut (x | y)`.
Ref(target, Mutability::Mut) => extend_with_matching(
target, start, alternatives,
|k| matches!(k, Ref(_, m2) if m1 == m2), // Mutabilities must match.
|k| matches!(k, Ref(_, Mutability::Mut)),
|k| always_pat!(k, Ref(p, _) => p),
),
// Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`.

View File

@ -30,7 +30,7 @@ declare_clippy_lint! {
///
/// ### Known problems
/// - Unaddressed false negative in fn bodies of trait implementations
/// - False positive with assotiated types in traits (#4140)
/// - False positive with associated types in traits (#4140)
///
/// ### Example
/// ```rust

View File

@ -70,7 +70,7 @@ macro_rules! bind {
};
}
/// Transforms the given `Option<T>` varibles into `OptionPat<Binding<T>>`.
/// Transforms the given `Option<T>` variables into `OptionPat<Binding<T>>`.
/// This displays as `Some($name)` or `None` when printed. The name of the inner binding
/// is set to the name of the variable passed to the macro.
macro_rules! opt_bind {

View File

@ -310,6 +310,12 @@ define_Conf! {
/// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
(max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE
(await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
/// Lint: LARGE_INCLUDE_FILE.
///
/// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
(max_include_file_size: u64 = 1_000_000),
}
/// Search for the configuration file.

View File

@ -0,0 +1,55 @@
use clippy_utils::get_attr;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// It formats the attached node with `{:#?}` and writes the result to the
/// standard output. This is intended for debugging.
///
/// ### Examples
/// ```rs
/// #[clippy::dump]
/// use std::mem;
///
/// #[clippy::dump]
/// fn foo(input: u32) -> u64 {
/// input as u64
/// }
/// ```
pub DUMP_HIR,
internal_warn,
"helper to dump info about code"
}
declare_lint_pass!(DumpHir => [DUMP_HIR]);
impl<'tcx> LateLintPass<'tcx> for DumpHir {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if has_attr(cx, item.hir_id()) {
println!("{item:#?}");
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if has_attr(cx, expr.hir_id) {
println!("{expr:#?}");
}
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
match stmt.kind {
hir::StmtKind::Expr(e) | hir::StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return,
_ => {},
}
if has_attr(cx, stmt.hir_id) {
println!("{stmt:#?}");
}
}
}
fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
let attrs = cx.tcx.hir().attrs(hir_id);
get_attr(cx.sess(), attrs, "dump").count() > 0
}

View File

@ -1,577 +0,0 @@
//! checks for attributes
use clippy_utils::get_attr;
use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::Session;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Dumps every ast/hir node which has the `#[clippy::dump]`
/// attribute
///
/// ### Example
/// ```rust,ignore
/// #[clippy::dump]
/// extern crate foo;
/// ```
///
/// prints
///
/// ```text
/// item `foo`
/// visibility inherited from outer item
/// extern crate dylib source: "/path/to/foo.so"
/// ```
pub DEEP_CODE_INSPECTION,
internal_warn,
"helper to dump info about code"
}
declare_lint_pass!(DeepCodeInspector => [DEEP_CODE_INSPECTION]);
impl<'tcx> LateLintPass<'tcx> for DeepCodeInspector {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !has_attr(cx.sess(), cx.tcx.hir().attrs(item.hir_id())) {
return;
}
print_item(cx, item);
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if !has_attr(cx.sess(), cx.tcx.hir().attrs(item.hir_id())) {
return;
}
println!("impl item `{}`", item.ident.name);
match cx.tcx.visibility(item.def_id) {
ty::Visibility::Public => println!("public"),
ty::Visibility::Restricted(def_id) => {
if def_id.is_top_level_module() {
println!("visible crate wide")
} else {
println!("visible in module `{}`", cx.tcx.def_path_str(def_id))
}
},
ty::Visibility::Invisible => println!("invisible"),
}
match item.kind {
hir::ImplItemKind::Const(_, body_id) => {
println!("associated constant");
print_expr(cx, &cx.tcx.hir().body(body_id).value, 1);
},
hir::ImplItemKind::Fn(..) => println!("method"),
hir::ImplItemKind::TyAlias(_) => println!("associated type"),
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if !has_attr(cx.sess(), cx.tcx.hir().attrs(expr.hir_id)) {
return;
}
print_expr(cx, expr, 0);
}
fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
if !has_attr(cx.sess(), cx.tcx.hir().attrs(arm.hir_id)) {
return;
}
print_pat(cx, arm.pat, 1);
if let Some(ref guard) = arm.guard {
println!("guard:");
print_guard(cx, guard, 1);
}
println!("body:");
print_expr(cx, arm.body, 1);
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
if !has_attr(cx.sess(), cx.tcx.hir().attrs(stmt.hir_id)) {
return;
}
match stmt.kind {
hir::StmtKind::Local(local) => {
println!("local variable of type {}", cx.typeck_results().node_type(local.hir_id));
println!("pattern:");
print_pat(cx, local.pat, 0);
if let Some(e) = local.init {
println!("init expression:");
print_expr(cx, e, 0);
}
},
hir::StmtKind::Item(_) => println!("item decl"),
hir::StmtKind::Expr(e) | hir::StmtKind::Semi(e) => print_expr(cx, e, 0),
}
}
}
fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool {
get_attr(sess, attrs, "dump").count() > 0
}
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn print_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
println!("{}ty: {}", ind, cx.typeck_results().expr_ty(expr));
println!(
"{}adjustments: {:?}",
ind,
cx.typeck_results().adjustments().get(expr.hir_id)
);
match expr.kind {
hir::ExprKind::Box(e) => {
println!("{}Box", ind);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Array(v) => {
println!("{}Array", ind);
for e in v {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Call(func, args) => {
println!("{}Call", ind);
println!("{}function:", ind);
print_expr(cx, func, indent + 1);
println!("{}arguments:", ind);
for arg in args {
print_expr(cx, arg, indent + 1);
}
},
hir::ExprKind::Let(hir::Let { pat, init, ty, .. }) => {
print_pat(cx, pat, indent + 1);
if let Some(ty) = ty {
println!("{} type annotation: {:?}", ind, ty);
}
print_expr(cx, init, indent + 1);
},
hir::ExprKind::MethodCall(path, args, _) => {
println!("{}MethodCall", ind);
println!("{}method name: {}", ind, path.ident.name);
for arg in args {
print_expr(cx, arg, indent + 1);
}
},
hir::ExprKind::Tup(v) => {
println!("{}Tup", ind);
for e in v {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Binary(op, lhs, rhs) => {
println!("{}Binary", ind);
println!("{}op: {:?}", ind, op.node);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::Unary(op, inner) => {
println!("{}Unary", ind);
println!("{}op: {:?}", ind, op);
print_expr(cx, inner, indent + 1);
},
hir::ExprKind::Lit(ref lit) => {
println!("{}Lit", ind);
println!("{}{:?}", ind, lit);
},
hir::ExprKind::Cast(e, target) => {
println!("{}Cast", ind);
print_expr(cx, e, indent + 1);
println!("{}target type: {:?}", ind, target);
},
hir::ExprKind::Type(e, target) => {
println!("{}Type", ind);
print_expr(cx, e, indent + 1);
println!("{}target type: {:?}", ind, target);
},
hir::ExprKind::Loop(..) => {
println!("{}Loop", ind);
},
hir::ExprKind::If(cond, _, ref else_opt) => {
println!("{}If", ind);
println!("{}condition:", ind);
print_expr(cx, cond, indent + 1);
if let Some(els) = *else_opt {
println!("{}else:", ind);
print_expr(cx, els, indent + 1);
}
},
hir::ExprKind::Match(cond, _, ref source) => {
println!("{}Match", ind);
println!("{}condition:", ind);
print_expr(cx, cond, indent + 1);
println!("{}source: {:?}", ind, source);
},
hir::ExprKind::Closure(ref clause, _, _, _, _) => {
println!("{}Closure", ind);
println!("{}clause: {:?}", ind, clause);
},
hir::ExprKind::Yield(sub, _) => {
println!("{}Yield", ind);
print_expr(cx, sub, indent + 1);
},
hir::ExprKind::Block(_, _) => {
println!("{}Block", ind);
},
hir::ExprKind::Assign(lhs, rhs, _) => {
println!("{}Assign", ind);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::AssignOp(ref binop, lhs, rhs) => {
println!("{}AssignOp", ind);
println!("{}op: {:?}", ind, binop.node);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::Field(e, ident) => {
println!("{}Field", ind);
println!("{}field name: {}", ind, ident.name);
println!("{}struct expr:", ind);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Index(arr, idx) => {
println!("{}Index", ind);
println!("{}array expr:", ind);
print_expr(cx, arr, indent + 1);
println!("{}index expr:", ind);
print_expr(cx, idx, indent + 1);
},
hir::ExprKind::Path(hir::QPath::Resolved(ref ty, path)) => {
println!("{}Resolved Path, {:?}", ind, ty);
println!("{}path: {:?}", ind, path);
},
hir::ExprKind::Path(hir::QPath::TypeRelative(ty, seg)) => {
println!("{}Relative Path, {:?}", ind, ty);
println!("{}seg: {:?}", ind, seg);
},
hir::ExprKind::Path(hir::QPath::LangItem(lang_item, ..)) => {
println!("{}Lang Item Path, {:?}", ind, lang_item.name());
},
hir::ExprKind::AddrOf(kind, ref muta, e) => {
println!("{}AddrOf", ind);
println!("kind: {:?}", kind);
println!("mutability: {:?}", muta);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Break(_, ref e) => {
println!("{}Break", ind);
if let Some(e) = *e {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Continue(_) => println!("{}Again", ind),
hir::ExprKind::Ret(ref e) => {
println!("{}Ret", ind);
if let Some(e) = *e {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::InlineAsm(asm) => {
println!("{}InlineAsm", ind);
println!("{}template: {}", ind, InlineAsmTemplatePiece::to_string(asm.template));
println!("{}options: {:?}", ind, asm.options);
println!("{}operands:", ind);
for (op, _op_sp) in asm.operands {
match op {
hir::InlineAsmOperand::In { expr, .. }
| hir::InlineAsmOperand::InOut { expr, .. } => {
print_expr(cx, expr, indent + 1);
}
hir::InlineAsmOperand::Out { expr, .. } => {
if let Some(expr) = expr {
print_expr(cx, expr, indent + 1);
}
},
hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
print_expr(cx, in_expr, indent + 1);
if let Some(out_expr) = out_expr {
print_expr(cx, out_expr, indent + 1);
}
},
hir::InlineAsmOperand::Const { anon_const }
| hir::InlineAsmOperand::SymFn { anon_const } => {
println!("{}anon_const:", ind);
print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1);
},
hir::InlineAsmOperand::SymStatic { path, .. } => {
match path {
hir::QPath::Resolved(ref ty, path) => {
println!("{}Resolved Path, {:?}", ind, ty);
println!("{}path: {:?}", ind, path);
},
hir::QPath::TypeRelative(ty, seg) => {
println!("{}Relative Path, {:?}", ind, ty);
println!("{}seg: {:?}", ind, seg);
},
hir::QPath::LangItem(lang_item, ..) => {
println!("{}Lang Item Path, {:?}", ind, lang_item.name());
},
}
}
}
}
},
hir::ExprKind::Struct(path, fields, ref base) => {
println!("{}Struct", ind);
println!("{}path: {:?}", ind, path);
for field in fields {
println!("{}field \"{}\":", ind, field.ident.name);
print_expr(cx, field.expr, indent + 1);
}
if let Some(base) = *base {
println!("{}base:", ind);
print_expr(cx, base, indent + 1);
}
},
hir::ExprKind::ConstBlock(ref anon_const) => {
println!("{}ConstBlock", ind);
println!("{}anon_const:", ind);
print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1);
},
hir::ExprKind::Repeat(val, length) => {
println!("{}Repeat", ind);
println!("{}value:", ind);
print_expr(cx, val, indent + 1);
println!("{}repeat count:", ind);
match length {
hir::ArrayLen::Infer(_, _) => println!("{}repeat count: _", ind),
hir::ArrayLen::Body(anon_const) => {
print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1);
},
}
},
hir::ExprKind::Err => {
println!("{}Err", ind);
},
hir::ExprKind::DropTemps(e) => {
println!("{}DropTemps", ind);
print_expr(cx, e, indent + 1);
},
}
}
fn print_item(cx: &LateContext<'_>, item: &hir::Item<'_>) {
let did = item.def_id;
println!("item `{}`", item.ident.name);
match cx.tcx.visibility(item.def_id) {
ty::Visibility::Public => println!("public"),
ty::Visibility::Restricted(def_id) => {
if def_id.is_top_level_module() {
println!("visible crate wide")
} else {
println!("visible in module `{}`", cx.tcx.def_path_str(def_id))
}
},
ty::Visibility::Invisible => println!("invisible"),
}
match item.kind {
hir::ItemKind::ExternCrate(ref _renamed_from) => {
if let Some(crate_id) = cx.tcx.extern_mod_stmt_cnum(did) {
let source = cx.tcx.used_crate_source(crate_id);
if let Some(ref src) = source.dylib {
println!("extern crate dylib source: {:?}", src.0);
}
if let Some(ref src) = source.rlib {
println!("extern crate rlib source: {:?}", src.0);
}
} else {
println!("weird extern crate without a crate id");
}
},
hir::ItemKind::Use(path, ref kind) => println!("{:?}, {:?}", path, kind),
hir::ItemKind::Static(..) => println!("static item of type {:#?}", cx.tcx.type_of(did)),
hir::ItemKind::Const(..) => println!("const item of type {:#?}", cx.tcx.type_of(did)),
hir::ItemKind::Fn(..) => {
let item_ty = cx.tcx.type_of(did);
println!("function of type {:#?}", item_ty);
},
hir::ItemKind::Macro(ref macro_def, _) => {
if macro_def.macro_rules {
println!("macro introduced by `macro_rules!`");
} else {
println!("macro introduced by `macro`");
}
},
hir::ItemKind::Mod(..) => println!("module"),
hir::ItemKind::ForeignMod { abi, .. } => println!("foreign module with abi: {}", abi),
hir::ItemKind::GlobalAsm(asm) => println!("global asm: {:?}", asm),
hir::ItemKind::TyAlias(..) => {
println!("type alias for {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::OpaqueTy(..) => {
println!("existential type with real type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Enum(..) => {
println!("enum definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Struct(..) => {
println!("struct definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Union(..) => {
println!("union definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Trait(..) => {
println!("trait decl");
if cx.tcx.trait_is_auto(did.to_def_id()) {
println!("trait is auto");
} else {
println!("trait is not auto");
}
},
hir::ItemKind::TraitAlias(..) => {
println!("trait alias");
},
hir::ItemKind::Impl(hir::Impl {
of_trait: Some(ref _trait_ref),
..
}) => {
println!("trait impl");
},
hir::ItemKind::Impl(hir::Impl { of_trait: None, .. }) => {
println!("impl");
},
}
}
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn print_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
match pat.kind {
hir::PatKind::Wild => println!("{}Wild", ind),
hir::PatKind::Binding(ref mode, .., ident, ref inner) => {
println!("{}Binding", ind);
println!("{}mode: {:?}", ind, mode);
println!("{}name: {}", ind, ident.name);
if let Some(inner) = *inner {
println!("{}inner:", ind);
print_pat(cx, inner, indent + 1);
}
},
hir::PatKind::Or(fields) => {
println!("{}Or", ind);
for field in fields {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Struct(ref path, fields, ignore) => {
println!("{}Struct", ind);
println!(
"{}name: {}",
ind,
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
);
println!("{}ignore leftover fields: {}", ind, ignore);
println!("{}fields:", ind);
for field in fields {
println!("{} field name: {}", ind, field.ident.name);
if field.is_shorthand {
println!("{} in shorthand notation", ind);
}
print_pat(cx, field.pat, indent + 1);
}
},
hir::PatKind::TupleStruct(ref path, fields, opt_dots_position) => {
println!("{}TupleStruct", ind);
println!(
"{}path: {}",
ind,
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
);
if let Some(dot_position) = opt_dots_position {
println!("{}dot position: {}", ind, dot_position);
}
for field in fields {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Path(hir::QPath::Resolved(ref ty, path)) => {
println!("{}Resolved Path, {:?}", ind, ty);
println!("{}path: {:?}", ind, path);
},
hir::PatKind::Path(hir::QPath::TypeRelative(ty, seg)) => {
println!("{}Relative Path, {:?}", ind, ty);
println!("{}seg: {:?}", ind, seg);
},
hir::PatKind::Path(hir::QPath::LangItem(lang_item, ..)) => {
println!("{}Lang Item Path, {:?}", ind, lang_item.name());
},
hir::PatKind::Tuple(pats, opt_dots_position) => {
println!("{}Tuple", ind);
if let Some(dot_position) = opt_dots_position {
println!("{}dot position: {}", ind, dot_position);
}
for field in pats {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Box(inner) => {
println!("{}Box", ind);
print_pat(cx, inner, indent + 1);
},
hir::PatKind::Ref(inner, ref muta) => {
println!("{}Ref", ind);
println!("{}mutability: {:?}", ind, muta);
print_pat(cx, inner, indent + 1);
},
hir::PatKind::Lit(e) => {
println!("{}Lit", ind);
print_expr(cx, e, indent + 1);
},
hir::PatKind::Range(ref l, ref r, ref range_end) => {
println!("{}Range", ind);
if let Some(expr) = l {
print_expr(cx, expr, indent + 1);
}
if let Some(expr) = r {
print_expr(cx, expr, indent + 1);
}
match *range_end {
hir::RangeEnd::Included => println!("{} end included", ind),
hir::RangeEnd::Excluded => println!("{} end excluded", ind),
}
},
hir::PatKind::Slice(first_pats, ref range, last_pats) => {
println!("{}Slice [a, b, ..i, y, z]", ind);
println!("[a, b]:");
for pat in first_pats {
print_pat(cx, pat, indent + 1);
}
println!("i:");
if let Some(pat) = *range {
print_pat(cx, pat, indent + 1);
}
println!("[y, z]:");
for pat in last_pats {
print_pat(cx, pat, indent + 1);
}
},
}
}
fn print_guard(cx: &LateContext<'_>, guard: &hir::Guard<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
match guard {
hir::Guard::If(expr) => {
println!("{}If", ind);
print_expr(cx, expr, indent + 1);
},
hir::Guard::IfLet(pat, expr) => {
println!("{}IfLet", ind);
print_pat(cx, pat, indent + 1);
print_expr(cx, expr, indent + 1);
},
}
}

View File

@ -292,7 +292,7 @@ declare_clippy_lint! {
/// Checks for unnecessary conversion from Symbol to a string.
///
/// ### Why is this bad?
/// It's faster use symbols directly intead of strings.
/// It's faster use symbols directly instead of strings.
///
/// ### Example
/// Bad:
@ -823,7 +823,7 @@ fn suggest_note(
cx,
COLLAPSIBLE_SPAN_LINT_CALLS,
expr.span,
"this call is collspible",
"this call is collapsible",
"collapse into",
format!(
"span_lint_and_note({}, {}, {}, {}, {}, {})",

View File

@ -33,7 +33,7 @@ use std::path::Path;
/// This is the output file of the lint collector.
const OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
/// These lints are excluded from the export.
const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"];
const BLACK_LISTED_LINTS: &[&str] = &["lint_author", "dump_hir", "internal_metadata_collector"];
/// These groups will be ignored by the lint group matcher. This is useful for collections like
/// `clippy::all`
const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
@ -117,7 +117,7 @@ const APPLICABILITY_NAME_INDEX: usize = 2;
/// This applicability will be set for unresolved applicability values.
const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved";
/// The version that will be displayed if none has been defined
const VERION_DEFAULT_STR: &str = "Unknown";
const VERSION_DEFAULT_STR: &str = "Unknown";
declare_clippy_lint! {
/// ### What it does
@ -571,7 +571,7 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
extract_clippy_version_value(cx, item).map_or_else(
|| VERION_DEFAULT_STR.to_string(),
|| VERSION_DEFAULT_STR.to_string(),
|version| version.as_str().to_string(),
)
}
@ -872,7 +872,7 @@ impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
self.suggestion_count += 2;
}
/// Checks if the suggestions include multiple spanns
/// Checks if the suggestions include multiple spans
fn is_multi_part(&self) -> bool {
self.suggestion_count > 1
}

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