├── lints ├── fold │ ├── .gitignore │ ├── README.md │ ├── ui │ │ ├── main.rs │ │ ├── main.fixed │ │ └── main.stderr │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── simple.rs ├── map │ ├── .gitignore │ ├── README.md │ ├── Cargo.toml │ ├── ui │ │ ├── main.fixed │ │ ├── main.rs │ │ └── main.stderr │ └── src │ │ ├── lib.rs │ │ └── builder.rs ├── filter │ ├── .gitignore │ ├── README.md │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ ├── simple.rs │ │ └── simple_flipped.rs │ └── ui │ │ ├── main.fixed │ │ ├── main.rs │ │ └── main.stderr ├── for_each │ ├── .gitignore │ ├── README.md │ ├── Cargo.toml │ ├── ui │ │ ├── main.stderr │ │ ├── main.rs │ │ └── main.fixed │ └── src │ │ ├── lib.rs │ │ └── variable_check.rs ├── par_fold │ ├── .gitignore │ ├── README.md │ ├── ui │ │ ├── par_fold_simple.rs │ │ ├── par_fold_simple.fixed │ │ └── par_fold_simple.stderr │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── par_fold_simple.rs ├── par_iter │ ├── .gitignore │ ├── README.md │ ├── src │ │ ├── constants.rs │ │ ├── lib.rs │ │ └── variable_check.rs │ ├── Cargo.toml │ └── ui │ │ ├── main2.fixed │ │ ├── main2.rs │ │ ├── main.stderr │ │ ├── main.rs │ │ └── main.fixed ├── rayon_imports │ ├── ui │ │ ├── build.stderr │ │ ├── rayon_present.stderr │ │ ├── build.rs │ │ ├── rayon_missing.rs │ │ ├── rayon_missing.fixed │ │ ├── rayon_present.rs │ │ ├── rayon_present.fixed │ │ └── rayon_missing.stderr │ ├── .gitignore │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── rayon_prelude.rs │ └── Cargo.toml └── to_iter │ ├── .gitignore │ ├── README.md │ ├── Cargo.toml │ ├── ui │ ├── main.rs │ ├── main.fixed │ └── main.stderr │ └── src │ ├── variable_check.rs │ └── lib.rs ├── .gitignore ├── .cargo └── config.toml ├── rust-toolchain ├── .github ├── dependabot.yml └── workflows │ ├── bench-test-github.yaml │ ├── bench-test-gitee.yaml │ └── verify.yaml ├── src └── lib.rs ├── scripts ├── counters.sh └── prepare_repo.sh ├── utils ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml └── README.md /lints/fold/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/map/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/filter/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/for_each/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/par_fold/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/par_iter/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/build.stderr: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lints/to_iter/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/rayon_imports/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_present.stderr: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | note.rs 4 | .vscode 5 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | linker = "dylint-link" 3 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | (0..100).for_each(|x| println!("{x}")); 3 | } 4 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-07-25" 3 | components = ["llvm-tools-preview", "rustc-dev"] 4 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_missing.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | 3 | fn main() { 4 | (0..100).for_each(|x| println!("{x}")); 5 | } 6 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_missing.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | 3 | #[allow(unused_imports)] 4 | use rayon::prelude::*; 5 | fn main() { 6 | (0..100).for_each(|x| println!("{x}")); 7 | } 8 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_present.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(unused_imports)] 3 | 4 | use rayon::prelude::*; 5 | 6 | fn main() { 7 | (0..100).into_par_iter().for_each(|x| println!("{x}")); 8 | } 9 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_present.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(unused_imports)] 3 | 4 | use rayon::prelude::*; 5 | 6 | fn main() { 7 | (0..100).into_par_iter().for_each(|x| println!("{x}")); 8 | } 9 | -------------------------------------------------------------------------------- /lints/filter/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/fold/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/map/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/for_each/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/par_fold/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/par_iter/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/to_iter/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/rayon_imports/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | ### What it does 4 | 5 | ### Why is this bad? 6 | 7 | ### Known problems 8 | Remove if none. 9 | 10 | ### Example 11 | ```rust 12 | // example code where a warning is issued 13 | ``` 14 | Use instead: 15 | ```rust 16 | // example code that does not raise a warning 17 | ``` 18 | -------------------------------------------------------------------------------- /lints/par_iter/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const TRAIT_PATHS: &[&[&str]] = &[ 2 | &["rayon", "iter", "IntoParallelIterator"], 3 | &["rayon", "iter", "ParallelIterator"], 4 | &["rayon", "iter", "IndexedParallelIterator"], 5 | &["rayon", "iter", "IntoParallelRefMutIterator"], 6 | &["rayon", "iter", "IntoParallelRefIterator"], 7 | ]; 8 | -------------------------------------------------------------------------------- /lints/rayon_imports/ui/rayon_missing.stderr: -------------------------------------------------------------------------------- 1 | warning: rayon::prelude::* is not imported 2 | --> $DIR/rayon_missing.rs:3:1 3 | | 4 | LL | fn main() { 5 | | ^ 6 | | 7 | = note: `#[warn(rayon_prelude)]` on by default 8 | help: consider adding this import 9 | | 10 | LL + #[allow(unused_imports)] 11 | LL + use rayon::prelude::*; 12 | LL | fn main() { 13 | | 14 | 15 | warning: 1 warning emitted 16 | 17 | -------------------------------------------------------------------------------- /lints/par_fold/ui/par_fold_simple.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | 3 | #[allow(unused_imports)] 4 | use rayon::prelude::*; 5 | 6 | fn main() { 7 | warn_fold_simple(); 8 | } 9 | 10 | fn warn_fold_simple() { 11 | let mut sum = 0; 12 | let numbers = vec![1, 2, 3, 4, 5]; 13 | sum += numbers.iter().map(|&num| num).fold(0, |mut sum, v| { 14 | sum += v; 15 | sum 16 | }); 17 | 18 | println!("Sum: {}", sum); 19 | } 20 | -------------------------------------------------------------------------------- /lints/par_fold/ui/par_fold_simple.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | 3 | #[allow(unused_imports)] 4 | use rayon::prelude::*; 5 | 6 | fn main() { 7 | warn_fold_simple(); 8 | } 9 | 10 | fn warn_fold_simple() { 11 | let mut sum = 0; 12 | let numbers = vec![1, 2, 3, 4, 5]; 13 | sum += numbers.par_iter().map(|&num| num).reduce(|| 0, |mut sum, v| { 14 | sum += v; 15 | sum 16 | }); 17 | 18 | println!("Sum: {}", sum); 19 | } 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries 14 | # remove this ignore rule if your package has binaries 15 | update-types: 16 | - "version-update:semver-patch" 17 | - "version-update:semver-minor" -------------------------------------------------------------------------------- /lints/par_fold/ui/par_fold_simple.stderr: -------------------------------------------------------------------------------- 1 | warning: sequential fold 2 | --> $DIR/par_fold_simple.rs:13:12 3 | | 4 | LL | sum += numbers.iter().map(|&num| num).fold(0, |mut sum, v| { 5 | | ____________^ 6 | LL | | sum += v; 7 | LL | | sum 8 | LL | | }); 9 | | |______^ 10 | | 11 | = note: `#[warn(warn_par_fold_simple)]` on by default 12 | help: try using a parallel fold on the iterator 13 | | 14 | LL | sum += numbers.par_iter().map(|&num| num).reduce(|| 0, |mut sum, v| { 15 | | ~~~~~~~~ ~~~~~~ ~~~~ 16 | 17 | warning: 1 warning emitted 18 | 19 | -------------------------------------------------------------------------------- /lints/fold/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | warn_fold_simple(); 4 | get_upload_file_total_size(); 5 | } 6 | 7 | fn warn_fold_simple() { 8 | let mut sum = 0; 9 | let numbers = vec![1, 2, 3, 4, 5]; 10 | 11 | numbers.iter().for_each(|&num| { 12 | sum += num; 13 | }); 14 | 15 | println!("Sum: {}", sum); 16 | } 17 | 18 | fn get_upload_file_total_size() -> u64 { 19 | let some_num = vec![0; 10]; 20 | let mut file_total_size = 0; 21 | (0..some_num.len()).into_iter().for_each(|_| { 22 | let (_, upload_size) = (true, 99); 23 | file_total_size += upload_size; 24 | }); 25 | file_total_size 26 | } 27 | -------------------------------------------------------------------------------- /lints/map/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "map" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | clippy_utils = { workspace = true } 14 | utils = { workspace = true } 15 | 16 | dylint_linting = "3.2.1" 17 | 18 | [dev-dependencies] 19 | dylint_testing = "3.2.1" 20 | 21 | [package.metadata.rust-analyzer] 22 | rustc_private = true 23 | 24 | [features] 25 | rlib = ["dylint_linting/constituent"] 26 | 27 | [[example]] 28 | name = "map_main" 29 | path = "ui/main.rs" 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /lints/filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filter" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | [dev-dependencies] 18 | dylint_testing = "3.2.1" 19 | 20 | [package.metadata.rust-analyzer] 21 | rustc_private = true 22 | 23 | [features] 24 | rlib = ["dylint_linting/constituent"] 25 | 26 | [[example]] 27 | name = "filter_main" 28 | path = "ui/main.rs" 29 | 30 | [lints] 31 | workspace = true 32 | -------------------------------------------------------------------------------- /lints/fold/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fold" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | clippy_utils = { workspace = true } 14 | utils = { workspace = true } 15 | 16 | dylint_linting = "3.2.1" 17 | 18 | [dev-dependencies] 19 | dylint_testing = "3.2.1" 20 | 21 | [package.metadata.rust-analyzer] 22 | rustc_private = true 23 | 24 | [features] 25 | rlib = ["dylint_linting/constituent"] 26 | 27 | [[example]] 28 | name = "fold_main" 29 | path = "ui/main.rs" 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /lints/to_iter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "to_iter" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | [dev-dependencies] 18 | dylint_testing = "3.2.1" 19 | 20 | [package.metadata.rust-analyzer] 21 | rustc_private = true 22 | 23 | [features] 24 | rlib = ["dylint_linting/constituent"] 25 | 26 | [[example]] 27 | name = "to_iter_main" 28 | path = "ui/main.rs" 29 | 30 | [lints] 31 | workspace = true 32 | -------------------------------------------------------------------------------- /lints/for_each/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "for_each" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | [dev-dependencies] 18 | dylint_testing = "3.2.1" 19 | 20 | [package.metadata.rust-analyzer] 21 | rustc_private = true 22 | 23 | [features] 24 | rlib = ["dylint_linting/constituent"] 25 | 26 | [[example]] 27 | name = "for_each_main" 28 | path = "ui/main.rs" 29 | 30 | [lints] 31 | workspace = true 32 | -------------------------------------------------------------------------------- /lints/fold/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | warn_fold_simple(); 4 | get_upload_file_total_size(); 5 | } 6 | 7 | fn warn_fold_simple() { 8 | let mut sum = 0; 9 | let numbers = vec![1, 2, 3, 4, 5]; 10 | 11 | sum += numbers.iter().map(|&num| { num}).fold(0, |mut sum, v| { sum += v; sum }); 12 | 13 | println!("Sum: {}", sum); 14 | } 15 | 16 | fn get_upload_file_total_size() -> u64 { 17 | let some_num = vec![0; 10]; 18 | let mut file_total_size = 0; 19 | file_total_size += (0..some_num.len()).into_iter().map(|_| {let (_, upload_size) = (true, 99); upload_size}).fold(0, |mut file_total_size, v| { file_total_size += v; file_total_size }); 20 | file_total_size 21 | } 22 | -------------------------------------------------------------------------------- /lints/par_fold/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "par_fold" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | [dev-dependencies] 18 | dylint_testing = "3.2.1" 19 | rayon = "1.9.0" 20 | 21 | [package.metadata.rust-analyzer] 22 | rustc_private = true 23 | 24 | [features] 25 | rlib = ["dylint_linting/constituent"] 26 | 27 | [[example]] 28 | name = "par_fold_simple" 29 | path = "ui/par_fold_simple.rs" 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /lints/fold/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | #![feature(iter_intersperse)] 5 | 6 | #[cfg(not(feature = "rlib"))] 7 | dylint_linting::dylint_library!(); 8 | 9 | extern crate rustc_errors; 10 | extern crate rustc_hir; 11 | extern crate rustc_lint; 12 | extern crate rustc_session; 13 | extern crate rustc_span; 14 | 15 | mod simple; 16 | 17 | #[allow(clippy::no_mangle_with_rust_abi)] 18 | #[cfg_attr(not(feature = "rlib"), no_mangle)] 19 | 20 | pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 21 | lint_store.register_late_pass(|_| Box::new(simple::FoldSimple)); 22 | } 23 | 24 | #[test] 25 | fn ui() { 26 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 27 | } 28 | -------------------------------------------------------------------------------- /lints/par_iter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "par_iter" 3 | version = "0.1.1" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | 18 | [dev-dependencies] 19 | dylint_testing = "3.2.1" 20 | rayon = "1.9.0" 21 | futures = "0.3.30" 22 | 23 | [package.metadata.rust-analyzer] 24 | rustc_private = true 25 | 26 | [features] 27 | rlib = ["dylint_linting/constituent"] 28 | 29 | [[example]] 30 | name = "par_iter_main" 31 | path = "ui/main.rs" 32 | 33 | [[example]] 34 | name = "par_iter_main2" 35 | path = "ui/main2.rs" 36 | 37 | [lints] 38 | workspace = true 39 | -------------------------------------------------------------------------------- /lints/par_fold/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![feature(let_chains)] 3 | 4 | #[cfg(not(feature = "rlib"))] 5 | dylint_linting::dylint_library!(); 6 | 7 | #[cfg(feature = "rlib")] 8 | extern crate rustc_driver; 9 | extern crate rustc_errors; 10 | extern crate rustc_hir; 11 | extern crate rustc_lint; 12 | extern crate rustc_middle; 13 | extern crate rustc_session; 14 | extern crate rustc_span; 15 | 16 | mod par_fold_simple; 17 | 18 | #[allow(clippy::no_mangle_with_rust_abi)] 19 | #[cfg_attr(not(feature = "rlib"), no_mangle)] 20 | pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 21 | lint_store.register_late_pass(|_| Box::new(par_fold_simple::ParFoldSimple)); 22 | } 23 | 24 | #[test] 25 | fn ui() { 26 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 27 | } 28 | -------------------------------------------------------------------------------- /lints/rayon_imports/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![feature(let_chains)] 3 | 4 | #[cfg(not(feature = "rlib"))] 5 | dylint_linting::dylint_library!(); 6 | 7 | #[cfg(feature = "rlib")] 8 | extern crate rustc_driver; 9 | extern crate rustc_errors; 10 | extern crate rustc_hir; 11 | extern crate rustc_lint; 12 | extern crate rustc_middle; 13 | extern crate rustc_session; 14 | extern crate rustc_span; 15 | 16 | mod rayon_prelude; 17 | 18 | #[allow(clippy::no_mangle_with_rust_abi)] 19 | #[cfg_attr(not(feature = "rlib"), no_mangle)] 20 | pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 21 | lint_store.register_late_pass(|_| Box::new(rayon_prelude::RayonPrelude)); 22 | } 23 | 24 | #[test] 25 | fn ui() { 26 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(unwrap_infallible)] 4 | 5 | dylint_linting::dylint_library!(); 6 | 7 | extern crate rustc_lint; 8 | extern crate rustc_session; 9 | 10 | #[allow(clippy::no_mangle_with_rust_abi)] 11 | #[no_mangle] 12 | pub fn register_lints(sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 13 | // PHASE 0 14 | rayon_imports::register_lints(sess, lint_store); 15 | // PHASE 1 16 | //for_each::register_lints(sess, lint_store); 17 | to_iter::register_lints(sess, lint_store); 18 | // PHASE 2 19 | //filter::register_lints(sess, lint_store); 20 | // PHASE 3 21 | fold::register_lints(sess, lint_store); 22 | // PHASE 4 23 | par_fold::register_lints(sess, lint_store); 24 | par_iter::register_lints(sess, lint_store); 25 | } 26 | -------------------------------------------------------------------------------- /lints/filter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | 5 | #[cfg(not(feature = "rlib"))] 6 | dylint_linting::dylint_library!(); 7 | 8 | extern crate rustc_errors; 9 | extern crate rustc_hir; 10 | extern crate rustc_lint; 11 | extern crate rustc_session; 12 | extern crate rustc_span; 13 | 14 | mod simple; 15 | mod simple_flipped; 16 | 17 | #[allow(clippy::no_mangle_with_rust_abi)] 18 | #[cfg_attr(not(feature = "rlib"), no_mangle)] 19 | pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 20 | lint_store.register_late_pass(|_| Box::new(simple::FilterSimple)); 21 | lint_store.register_late_pass(|_| Box::new(simple_flipped::FilterSimpleFlipped)); 22 | } 23 | 24 | #[test] 25 | fn ui() { 26 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 27 | } 28 | -------------------------------------------------------------------------------- /lints/rayon_imports/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rayon_imports" 3 | version = "0.1.0" 4 | authors = ["authors go here"] 5 | description = "description goes here" 6 | edition = "2021" 7 | publish = false 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | [dependencies] 13 | dylint_linting = "3.2.1" 14 | 15 | clippy_utils = { workspace = true } 16 | utils = { workspace = true } 17 | [dev-dependencies] 18 | dylint_testing = "3.2.1" 19 | rayon = "1.9.0" 20 | 21 | [package.metadata.rust-analyzer] 22 | rustc_private = true 23 | 24 | [features] 25 | rlib = ["dylint_linting/constituent"] 26 | 27 | [[example]] 28 | name = "build" 29 | path = "ui/build.rs" 30 | 31 | [[example]] 32 | name = "rayon_present" 33 | path = "ui/rayon_present.rs" 34 | 35 | [[example]] 36 | name = "rayon_missing" 37 | path = "ui/rayon_missing.rs" 38 | 39 | [lints] 40 | workspace = true 41 | -------------------------------------------------------------------------------- /lints/fold/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: implicit fold 2 | --> $DIR/main.rs:11:5 3 | | 4 | LL | / numbers.iter().for_each(|&num| { 5 | LL | | sum += num; 6 | LL | | }); 7 | | |______^ help: try using `fold` instead: `sum += numbers.iter().map(|&num| { num}).fold(0, |mut sum, v| { sum += v; sum })` 8 | | 9 | = note: `#[warn(fold_simple)]` on by default 10 | 11 | warning: implicit fold 12 | --> $DIR/main.rs:21:5 13 | | 14 | LL | / (0..some_num.len()).into_iter().for_each(|_| { 15 | LL | | let (_, upload_size) = (true, 99); 16 | LL | | file_total_size += upload_size; 17 | LL | | }); 18 | | |______^ help: try using `fold` instead: `file_total_size += (0..some_num.len()).into_iter().map(|_| {let (_, upload_size) = (true, 99); upload_size}).fold(0, |mut file_total_size, v| { file_total_size += v; file_total_size })` 19 | 20 | warning: 2 warnings emitted 21 | 22 | -------------------------------------------------------------------------------- /lints/filter/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | filter_simple(); 4 | filter_simple_flipped(); 5 | filter_simple_macro(); 6 | filter_simple_flipped_macro(); 7 | } 8 | 9 | fn filter_simple() { 10 | let items = vec!["apple", "banana", "cherry"]; 11 | let mut one_string = String::new(); 12 | items.iter().filter(|&item| { item.starts_with('a') }).for_each(|&item| { one_string.push_str(item); }); 13 | } 14 | 15 | fn filter_simple_flipped() { 16 | let numbers = vec![1, 2, 3, 4, 5]; 17 | let mut sum = 0; 18 | 19 | numbers.iter().filter(|&num| { !(num % 2 == 0) }).for_each(|&num| { sum += num; }); 20 | } 21 | 22 | fn filter_simple_macro() { 23 | let items = vec!["apple", "banana", "cherry"]; 24 | 25 | items.iter().filter(|&item| { item.starts_with('a') }).for_each(|&item| { println!("Starts with 'a': {}", item) }); 26 | } 27 | 28 | fn filter_simple_flipped_macro() { 29 | let numbers = vec![1, 2, 3, 4, 5]; 30 | 31 | numbers.iter().filter(|&num| { !(num % 2 == 0) }).for_each(|&num| { println!("Odd number: {}", num) }); 32 | } 33 | -------------------------------------------------------------------------------- /lints/filter/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | filter_simple(); 4 | filter_simple_flipped(); 5 | filter_simple_macro(); 6 | filter_simple_flipped_macro(); 7 | } 8 | 9 | fn filter_simple() { 10 | let items = vec!["apple", "banana", "cherry"]; 11 | let mut one_string = String::new(); 12 | items.iter().for_each(|&item| { 13 | if item.starts_with('a') { 14 | one_string.push_str(item); 15 | } 16 | }); 17 | } 18 | 19 | fn filter_simple_flipped() { 20 | let numbers = vec![1, 2, 3, 4, 5]; 21 | let mut sum = 0; 22 | 23 | numbers.iter().for_each(|&num| { 24 | if num % 2 == 0 { 25 | return; 26 | } 27 | sum += num; 28 | }); 29 | } 30 | 31 | fn filter_simple_macro() { 32 | let items = vec!["apple", "banana", "cherry"]; 33 | 34 | items.iter().for_each(|&item| { 35 | if item.starts_with('a') { 36 | println!("Starts with 'a': {}", item); 37 | } 38 | }); 39 | } 40 | 41 | fn filter_simple_flipped_macro() { 42 | let numbers = vec![1, 2, 3, 4, 5]; 43 | 44 | numbers.iter().for_each(|&num| { 45 | if num % 2 == 0 { 46 | return; 47 | } 48 | println!("Odd number: {}", num); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /scripts/counters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | suffix=$1 4 | # Find and replace the string in Cargo.toml 5 | sed -i.bak 's|https://github.com/trusted-programming/mate|https://github.com/trusted-programming/count_loop|' Cargo.toml 6 | 7 | echo 8 | echo "### FILE OUTPUT ###" 9 | echo 10 | 11 | dylint_output=$(cargo dylint --workspace --all -- --lib --bins 2>&1) 12 | 13 | echo "$dylint_output" 14 | echo 15 | echo "### FILE OUTPUT END ###" 16 | 17 | 18 | for_loop_count=$(echo "$dylint_output" | grep -c "warning: found for loop, code: 213423") 19 | iter_count=$(echo "$dylint_output" | grep -c "warning: found iterator, code: 213932") 20 | par_iter_count=$(echo "$dylint_output" | grep -c "warning: found par iterator, code: 213312") 21 | 22 | echo 23 | echo "### FINAL RESULTS ###" 24 | echo 25 | echo "for loop occurrences: $for_loop_count" 26 | echo "Total iter occurrences: $iter_count" 27 | echo "Total par iter occurrences: $par_iter_count" 28 | echo 29 | echo "### ALL DONE ###" 30 | 31 | # Echo the variables with the suffix to set them in the GitHub environment 32 | echo "for_loop_count_${suffix}=$for_loop_count" >>$GITHUB_ENV 33 | echo "iter_count_${suffix}=$iter_count" >>$GITHUB_ENV 34 | echo "par_iter_count_${suffix}=$par_iter_count" >>$GITHUB_ENV 35 | 36 | # Change the string back in Cargo.toml 37 | mv Cargo.toml.bak Cargo.toml 38 | -------------------------------------------------------------------------------- /utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clippy_utils = { workspace = true } 10 | 11 | 12 | [package.metadata.rust-analyzer] 13 | rustc_private = true 14 | 15 | [lints.clippy] 16 | # GROUPS 17 | perf = { level = "warn", priority = -1 } 18 | pedantic = { level = "warn", priority = -2 } 19 | correctness = { level = "forbid", priority = -3 } 20 | complexity = { level = "forbid", priority = -4 } 21 | 22 | 23 | # INDIVIDUAL LINTS 24 | semicolon_if_nothing_returned = { level = "allow", priority = 0 } 25 | similar_names = { level = "allow", priority = 0 } 26 | alloc_instead_of_core = { level = "warn", priority = 0 } 27 | as_conversions = { level = "warn", priority = 0 } 28 | as_underscore = { level = "warn", priority = 0 } 29 | dbg_macro = { level = "warn", priority = 0 } 30 | default_numeric_fallback = { level = "warn", priority = 0 } 31 | deref_by_slicing = { level = "warn", priority = 0 } 32 | empty_enum_variants_with_brackets = { level = "warn", priority = 0 } 33 | empty_structs_with_brackets = { level = "warn", priority = 0 } 34 | expect_used = { level = "warn", priority = 0 } 35 | format_push_string = { level = "warn", priority = 0 } 36 | if_then_some_else_none = { level = "warn", priority = 0 } 37 | unwrap_used = { level = "warn", priority = 0 } 38 | -------------------------------------------------------------------------------- /lints/par_iter/ui/main2.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code)] 3 | #![allow(unused_imports)] 4 | #![allow(unused_variables)] 5 | 6 | use core::ascii; 7 | use rayon::prelude::*; 8 | use std::ops::Range; 9 | use std::rc::Rc; 10 | 11 | fn main() {} 12 | 13 | // // no 14 | // pub fn complex_long_chain_no_par() { 15 | // let words = vec!["apple", "banana", "cherry", "date"]; 16 | // let numbers = vec![1, 2, 3, 4]; 17 | // let suffixes = vec!["st", "nd", "rd", "th"]; 18 | 19 | // words 20 | // .into_iter() 21 | // .enumerate() 22 | // .map(|(i, word)| { 23 | // let number = *numbers.get(i).unwrap_or(&0) * 2; 24 | // let suffix = suffixes.get(i).unwrap_or(&"th"); 25 | // (word, number, suffix) 26 | // }) 27 | // .filter(|(word, number, _)| !word.contains('a') || *number > 5) 28 | // .map(|(word, number, suffix)| format!("{}-{}{}", word, number, suffix)) 29 | // .enumerate() 30 | // .map(|(i, s)| if i % 2 == 0 { s.to_uppercase() } else { s }) 31 | // .take(2) 32 | // .for_each(|x| { 33 | // println!("{x}"); 34 | // }); 35 | // } 36 | 37 | // // should parallelize 38 | // fn no_iter_keywords() { 39 | // (0..100).for_each(|x| println!("{x}")); 40 | // } 41 | 42 | // // should parallelize 43 | // pub fn iter_returned_in_variable() { 44 | // let _: Range = (0..100).into_iter(); 45 | // } 46 | -------------------------------------------------------------------------------- /lints/par_iter/ui/main2.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code)] 3 | #![allow(unused_imports)] 4 | #![allow(unused_variables)] 5 | 6 | use core::ascii; 7 | use rayon::prelude::*; 8 | use std::ops::Range; 9 | use std::rc::Rc; 10 | 11 | fn main() {} 12 | 13 | // // no 14 | // pub fn complex_long_chain_no_par() { 15 | // let words = vec!["apple", "banana", "cherry", "date"]; 16 | // let numbers = vec![1, 2, 3, 4]; 17 | // let suffixes = vec!["st", "nd", "rd", "th"]; 18 | 19 | // words 20 | // .into_iter() 21 | // .enumerate() 22 | // .map(|(i, word)| { 23 | // let number = *numbers.get(i).unwrap_or(&0) * 2; 24 | // let suffix = suffixes.get(i).unwrap_or(&"th"); 25 | // (word, number, suffix) 26 | // }) 27 | // .filter(|(word, number, _)| !word.contains('a') || *number > 5) 28 | // .map(|(word, number, suffix)| format!("{}-{}{}", word, number, suffix)) 29 | // .enumerate() 30 | // .map(|(i, s)| if i % 2 == 0 { s.to_uppercase() } else { s }) 31 | // .take(2) 32 | // .for_each(|x| { 33 | // println!("{x}"); 34 | // }); 35 | // } 36 | 37 | // // should parallelize 38 | // fn no_iter_keywords() { 39 | // (0..100).for_each(|x| println!("{x}")); 40 | // } 41 | 42 | // // should parallelize 43 | // pub fn iter_returned_in_variable() { 44 | // let _: Range = (0..100).into_iter(); 45 | // } 46 | -------------------------------------------------------------------------------- /lints/map/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | warn_vec(); 4 | warn_hashmap(); 5 | warn_hashset(); 6 | warn_btreemap(); 7 | warn_btreeset(); 8 | } 9 | 10 | fn warn_vec() { 11 | let mut data = vec![]; 12 | let numbers = vec![1, 2, 3, 4, 5]; 13 | data.extend(numbers.iter().map(|&num| { num * 3 }).collect::>()); 14 | 15 | println!("Data: {:?}", data); 16 | } 17 | 18 | fn warn_hashmap() { 19 | use std::collections::HashMap; 20 | 21 | let mut data = HashMap::new(); 22 | let numbers = vec![1, 2, 3, 4, 5]; 23 | data.extend(numbers.iter().map(|&num| { (num, num.to_string()) }).collect::>()); 24 | 25 | println!("Data: {:?}", data); 26 | } 27 | 28 | fn warn_hashset() { 29 | use std::collections::HashSet; 30 | 31 | let mut data = HashSet::new(); 32 | let numbers = vec![1, 2, 3, 4, 5]; 33 | data.extend(numbers.iter().map(|&num| { num }).collect::>()); 34 | 35 | println!("Data: {:?}", data); 36 | } 37 | 38 | fn warn_btreemap() { 39 | use std::collections::BTreeMap; 40 | 41 | let mut data = BTreeMap::new(); 42 | let numbers = vec![1, 2, 3, 4, 5]; 43 | data.extend(numbers.iter().map(|&num| { (num, num.to_string()) }).collect::>()); 44 | 45 | println!("Data: {:?}", data); 46 | } 47 | 48 | fn warn_btreeset() { 49 | use std::collections::BTreeSet; 50 | 51 | let mut data = BTreeSet::new(); 52 | let numbers = vec![1, 2, 3, 4, 5]; 53 | data.extend(numbers.iter().map(|&num| { num }).collect::>()); 54 | 55 | println!("Data: {:?}", data); 56 | } 57 | -------------------------------------------------------------------------------- /lints/map/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | fn main() { 3 | warn_vec(); 4 | warn_hashmap(); 5 | warn_hashset(); 6 | warn_btreemap(); 7 | warn_btreeset(); 8 | } 9 | 10 | fn warn_vec() { 11 | let mut data = vec![]; 12 | let numbers = vec![1, 2, 3, 4, 5]; 13 | numbers.iter().for_each(|&num| { 14 | data.push(num * 3); 15 | }); 16 | 17 | println!("Data: {:?}", data); 18 | } 19 | 20 | fn warn_hashmap() { 21 | use std::collections::HashMap; 22 | 23 | let mut data = HashMap::new(); 24 | let numbers = vec![1, 2, 3, 4, 5]; 25 | numbers.iter().for_each(|&num| { 26 | data.insert(num, num.to_string()); 27 | }); 28 | 29 | println!("Data: {:?}", data); 30 | } 31 | 32 | fn warn_hashset() { 33 | use std::collections::HashSet; 34 | 35 | let mut data = HashSet::new(); 36 | let numbers = vec![1, 2, 3, 4, 5]; 37 | numbers.iter().for_each(|&num| { 38 | data.insert(num); 39 | }); 40 | 41 | println!("Data: {:?}", data); 42 | } 43 | 44 | fn warn_btreemap() { 45 | use std::collections::BTreeMap; 46 | 47 | let mut data = BTreeMap::new(); 48 | let numbers = vec![1, 2, 3, 4, 5]; 49 | numbers.iter().for_each(|&num| { 50 | data.insert(num, num.to_string()); 51 | }); 52 | 53 | println!("Data: {:?}", data); 54 | } 55 | 56 | fn warn_btreeset() { 57 | use std::collections::BTreeSet; 58 | 59 | let mut data = BTreeSet::new(); 60 | let numbers = vec![1, 2, 3, 4, 5]; 61 | numbers.iter().for_each(|&num| { 62 | data.insert(num); 63 | }); 64 | 65 | println!("Data: {:?}", data); 66 | } 67 | -------------------------------------------------------------------------------- /scripts/prepare_repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! command -v cargo-dylint &> /dev/null || ! command -v dylint-link &> /dev/null; then 4 | cargo install cargo-dylint dylint-link 5 | fi 6 | 7 | CARGO_TOML="Cargo.toml" 8 | STRING_TO_APPEND="\n[workspace.metadata.dylint]\nlibraries = [\n { git = \"https://github.com/trusted-programming/mate\"},\n]" 9 | 10 | # Check if Cargo.toml exists 11 | if [ -f "$CARGO_TOML" ]; then 12 | # Check if the string is already in the file 13 | if ! grep -qF -- "$STRING_TO_APPEND" "$CARGO_TOML"; then 14 | # Append the string to Cargo.toml 15 | echo -e "$STRING_TO_APPEND" >> "$CARGO_TOML" 16 | else 17 | echo "String already present in $CARGO_TOML" 18 | fi 19 | else 20 | echo "$CARGO_TOML does not exist" 21 | fi 22 | 23 | find . -type f -name "Cargo.toml" | while read -r file; do 24 | # Check if there is a [package] section in the file 25 | if grep -qE "^\[package\]" "$file"; then 26 | # Check if rayon is already in the dependencies 27 | if ! grep -qE "^rayon =" "$file"; then 28 | # Check if [dependencies] section exists 29 | if ! grep -qE "^\[dependencies\]" "$file"; then 30 | # Add [dependencies] section at the end of the file 31 | echo -e "\n[dependencies]" >> "$file" 32 | fi 33 | # Add rayon under the [dependencies] line 34 | sed -i '/\[dependencies\]/a rayon = "1.9.0"' "$file" 35 | fi 36 | fi 37 | done 38 | 39 | git config user.name 'test' 40 | git config user.email 'test@test.com' 41 | git add . 42 | git commit -am "test" 43 | -------------------------------------------------------------------------------- /lints/map/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | #![feature(iter_intersperse)] 5 | 6 | #[cfg(not(feature = "rlib"))] 7 | dylint_linting::dylint_library!(); 8 | 9 | extern crate rustc_errors; 10 | extern crate rustc_hir; 11 | extern crate rustc_lint; 12 | extern crate rustc_session; 13 | extern crate rustc_span; 14 | 15 | use rustc_errors::Applicability; 16 | use rustc_hir::{Expr, ExprKind}; 17 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 18 | use rustc_session::{declare_lint, declare_lint_pass}; 19 | use rustc_span::{sym, Symbol}; 20 | use utils::span_to_snippet_macro; 21 | 22 | mod builder; 23 | 24 | builder::map_collection!(MapVec, MAP_VEC, Vec, "push", "Vec"); 25 | builder::map_collection!(MapHashMap, MAP_HASHMAP, HashMap, "insert", "HashMap"); 26 | builder::map_collection!(MapHashSet, MAP_HASHSET, HashSet, "insert", "HashSet"); 27 | builder::map_collection!(MapBTreeMap, MAP_BTREEMAP, BTreeMap, "insert", "BTreeMap"); 28 | builder::map_collection!(MapBTreeSet, MAP_BTREESET, BTreeSet, "insert", "BTreeSet"); 29 | 30 | #[allow(clippy::no_mangle_with_rust_abi)] 31 | #[cfg_attr(not(feature = "rlib"), no_mangle)] 32 | 33 | pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { 34 | lint_store.register_late_pass(|_| Box::new(MapVec)); 35 | lint_store.register_late_pass(|_| Box::new(MapHashMap)); 36 | lint_store.register_late_pass(|_| Box::new(MapHashSet)); 37 | lint_store.register_late_pass(|_| Box::new(MapBTreeMap)); 38 | lint_store.register_late_pass(|_| Box::new(MapBTreeSet)); 39 | } 40 | 41 | #[test] 42 | fn ui() { 43 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 44 | } 45 | -------------------------------------------------------------------------------- /lints/map/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: implicit map 2 | --> $DIR/main.rs:13:5 3 | | 4 | LL | / numbers.iter().for_each(|&num| { 5 | LL | | data.push(num * 3); 6 | LL | | }); 7 | | |______^ help: try using `map` instead: `data.extend(numbers.iter().map(|&num| { num * 3 }).collect::>())` 8 | | 9 | = note: `#[warn(map_vec)]` on by default 10 | 11 | warning: implicit map 12 | --> $DIR/main.rs:25:5 13 | | 14 | LL | / numbers.iter().for_each(|&num| { 15 | LL | | data.insert(num, num.to_string()); 16 | LL | | }); 17 | | |______^ help: try using `map` instead: `data.extend(numbers.iter().map(|&num| { (num, num.to_string()) }).collect::>())` 18 | | 19 | = note: `#[warn(map_hashmap)]` on by default 20 | 21 | warning: implicit map 22 | --> $DIR/main.rs:37:5 23 | | 24 | LL | / numbers.iter().for_each(|&num| { 25 | LL | | data.insert(num); 26 | LL | | }); 27 | | |______^ help: try using `map` instead: `data.extend(numbers.iter().map(|&num| { num }).collect::>())` 28 | | 29 | = note: `#[warn(map_hashset)]` on by default 30 | 31 | warning: implicit map 32 | --> $DIR/main.rs:49:5 33 | | 34 | LL | / numbers.iter().for_each(|&num| { 35 | LL | | data.insert(num, num.to_string()); 36 | LL | | }); 37 | | |______^ help: try using `map` instead: `data.extend(numbers.iter().map(|&num| { (num, num.to_string()) }).collect::>())` 38 | | 39 | = note: `#[warn(map_btreemap)]` on by default 40 | 41 | warning: implicit map 42 | --> $DIR/main.rs:61:5 43 | | 44 | LL | / numbers.iter().for_each(|&num| { 45 | LL | | data.insert(num); 46 | LL | | }); 47 | | |______^ help: try using `map` instead: `data.extend(numbers.iter().map(|&num| { num }).collect::>())` 48 | | 49 | = note: `#[warn(map_btreeset)]` on by default 50 | 51 | warning: 5 warnings emitted 52 | 53 | -------------------------------------------------------------------------------- /lints/filter/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: implicit filter inside `for_each` 2 | --> $DIR/main.rs:12:18 3 | | 4 | LL | items.iter().for_each(|&item| { 5 | | __________________^ 6 | LL | | if item.starts_with('a') { 7 | LL | | one_string.push_str(item); 8 | LL | | } 9 | LL | | }); 10 | | |______^ help: try lifting the filter iterator: `filter(|&item| { item.starts_with('a') }).for_each(|&item| { one_string.push_str(item); })` 11 | | 12 | = note: `#[warn(filter_simple)]` on by default 13 | 14 | warning: implicit filter inside `for_each` 15 | --> $DIR/main.rs:23:20 16 | | 17 | LL | numbers.iter().for_each(|&num| { 18 | | ____________________^ 19 | LL | | if num % 2 == 0 { 20 | LL | | return; 21 | LL | | } 22 | LL | | sum += num; 23 | LL | | }); 24 | | |______^ help: try lifting the filter iterator: `filter(|&num| { !(num % 2 == 0) }).for_each(|&num| { sum += num; })` 25 | | 26 | = note: `#[warn(filter_simple_flipped)]` on by default 27 | 28 | warning: implicit filter inside `for_each` 29 | --> $DIR/main.rs:34:18 30 | | 31 | LL | items.iter().for_each(|&item| { 32 | | __________________^ 33 | LL | | if item.starts_with('a') { 34 | LL | | println!("Starts with 'a': {}", item); 35 | LL | | } 36 | LL | | }); 37 | | |______^ help: try lifting the filter iterator: `filter(|&item| { item.starts_with('a') }).for_each(|&item| { println!("Starts with 'a': {}", item) })` 38 | 39 | warning: implicit filter inside `for_each` 40 | --> $DIR/main.rs:44:20 41 | | 42 | LL | numbers.iter().for_each(|&num| { 43 | | ____________________^ 44 | LL | | if num % 2 == 0 { 45 | LL | | return; 46 | LL | | } 47 | LL | | println!("Odd number: {}", num); 48 | LL | | }); 49 | | |______^ help: try lifting the filter iterator: `filter(|&num| { !(num % 2 == 0) }).for_each(|&num| { println!("Odd number: {}", num) })` 50 | 51 | warning: 4 warnings emitted 52 | 53 | -------------------------------------------------------------------------------- /lints/for_each/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: use a for_each to enable iterator refinement 2 | --> $DIR/main.rs:43:5 3 | | 4 | LL | / for x in 1..=100 { 5 | LL | | println!("{x}"); 6 | LL | | } 7 | | |_____^ 8 | | 9 | = note: `#[warn(for_each)]` on by default 10 | help: try using `for_each` on the iterator 11 | | 12 | LL ~ (1..=100).into_iter().for_each(|x| { 13 | LL + println!("{x}"); 14 | LL + }); 15 | | 16 | 17 | warning: use a for_each to enable iterator refinement 18 | --> $DIR/main.rs:52:5 19 | | 20 | LL | / for a in vec_a { 21 | LL | | if a == 1 { 22 | LL | | continue; 23 | LL | | } 24 | LL | | dbg!(a); 25 | LL | | } 26 | | |_____^ 27 | | 28 | help: try using `for_each` on the iterator 29 | | 30 | LL ~ (vec_a).into_iter().for_each(|a| { 31 | LL + if a == 1 { 32 | LL + return; 33 | LL + } 34 | LL + dbg!(a); 35 | LL + }); 36 | | 37 | 38 | warning: use a for_each to enable iterator refinement 39 | --> $DIR/main.rs:78:9 40 | | 41 | LL | / for b in &vec_b { 42 | LL | | dbg!(a, b); 43 | LL | | } 44 | | |_________^ 45 | | 46 | help: try using `for_each` on the iterator 47 | | 48 | LL ~ (&vec_b).into_iter().for_each(|b| { 49 | LL + dbg!(a, b); 50 | LL + }); 51 | | 52 | 53 | warning: use a for_each to enable iterator refinement 54 | --> $DIR/main.rs:88:5 55 | | 56 | LL | / for _ in 0..some_num.len() { 57 | LL | | let (_, upload_size) = (true, 99); 58 | LL | | file_total_size += upload_size; 59 | LL | | } 60 | | |_____^ 61 | | 62 | help: try using `for_each` on the iterator 63 | | 64 | LL ~ (0..some_num.len()).into_iter().for_each(|_| { 65 | LL + let (_, upload_size) = (true, 99); 66 | LL + file_total_size += upload_size; 67 | LL + }); 68 | | 69 | 70 | warning: use a for_each to enable iterator refinement 71 | --> $DIR/main.rs:112:5 72 | | 73 | LL | / for _ in 0..thread_num { 74 | LL | | locals.push(LocalQueue::new()); 75 | LL | | } 76 | | |_____^ 77 | | 78 | help: try using `for_each` on the iterator 79 | | 80 | LL ~ (0..thread_num).into_iter().for_each(|_| { 81 | LL + locals.push(LocalQueue::new()); 82 | LL + }); 83 | | 84 | 85 | warning: 5 warnings emitted 86 | 87 | -------------------------------------------------------------------------------- /lints/for_each/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables)] 3 | 4 | fn main() {} 5 | 6 | struct MyBuilder { 7 | headers: Vec<(String, String)>, 8 | } 9 | 10 | impl MyBuilder { 11 | fn new() -> MyBuilder { 12 | MyBuilder { 13 | headers: Vec::new(), 14 | } 15 | } 16 | 17 | fn header(mut self, key: &str, value: &str) -> MyBuilder { 18 | self.headers.push((key.to_string(), value.to_string())); 19 | self 20 | } 21 | } 22 | 23 | struct LocalQueue {} 24 | 25 | impl LocalQueue { 26 | fn new() -> Self { 27 | Self {} 28 | } 29 | } 30 | 31 | // no 32 | fn build_request_builder() { 33 | let headers = vec![("Key1", "Value1"), ("Key2", "Value2")]; 34 | let mut request = MyBuilder::new(); 35 | 36 | for (key, value) in headers { 37 | request = request.header(key, value); 38 | } 39 | } 40 | 41 | // for_each 42 | fn just_loop() { 43 | for x in 1..=100 { 44 | println!("{x}"); 45 | } 46 | } 47 | 48 | // for_each 49 | fn loop_continue() { 50 | let vec_a = vec![1, 2, 3]; 51 | 52 | for a in vec_a { 53 | if a == 1 { 54 | continue; 55 | } 56 | dbg!(a); 57 | } 58 | } 59 | 60 | // no 61 | fn loop_break() { 62 | let vec_a = vec![1, 2, 3]; 63 | 64 | for a in vec_a { 65 | if a == 1 { 66 | break; 67 | } 68 | dbg!(a); 69 | } 70 | } 71 | 72 | // for_each internal 73 | fn nested_loop() { 74 | let vec_a = vec![1, 2, 3]; 75 | let vec_b = vec![1, 2, 3]; 76 | 77 | for a in vec_a { 78 | for b in &vec_b { 79 | dbg!(a, b); 80 | } 81 | } 82 | } 83 | 84 | // for_each 85 | fn get_upload_file_total_size() -> u64 { 86 | let some_num = vec![0; 10]; 87 | let mut file_total_size = 0; 88 | for _ in 0..some_num.len() { 89 | let (_, upload_size) = (true, 99); 90 | file_total_size += upload_size; 91 | } 92 | file_total_size 93 | } 94 | 95 | // no 96 | fn return_loop() { 97 | let num_workers = 10; 98 | let locals = vec![1, 2, 3, 4, 5]; 99 | for index in 0..num_workers { 100 | let item = locals.get(index); 101 | if !item.is_some() { 102 | return; 103 | } 104 | } 105 | } 106 | 107 | // for_each 108 | fn local_into_iter() { 109 | let thread_num = 10; 110 | let mut locals = vec![]; 111 | 112 | for _ in 0..thread_num { 113 | locals.push(LocalQueue::new()); 114 | } 115 | } 116 | 117 | // TODO: double capture 118 | -------------------------------------------------------------------------------- /lints/for_each/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables)] 3 | 4 | fn main() {} 5 | 6 | struct MyBuilder { 7 | headers: Vec<(String, String)>, 8 | } 9 | 10 | impl MyBuilder { 11 | fn new() -> MyBuilder { 12 | MyBuilder { 13 | headers: Vec::new(), 14 | } 15 | } 16 | 17 | fn header(mut self, key: &str, value: &str) -> MyBuilder { 18 | self.headers.push((key.to_string(), value.to_string())); 19 | self 20 | } 21 | } 22 | 23 | struct LocalQueue {} 24 | 25 | impl LocalQueue { 26 | fn new() -> Self { 27 | Self {} 28 | } 29 | } 30 | 31 | // no 32 | fn build_request_builder() { 33 | let headers = vec![("Key1", "Value1"), ("Key2", "Value2")]; 34 | let mut request = MyBuilder::new(); 35 | 36 | for (key, value) in headers { 37 | request = request.header(key, value); 38 | } 39 | } 40 | 41 | // for_each 42 | fn just_loop() { 43 | (1..=100).into_iter().for_each(|x| { 44 | println!("{x}"); 45 | }); 46 | } 47 | 48 | // for_each 49 | fn loop_continue() { 50 | let vec_a = vec![1, 2, 3]; 51 | 52 | (vec_a).into_iter().for_each(|a| { 53 | if a == 1 { 54 | return; 55 | } 56 | dbg!(a); 57 | }); 58 | } 59 | 60 | // no 61 | fn loop_break() { 62 | let vec_a = vec![1, 2, 3]; 63 | 64 | for a in vec_a { 65 | if a == 1 { 66 | break; 67 | } 68 | dbg!(a); 69 | } 70 | } 71 | 72 | // for_each internal 73 | fn nested_loop() { 74 | let vec_a = vec![1, 2, 3]; 75 | let vec_b = vec![1, 2, 3]; 76 | 77 | for a in vec_a { 78 | (&vec_b).into_iter().for_each(|b| { 79 | dbg!(a, b); 80 | }); 81 | } 82 | } 83 | 84 | // for_each 85 | fn get_upload_file_total_size() -> u64 { 86 | let some_num = vec![0; 10]; 87 | let mut file_total_size = 0; 88 | (0..some_num.len()).into_iter().for_each(|_| { 89 | let (_, upload_size) = (true, 99); 90 | file_total_size += upload_size; 91 | }); 92 | file_total_size 93 | } 94 | 95 | // no 96 | fn return_loop() { 97 | let num_workers = 10; 98 | let locals = vec![1, 2, 3, 4, 5]; 99 | for index in 0..num_workers { 100 | let item = locals.get(index); 101 | if !item.is_some() { 102 | return; 103 | } 104 | } 105 | } 106 | 107 | // for_each 108 | fn local_into_iter() { 109 | let thread_num = 10; 110 | let mut locals = vec![]; 111 | 112 | (0..thread_num).into_iter().for_each(|_| { 113 | locals.push(LocalQueue::new()); 114 | }); 115 | } 116 | 117 | // TODO: double capture 118 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mate" 3 | version = "0.1.6" 4 | authors = [ 5 | "Luca Carlig ", 7 | ] 8 | description = "library of lints for automatic parallelization" 9 | edition = "2021" 10 | publish = false 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | [dependencies] 17 | to_iter = { path = "lints/to_iter", features = ["rlib"] } 18 | for_each = { path = "lints/for_each", features = ["rlib"] } 19 | filter = { path = "lints/filter", features = ["rlib"] } 20 | map = { path = "lints/map", features = ["rlib"] } 21 | fold = { path = "lints/fold", features = ["rlib"] } 22 | par_fold = { path = "lints/par_fold", features = ["rlib"] } 23 | par_iter = { path = "lints/par_iter", features = ["rlib"] } 24 | rayon_imports = { path = "lints/rayon_imports", features = ["rlib"] } 25 | 26 | dylint_linting = { version = "3.2.1" } 27 | 28 | [package.metadata.rust-analyzer] 29 | rustc_private = true 30 | 31 | [workspace] 32 | members = [ 33 | "lints/rayon_imports", 34 | "lints/to_iter", 35 | "lints/for_each", 36 | "lints/filter", 37 | "lints/map", 38 | "lints/fold", 39 | "lints/par_fold", 40 | "lints/par_iter", 41 | "utils", 42 | ] 43 | 44 | [dev-dependencies] 45 | dylint_testing = "3.1.2" 46 | 47 | [workspace.dependencies] 48 | clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "37f4fbb92913586b73a35772efd00eccd1cbbe13" } 49 | utils = { path = "utils" } 50 | 51 | [workspace.lints.rust.unexpected_cfgs] 52 | level = "deny" 53 | check-cfg = ["cfg(dylint_lib, values(any()))"] 54 | 55 | [workspace.metadata.dylint] 56 | clibraries = [{ path = "../mate" }] 57 | 58 | [workspace.lints.clippy] 59 | # GROUPS 60 | perf = { level = "warn", priority = -1 } 61 | pedantic = { level = "warn", priority = -2 } 62 | correctness = { level = "forbid", priority = -3 } 63 | complexity = { level = "forbid", priority = -4 } 64 | 65 | 66 | # INDIVIDUAL LINTS 67 | semicolon_if_nothing_returned = { level = "allow", priority = 0 } 68 | similar_names = { level = "allow", priority = 0 } 69 | too_many_lines = { level = "allow", priority = 0 } 70 | alloc_instead_of_core = { level = "warn", priority = 0 } 71 | as_conversions = { level = "warn", priority = 0 } 72 | as_underscore = { level = "warn", priority = 0 } 73 | dbg_macro = { level = "warn", priority = 0 } 74 | default_numeric_fallback = { level = "warn", priority = 0 } 75 | deref_by_slicing = { level = "warn", priority = 0 } 76 | empty_enum_variants_with_brackets = { level = "warn", priority = 0 } 77 | empty_structs_with_brackets = { level = "warn", priority = 0 } 78 | expect_used = { level = "warn", priority = 0 } 79 | format_push_string = { level = "warn", priority = 0 } 80 | if_then_some_else_none = { level = "warn", priority = 0 } 81 | unwrap_used = { level = "warn", priority = 0 } 82 | -------------------------------------------------------------------------------- /.github/workflows/bench-test-github.yaml: -------------------------------------------------------------------------------- 1 | name: bench-test-github 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | project: 11 | [ 12 | BurntSushi/aho-corasick, 13 | rust-lang/rust-bindgen, 14 | dtolnay/cxx, 15 | rust-bakery/nom, 16 | sfackler/rust-openssl, 17 | serde-rs/serde, 18 | clap-rs/clap, 19 | tokio-rs/tracing, 20 | brendanzab/codespan, 21 | rust-lang/regex, 22 | dtolnay/syn, 23 | huggingface/candle, 24 | ] 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | repository: "${{ matrix.project }}" 29 | 30 | - name: Cache Cargo dependencies and binaries 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/.cargo 35 | key: ${{ runner.os }}-cargo-${{ matrix.project }}-v2 36 | restore-keys: | 37 | ${{ runner.os }}-cargo-${{ matrix.project }}-v2 38 | ${{ runner.os }}-cargo-${{ matrix.project }}- 39 | ${{ runner.os }}-cargo- 40 | 41 | - name: prepare repository for dylint 42 | run: | 43 | curl -o prepare_repo.sh https://raw.githubusercontent.com/trusted-programming/mate/main/scripts/prepare_repo.sh 44 | bash prepare_repo.sh 45 | 46 | - name: count occurrences before 47 | run: | 48 | curl -o counters.sh https://raw.githubusercontent.com/trusted-programming/mate/main/scripts/counters.sh 49 | bash counters.sh before 50 | 51 | - name: Lint fix (run 5 times) 52 | continue-on-error: true 53 | run: | 54 | for i in {1..5}; do 55 | cargo dylint --all --workspace --fix -- --allow-dirty --allow-no-vcs --broken-code --lib --bins 56 | done 57 | 58 | - name: cargo check 59 | run: cargo check 60 | 61 | - name: git diff for .rs files 62 | if: always() 63 | run: git diff -- '*.rs' 64 | 65 | - name: count occurrences after 66 | if: always() 67 | run: bash counters.sh after 68 | 69 | - name: Calculate and print differences 70 | if: always() 71 | run: | 72 | echo "TOTAL OVERVIEW OF RESULTS" 73 | echo "" 74 | echo "For loop count before: $for_loop_count_before" 75 | echo "For loop count after: $for_loop_count_after" 76 | echo "For loop count difference: $((for_loop_count_after - for_loop_count_before))" 77 | echo "" 78 | echo "Iter count before: $iter_count_before" 79 | echo "Iter count after: $iter_count_after" 80 | echo "Iter count difference: $((iter_count_after - iter_count_before))" 81 | echo "" 82 | echo "Par iter count before: $par_iter_count_before" 83 | echo "Par iter count after: $par_iter_count_after" 84 | echo "Par iter count difference: $((par_iter_count_after - par_iter_count_before))" 85 | -------------------------------------------------------------------------------- /.github/workflows/bench-test-gitee.yaml: -------------------------------------------------------------------------------- 1 | name: bench-test-gitee 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | project: 11 | [ 12 | commonlibrary_rust_ylong_http, 13 | commonlibrary_rust_ylong_json, 14 | commonlibrary_rust_ylong_runtime, 15 | communication_ipc, 16 | hiviewdfx_hisysevent, 17 | request_request, 18 | ] 19 | steps: 20 | - name: clone repo 21 | uses: nick-fields/retry@v3 22 | with: 23 | timeout_minutes: 5 24 | max_attempts: 3 25 | retry_on: error 26 | command: git clone https://gitee.com/openharmony/${{ matrix.project }}.git 27 | 28 | - name: Cache Cargo dependencies and binaries 29 | uses: actions/cache@v4 30 | with: 31 | path: | 32 | ~/.cargo 33 | key: ${{ runner.os }}-cargo-${{ matrix.project }}-v2 34 | restore-keys: | 35 | ${{ runner.os }}-cargo-${{ matrix.project }}-v2 36 | ${{ runner.os }}-cargo-${{ matrix.project }}- 37 | ${{ runner.os }}-cargo- 38 | 39 | - name: prepare repository for dylint 40 | working-directory: ./${{ matrix.project }} 41 | run: | 42 | curl -o prepare_repo.sh https://raw.githubusercontent.com/trusted-programming/mate/main/scripts/prepare_repo.sh 43 | bash prepare_repo.sh 44 | 45 | - name: count occurrences before 46 | working-directory: ./${{ matrix.project }} 47 | run: | 48 | curl -o counters.sh https://raw.githubusercontent.com/trusted-programming/mate/main/scripts/counters.sh 49 | bash counters.sh before 50 | 51 | - name: Lint fix (run 5 times) 52 | working-directory: ./${{ matrix.project }} 53 | continue-on-error: true 54 | run: | 55 | for i in {1..5}; do 56 | cargo dylint --all --workspace --fix -- --allow-dirty --allow-no-vcs --broken-code --lib --bins 57 | done 58 | 59 | - name: cargo check 60 | working-directory: ./${{ matrix.project }} 61 | run: cargo check 62 | 63 | - name: git diff for .rs files 64 | if: always() 65 | working-directory: ./${{ matrix.project }} 66 | run: git diff -- '*.rs' 67 | 68 | - name: count occurrences after 69 | if: always() 70 | working-directory: ./${{ matrix.project }} 71 | run: bash counters.sh after 72 | 73 | - name: Calculate and print differences 74 | if: always() 75 | run: | 76 | echo "TOTAL OVERVIEW OF RESULTS" 77 | echo "" 78 | echo "For loop count before: $for_loop_count_before" 79 | echo "For loop count after: $for_loop_count_after" 80 | echo "For loop count difference: $((for_loop_count_after - for_loop_count_before))" 81 | echo "" 82 | echo "Iter count before: $iter_count_before" 83 | echo "Iter count after: $iter_count_after" 84 | echo "Iter count difference: $((iter_count_after - iter_count_before))" 85 | echo "" 86 | echo "Par iter count before: $par_iter_count_before" 87 | echo "Par iter count after: $par_iter_count_after" 88 | echo "Par iter count difference: $((par_iter_count_after - par_iter_count_before))" 89 | -------------------------------------------------------------------------------- /lints/to_iter/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables)] 3 | 4 | fn main() {} 5 | 6 | struct MyBuilder { 7 | headers: Vec<(String, String)>, 8 | } 9 | 10 | impl MyBuilder { 11 | fn new() -> MyBuilder { 12 | MyBuilder { 13 | headers: Vec::new(), 14 | } 15 | } 16 | 17 | fn header(mut self, key: &str, value: &str) -> MyBuilder { 18 | self.headers.push((key.to_string(), value.to_string())); 19 | self 20 | } 21 | } 22 | 23 | struct LocalQueue {} 24 | 25 | impl LocalQueue { 26 | fn new() -> Self { 27 | Self {} 28 | } 29 | } 30 | 31 | // no 32 | fn build_request_builder() { 33 | let headers = vec![("Key1", "Value1"), ("Key2", "Value2")]; 34 | let mut request = MyBuilder::new(); 35 | 36 | for (key, value) in headers { 37 | request = request.header(key, value); 38 | } 39 | } 40 | // for_each 41 | fn just_loop() { 42 | for x in 1..=100 { 43 | println!("{x}"); 44 | } 45 | } 46 | 47 | // for_each 48 | fn loop_continue() { 49 | let vec_a = vec![1, 2, 3]; 50 | 51 | for a in vec_a { 52 | if a == 1 { 53 | continue; 54 | } 55 | dbg!(a); 56 | } 57 | } 58 | 59 | // no 60 | fn loop_break() { 61 | let vec_a = vec![1, 2, 3]; 62 | 63 | for a in vec_a { 64 | if a == 1 { 65 | break; 66 | } 67 | dbg!(a); 68 | } 69 | } 70 | 71 | // for_each internal 72 | fn nested_loop() { 73 | let vec_a = vec![1, 2, 3]; 74 | let vec_b = vec![1, 2, 3]; 75 | 76 | for a in vec_a { 77 | for b in &vec_b { 78 | dbg!(a, b); 79 | } 80 | } 81 | } 82 | 83 | // for_each 84 | fn get_upload_file_total_size() -> u64 { 85 | let some_num = vec![0; 10]; 86 | let mut file_total_size = 0; 87 | for _ in 0..some_num.len() { 88 | let (_, upload_size) = (true, 99); 89 | file_total_size += upload_size; 90 | } 91 | file_total_size 92 | } 93 | 94 | fn return_loop() -> Option<()> { 95 | let num_workers = 10; 96 | let locals = vec![1, 2, 3, 4, 5]; 97 | for index in 0..num_workers { 98 | let item = locals.get(index)?; 99 | } 100 | Some(()) 101 | } 102 | 103 | fn return_loop_expand() -> Option<()> { 104 | let num_workers = 10; 105 | let locals = vec![1, 2, 3, 4, 5]; 106 | for index in 0..num_workers { 107 | let item = locals.get(index); 108 | if item.is_none() { 109 | return None; 110 | } 111 | } 112 | Some(()) 113 | } 114 | 115 | fn return_loop_continue() -> Option<()> { 116 | let num_workers = 10; 117 | let locals = vec![1, 2, 3, 4, 5]; 118 | for index in 0..num_workers { 119 | if index == 1 { 120 | continue; 121 | } 122 | let item = locals.get(index)?; 123 | } 124 | Some(()) 125 | } 126 | 127 | // for_each 128 | fn local_into_iter() { 129 | let thread_num = 10; 130 | let mut locals = vec![]; 131 | 132 | for _ in 0..thread_num { 133 | locals.push(LocalQueue::new()); 134 | } 135 | } 136 | 137 | fn try_mut_var() -> Option { 138 | let char = 'c'; 139 | let esc = char.escape_debug(); 140 | for c in esc { 141 | if c.is_ascii() { 142 | return None; 143 | } 144 | } 145 | Some(char) 146 | } 147 | -------------------------------------------------------------------------------- /lints/rayon_imports/src/rayon_prelude.rs: -------------------------------------------------------------------------------- 1 | use rustc_hir::{intravisit::Visitor, Item, ItemKind, UseKind}; 2 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 3 | use rustc_session::{declare_lint, declare_lint_pass}; 4 | use rustc_span::{FileName, FileNameDisplayPreference}; 5 | 6 | declare_lint! { 7 | /// ### What it does 8 | /// Checks the rayon iterator trait are import. 9 | /// 10 | /// ### Why is this bad? 11 | /// Not importing can lead to missed opportunities for 12 | /// parallelization and performance optimization when using Rayon. 13 | /// 14 | /// ### Known problems 15 | /// This lint does not check if Rayon is actually used, so it might suggest 16 | /// imports that are unnecessary. but can be fixed using cargo fix. 17 | /// 18 | /// ### Example 19 | /// ``` 20 | /// use rayon::iter::{IntoParallelIterator, ParallelIterator}; 21 | /// ``` 22 | 23 | pub RAYON_PRELUDE, 24 | Warn, 25 | "check if use statements for iterators are present." 26 | } 27 | 28 | declare_lint_pass!(RayonPrelude => [RAYON_PRELUDE]); 29 | 30 | impl LateLintPass<'_> for RayonPrelude { 31 | fn check_mod( 32 | &mut self, 33 | cx: &LateContext<'_>, 34 | md: &'_ rustc_hir::Mod<'_>, 35 | _hir_id: rustc_hir::HirId, 36 | ) { 37 | // Skip linting if in build.rs file 38 | if let FileName::Real(f) = cx 39 | .sess() 40 | .source_map() 41 | .span_to_filename(cx.tcx.hir().root_module().spans.inner_span) 42 | { 43 | if f.to_string_lossy(FileNameDisplayPreference::Short) 44 | .ends_with("build.rs") 45 | { 46 | return; 47 | } 48 | } 49 | 50 | let hir = cx.tcx.hir(); 51 | let mut use_statement_visitor = UseStatementVisitor { has_import: false }; 52 | let module_item_ids = md.item_ids; 53 | for item_id in module_item_ids { 54 | let item = hir.item(*item_id); 55 | use_statement_visitor.visit_item(item); 56 | } 57 | 58 | // @todo remove #[allow(unused_imports)] and only add the import when it is going to be used 59 | let import_suggestion = "#[allow(unused_imports)]\nuse rayon::prelude::*;\n".to_string(); 60 | let inject_use_span = md.spans.inject_use_span; 61 | if !use_statement_visitor.has_import { 62 | cx.span_lint(RAYON_PRELUDE, inject_use_span, |diag| { 63 | diag.primary_message("rayon::prelude::* is not imported"); 64 | diag.span_suggestion( 65 | inject_use_span, 66 | "consider adding this import", 67 | import_suggestion, 68 | rustc_errors::Applicability::MachineApplicable, 69 | ); 70 | }); 71 | } 72 | } 73 | } 74 | 75 | struct UseStatementVisitor { 76 | has_import: bool, 77 | } 78 | 79 | impl<'v> Visitor<'v> for UseStatementVisitor { 80 | fn visit_item(&mut self, item: &'v Item<'v>) { 81 | if let ItemKind::Use(use_path, UseKind::Glob) = item.kind { 82 | let path: Vec = use_path 83 | .segments 84 | .to_vec() 85 | .iter() 86 | .map(|x| x.ident.to_string()) 87 | .collect(); 88 | 89 | let trait_path = vec!["rayon".to_string(), "prelude".to_string()]; 90 | self.has_import |= trait_path == path 91 | } 92 | // Continue walking the rest of the tree 93 | rustc_hir::intravisit::walk_item(self, item); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/verify.yaml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | on: [push] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 6 | cancel-in-progress: true 7 | 8 | env: 9 | toolchain: nightly-2024-07-25 10 | 11 | jobs: 12 | fmt: 13 | name: cargo fmt 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Cache Cargo dependencies and binaries 19 | uses: actions/cache@v4 20 | with: 21 | path: | 22 | ~/.cargo 23 | target 24 | key: ${{ runner.os }}-cargo-${{ env.toolchain }}-${{ hashFiles('**/Cargo.lock') }} 25 | restore-keys: | 26 | ${{ runner.os }}-cargo-${{ env.toolchain }}- 27 | ${{ runner.os }}-cargo- 28 | 29 | - name: Install Rust toolchain 30 | uses: dtolnay/rust-toolchain@master 31 | with: 32 | toolchain: ${{ env.toolchain }} 33 | components: rustfmt, clippy 34 | 35 | - name: Install dylint 36 | run: | 37 | if ! command -v cargo-dylint &> /dev/null || ! command -v dylint-link &> /dev/null; then 38 | cargo install cargo-dylint dylint-link 39 | fi 40 | 41 | - name: cargo fmt --check 42 | run: cargo fmt --check 43 | 44 | clippy: 45 | name: cargo clippy 46 | env: 47 | RUSTFLAGS: -D warnings 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - name: Cache Cargo dependencies and binaries 53 | uses: actions/cache@v4 54 | with: 55 | path: | 56 | ~/.cargo/registry 57 | ~/.cargo/git 58 | ~/.cargo/bin 59 | target 60 | key: ${{ runner.os }}-cargo-${{ env.toolchain }}-${{ hashFiles('**/Cargo.lock') }} 61 | restore-keys: | 62 | ${{ runner.os }}-cargo-${{ env.toolchain }}- 63 | ${{ runner.os }}-cargo- 64 | 65 | - name: Install Rust toolchain 66 | uses: dtolnay/rust-toolchain@master 67 | with: 68 | toolchain: ${{ env.toolchain }} 69 | components: rustfmt, clippy 70 | 71 | - name: Install dylint 72 | run: | 73 | if ! command -v cargo-dylint &> /dev/null || ! command -v dylint-link &> /dev/null; then 74 | cargo install cargo-dylint dylint-link 75 | fi 76 | 77 | - name: cargo clippy 78 | run: cargo clippy --lib --all-features 79 | 80 | test: 81 | name: cargo test 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | 86 | - name: Cache Cargo dependencies and binaries 87 | uses: actions/cache@v4 88 | with: 89 | path: | 90 | ~/.cargo 91 | target 92 | key: ${{ runner.os }}-cargo-${{ env.toolchain }}-${{ hashFiles('**/Cargo.lock') }} 93 | restore-keys: | 94 | ${{ runner.os }}-cargo-${{ env.toolchain }}- 95 | ${{ runner.os }}-cargo- 96 | 97 | - name: Install Rust toolchain 98 | uses: dtolnay/rust-toolchain@master 99 | with: 100 | toolchain: ${{ env.toolchain }} 101 | components: rustfmt, clippy 102 | 103 | - name: Install dylint 104 | run: | 105 | if ! command -v cargo-dylint &> /dev/null || ! command -v dylint-link &> /dev/null; then 106 | cargo install cargo-dylint dylint-link 107 | fi 108 | 109 | - name: cargo test 110 | run: cargo test --workspace 111 | -------------------------------------------------------------------------------- /lints/to_iter/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables)] 3 | 4 | fn main() {} 5 | 6 | struct MyBuilder { 7 | headers: Vec<(String, String)>, 8 | } 9 | 10 | impl MyBuilder { 11 | fn new() -> MyBuilder { 12 | MyBuilder { 13 | headers: Vec::new(), 14 | } 15 | } 16 | 17 | fn header(mut self, key: &str, value: &str) -> MyBuilder { 18 | self.headers.push((key.to_string(), value.to_string())); 19 | self 20 | } 21 | } 22 | 23 | struct LocalQueue {} 24 | 25 | impl LocalQueue { 26 | fn new() -> Self { 27 | Self {} 28 | } 29 | } 30 | 31 | // no 32 | fn build_request_builder() { 33 | let headers = vec![("Key1", "Value1"), ("Key2", "Value2")]; 34 | let mut request = MyBuilder::new(); 35 | 36 | for (key, value) in headers { 37 | request = request.header(key, value); 38 | } 39 | } 40 | // for_each 41 | fn just_loop() { 42 | (1..=100).into_iter().for_each(|x| { 43 | println!("{x}"); 44 | }); 45 | } 46 | 47 | // for_each 48 | fn loop_continue() { 49 | let vec_a = vec![1, 2, 3]; 50 | 51 | (vec_a).into_iter().for_each(|a| { 52 | dbg!(a); 53 | }); 54 | } 55 | 56 | // no 57 | fn loop_break() { 58 | let vec_a = vec![1, 2, 3]; 59 | 60 | for a in vec_a { 61 | if a == 1 { 62 | break; 63 | } 64 | dbg!(a); 65 | } 66 | } 67 | 68 | // for_each internal 69 | fn nested_loop() { 70 | let vec_a = vec![1, 2, 3]; 71 | let vec_b = vec![1, 2, 3]; 72 | 73 | for a in vec_a { 74 | (&vec_b).into_iter().for_each(|b| { 75 | dbg!(a, b); 76 | }); 77 | } 78 | } 79 | 80 | // for_each 81 | fn get_upload_file_total_size() -> u64 { 82 | let some_num = vec![0; 10]; 83 | let mut file_total_size = 0; 84 | (0..some_num.len()).into_iter().for_each(|_| { 85 | let (_, upload_size) = (true, 99); 86 | file_total_size += upload_size; 87 | }); 88 | file_total_size 89 | } 90 | 91 | fn return_loop() -> Option<()> { 92 | let num_workers = 10; 93 | let locals = vec![1, 2, 3, 4, 5]; 94 | (0..num_workers).into_iter().try_for_each(|index| { 95 | let item = locals.get(index)?; 96 | return Some(()); 97 | })?; 98 | Some(()) 99 | } 100 | 101 | fn return_loop_expand() -> Option<()> { 102 | let num_workers = 10; 103 | let locals = vec![1, 2, 3, 4, 5]; 104 | (0..num_workers).into_iter().try_for_each(|index| { 105 | let item = locals.get(index); 106 | if item.is_none() { 107 | return None; 108 | } 109 | return Some(()); 110 | })?; 111 | Some(()) 112 | } 113 | 114 | fn return_loop_continue() -> Option<()> { 115 | let num_workers = 10; 116 | let locals = vec![1, 2, 3, 4, 5]; 117 | (0..num_workers).into_iter().try_for_each(|index| { 118 | if index == 1 { 119 | return Some(()); 120 | } 121 | let item = locals.get(index)?; 122 | return Some(()); 123 | })?; 124 | Some(()) 125 | } 126 | 127 | // for_each 128 | fn local_into_iter() { 129 | let thread_num = 10; 130 | let mut locals = vec![]; 131 | 132 | (0..thread_num).into_iter().for_each(|_| { 133 | locals.push(LocalQueue::new()); 134 | }); 135 | } 136 | 137 | fn try_mut_var() -> Option { 138 | let char = 'c'; 139 | let mut esc = char.escape_debug(); 140 | (esc).try_for_each(|c| { 141 | if c.is_ascii() { 142 | return None; 143 | } 144 | return Some(()); 145 | })?; 146 | Some(char) 147 | } 148 | -------------------------------------------------------------------------------- /utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![allow(clippy::result_unit_err)] 3 | 4 | extern crate rustc_driver; 5 | extern crate rustc_hash; 6 | extern crate rustc_hir; 7 | extern crate rustc_hir_typeck; 8 | extern crate rustc_infer; 9 | extern crate rustc_lint; 10 | extern crate rustc_middle; 11 | extern crate rustc_span; 12 | extern crate rustc_trait_selection; 13 | 14 | use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; 15 | use rustc_span::source_map::SourceMap; 16 | use rustc_span::{Span, SyntaxContext}; 17 | 18 | pub fn is_local_def(stmt: &Stmt) -> bool { 19 | match stmt.kind { 20 | StmtKind::Let(_) => true, 21 | StmtKind::Expr(e) | StmtKind::Semi(e) => { 22 | if let ExprKind::Block(b, _) = e.kind { 23 | b.stmts.iter().all(is_local_def) && b.expr.is_none() 24 | } else { 25 | false 26 | } 27 | } 28 | StmtKind::Item(_) => false, 29 | } 30 | } 31 | 32 | #[must_use] 33 | pub fn get_pat_expr_and_spans<'a>( 34 | expr: &'a Expr<'a>, 35 | ) -> Option<(Option<&'a Expr<'a>>, Option, Option)> { 36 | let mut local_defs_span = None; 37 | let mut body_span = None; 38 | let pat_expr = if let ExprKind::Block(block, _) = &expr.kind { 39 | if block.stmts.is_empty() { 40 | block.expr 41 | } else { 42 | let mut local_defs = vec![]; 43 | let mut body = vec![]; 44 | let mut add_locals = true; 45 | for s in block.stmts { 46 | if is_local_def(s) & add_locals { 47 | local_defs.push(s.span); 48 | } else { 49 | add_locals = false; 50 | body.push(s); 51 | } 52 | } 53 | if !local_defs.is_empty() { 54 | let fst_span = local_defs[0]; 55 | let lst_span = local_defs[local_defs.len() - 1]; 56 | local_defs_span = Some(fst_span.to(lst_span)); 57 | } 58 | if body.is_empty() { 59 | block.expr 60 | } else { 61 | match body.remove(0).kind { 62 | StmtKind::Expr(e) | StmtKind::Semi(e) => { 63 | if body.is_empty() { 64 | body_span = block.expr.map(|e| e.span); 65 | } else { 66 | let fst_span = body[0].span; 67 | let lst_span = match block.expr { 68 | None => body[body.len() - 1].span, 69 | Some(e) => e.span, 70 | }; 71 | body_span = Some(fst_span.to(lst_span)); 72 | } 73 | Some(e) 74 | } 75 | _ => return None, 76 | } 77 | } 78 | } 79 | } else { 80 | Some(expr) 81 | }; 82 | Some((pat_expr, local_defs_span, body_span)) 83 | } 84 | 85 | pub fn span_to_snippet_macro(src_map: &SourceMap, span: Span) -> String { 86 | if span.ctxt() == SyntaxContext::root() { 87 | // It's not a macro, proceed as usual 88 | src_map 89 | .span_to_snippet(span) 90 | .unwrap_or_else(|_| String::new()) 91 | } else { 92 | // TODO: Handle the macro case 93 | // The combined_span originates from a macro expansion 94 | // You might need a different approach to handle this case 95 | let callsite_span = span.source_callsite(); 96 | src_map 97 | .span_to_snippet(callsite_span) 98 | .unwrap_or_else(|_| String::new()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lints/filter/src/simple.rs: -------------------------------------------------------------------------------- 1 | use rustc_errors::Applicability; 2 | use rustc_hir::{Expr, ExprKind}; 3 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 | use rustc_session::{declare_lint, declare_lint_pass}; 5 | use rustc_span::symbol::Symbol; 6 | use utils::span_to_snippet_macro; 7 | 8 | declare_lint! { 9 | pub FILTER_SIMPLE, 10 | Warn, 11 | "suggest using explicit filter iterator" 12 | } 13 | 14 | declare_lint_pass!(FilterSimple => [FILTER_SIMPLE]); 15 | impl<'tcx> LateLintPass<'tcx> for FilterSimple { 16 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 17 | // TODO: extract to helper function 18 | let hir_map = cx.tcx.hir(); 19 | if let ExprKind::MethodCall(seg, _recv, args, span) = &expr.kind 20 | && seg.ident.name == Symbol::intern("for_each") 21 | { 22 | // A trait check is required here to make sure we are calling 23 | // the Iterator::for_each method instead of some other for_each method. 24 | // For now we just check argument count. 25 | assert_eq!(args.len(), 1); 26 | 27 | // Extract the for_each closure. 28 | let ExprKind::Closure(for_each_cls) = &args[0].kind else { 29 | return; 30 | }; 31 | let cls_body = hir_map.body(for_each_cls.body); 32 | 33 | // Collect a set of local definitions, the expression we wish to analyze and 34 | // the statements following it 35 | let Some((Some(pat_expr), local_defs_span, body_span)) = 36 | utils::get_pat_expr_and_spans(cls_body.value) 37 | else { 38 | return; 39 | }; 40 | 41 | // We should only have one statement left 42 | if body_span.is_some() { 43 | return; 44 | } 45 | 46 | // Check for a single branched if. 47 | let ExprKind::If(cond, then, None) = &pat_expr.kind else { 48 | return; 49 | }; 50 | if let ExprKind::Let(_) = cond.kind { 51 | return; 52 | } 53 | 54 | let src_map = cx.sess().source_map(); 55 | let ExprKind::Block(then_block, _) = then.kind else { 56 | return; 57 | }; 58 | let then_snip = if then_block.stmts.is_empty() { 59 | then_block 60 | .expr 61 | .map_or(String::new(), |e| span_to_snippet_macro(src_map, e.span)) 62 | } else { 63 | let fst_span = then_block.stmts[0].span; 64 | let lst_span = match then_block.expr { 65 | None => then_block.stmts[then_block.stmts.len() - 1].span, 66 | Some(e) => e.span, 67 | }; 68 | span_to_snippet_macro(src_map, fst_span.to(lst_span)) 69 | }; 70 | 71 | let local_defs_snip = 72 | local_defs_span.map_or(String::new(), |sp| span_to_snippet_macro(src_map, sp)); 73 | 74 | let pat_snip = if cls_body.params.is_empty() { 75 | String::new() 76 | } else { 77 | let fst_span = cls_body.params[0].span; 78 | let lst_span = cls_body.params[cls_body.params.len() - 1].span; 79 | span_to_snippet_macro(src_map, fst_span.to(lst_span)) 80 | }; 81 | 82 | let cond_snip = span_to_snippet_macro(src_map, cond.span); 83 | let suggestion = format!("filter(|{pat_snip}| {{ {local_defs_snip} {cond_snip} }}).for_each(|{pat_snip}| {{ {local_defs_snip} {then_snip} }})"); 84 | cx.span_lint(FILTER_SIMPLE, *span, |diag| { 85 | diag.primary_message("implicit filter inside `for_each`"); 86 | diag.span_suggestion( 87 | *span, 88 | "try lifting the filter iterator", 89 | suggestion, 90 | Applicability::MachineApplicable, 91 | ); 92 | }); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lints/par_fold/src/par_fold_simple.rs: -------------------------------------------------------------------------------- 1 | use rustc_errors::Applicability; 2 | use rustc_hir::{Expr, ExprKind, HirId}; 3 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 | use rustc_middle::ty::TyKind; 5 | use rustc_session::{declare_lint, declare_lint_pass}; 6 | use rustc_span::{Span, Symbol}; 7 | use utils::span_to_snippet_macro; 8 | 9 | declare_lint! { 10 | pub WARN_PAR_FOLD_SIMPLE, 11 | Warn, 12 | "suggest using parallel fold" 13 | } 14 | 15 | declare_lint_pass!(ParFoldSimple => [WARN_PAR_FOLD_SIMPLE]); 16 | impl<'tcx> LateLintPass<'tcx> for ParFoldSimple { 17 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 18 | if let ExprKind::MethodCall(path, recv, args, _span) = &expr.kind 19 | && path.ident.name == Symbol::intern("fold") 20 | { 21 | assert_eq!(args.len(), 2); 22 | let id_expr = args[0]; 23 | let op_expr = args[1]; 24 | 25 | // Are we in the reduce case? 26 | // i.e. closure arg types are equal 27 | let ExprKind::Closure(_op_cls) = op_expr.kind else { 28 | return; 29 | }; 30 | let type_chk = cx.tcx.typeck(expr.hir_id.owner.def_id); 31 | let op_ty = type_chk.node_type(op_expr.hir_id); 32 | let TyKind::Closure(_, io_tys) = op_ty.kind() else { 33 | return; 34 | }; 35 | 36 | // TODO: Check how robust this is. 37 | let op_cls_arg_tys = io_tys 38 | .as_closure() 39 | .sig() 40 | .input(0) 41 | .skip_binder() 42 | .tuple_fields(); 43 | assert_eq!(op_cls_arg_tys.len(), 2); 44 | if op_cls_arg_tys[0] != op_cls_arg_tys[1] { 45 | return; 46 | } 47 | 48 | let mut ir = IterRenaming::new(); 49 | ir.traverse_iter_chain(recv); 50 | 51 | let src_map = cx.sess().source_map(); 52 | let id_snip = span_to_snippet_macro(src_map, id_expr.span); 53 | 54 | let suggestion = "reduce".to_string(); 55 | let suggestion2 = format!("|| {id_snip}"); 56 | ir.suggestions 57 | .extend_from_slice(&[(path.ident.span, suggestion), (id_expr.span, suggestion2)]); 58 | 59 | cx.span_lint(WARN_PAR_FOLD_SIMPLE, expr.span, |diag| { 60 | diag.primary_message("sequential fold"); 61 | diag.multipart_suggestion( 62 | "try using a parallel fold on the iterator", 63 | ir.suggestions, 64 | Applicability::MachineApplicable, 65 | ); 66 | }); 67 | } 68 | } 69 | } 70 | 71 | // Traverse an iterator chain and rename all occurrences 72 | // of sequential iterator calls to parallel ones. 73 | struct IterRenaming { 74 | suggestions: Vec<(Span, String)>, 75 | seen: Vec, 76 | } 77 | 78 | impl IterRenaming { 79 | fn new() -> Self { 80 | IterRenaming { 81 | suggestions: vec![], 82 | seen: vec![], 83 | } 84 | } 85 | 86 | fn traverse_iter_chain(&mut self, expr: &Expr) { 87 | if self.seen.contains(&expr.hir_id) { 88 | return; 89 | } 90 | self.seen.push(expr.hir_id); 91 | 92 | if let ExprKind::MethodCall(path, recv, args, _span) = &expr.kind { 93 | // TODO: Optimize this. 94 | let seq_names = vec![ 95 | Symbol::intern("iter"), 96 | Symbol::intern("iter_mut"), 97 | Symbol::intern("into_iter"), 98 | ]; 99 | let par_names = vec!["par_iter", "par_iter_mut", "into_par_iter"]; 100 | for (sm, pm) in seq_names.into_iter().zip(par_names.into_iter()) { 101 | if path.ident.name == sm { 102 | self.suggestions.push((path.ident.span, pm.to_string())); 103 | break; 104 | } 105 | } 106 | self.traverse_iter_chain(recv); 107 | args.iter().for_each(|e| self.traverse_iter_chain(e)); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lints/to_iter/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: use an iterator 2 | --> $DIR/main.rs:42:5 3 | | 4 | LL | / for x in 1..=100 { 5 | LL | | println!("{x}"); 6 | LL | | } 7 | | |_____^ 8 | | 9 | = note: `#[warn(to_iter)]` on by default 10 | help: try using an iterator 11 | | 12 | LL ~ (1..=100).into_iter().for_each(|x| { 13 | LL + println!("{x}"); 14 | LL + }); 15 | | 16 | 17 | warning: use an iterator 18 | --> $DIR/main.rs:51:5 19 | | 20 | LL | / for a in vec_a { 21 | LL | | if a == 1 { 22 | LL | | continue; 23 | LL | | } 24 | LL | | dbg!(a); 25 | LL | | } 26 | | |_____^ 27 | | 28 | help: try using an iterator 29 | | 30 | LL ~ (vec_a).into_iter().for_each(|a| { 31 | LL + dbg!(a); 32 | LL + }); 33 | | 34 | 35 | warning: use an iterator 36 | --> $DIR/main.rs:77:9 37 | | 38 | LL | / for b in &vec_b { 39 | LL | | dbg!(a, b); 40 | LL | | } 41 | | |_________^ 42 | | 43 | help: try using an iterator 44 | | 45 | LL ~ (&vec_b).into_iter().for_each(|b| { 46 | LL + dbg!(a, b); 47 | LL + }); 48 | | 49 | 50 | warning: use an iterator 51 | --> $DIR/main.rs:87:5 52 | | 53 | LL | / for _ in 0..some_num.len() { 54 | LL | | let (_, upload_size) = (true, 99); 55 | LL | | file_total_size += upload_size; 56 | LL | | } 57 | | |_____^ 58 | | 59 | help: try using an iterator 60 | | 61 | LL ~ (0..some_num.len()).into_iter().for_each(|_| { 62 | LL + let (_, upload_size) = (true, 99); 63 | LL + file_total_size += upload_size; 64 | LL + }); 65 | | 66 | 67 | warning: use an iterator 68 | --> $DIR/main.rs:97:5 69 | | 70 | LL | / for index in 0..num_workers { 71 | LL | | let item = locals.get(index)?; 72 | LL | | } 73 | | |_____^ 74 | | 75 | help: try using an iterator 76 | | 77 | LL ~ (0..num_workers).into_iter().try_for_each(|index| { 78 | LL + let item = locals.get(index)?; 79 | LL + return Some(()); 80 | LL + })?; 81 | | 82 | 83 | warning: use an iterator 84 | --> $DIR/main.rs:106:5 85 | | 86 | LL | / for index in 0..num_workers { 87 | LL | | let item = locals.get(index); 88 | LL | | if item.is_none() { 89 | LL | | return None; 90 | LL | | } 91 | LL | | } 92 | | |_____^ 93 | | 94 | help: try using an iterator 95 | | 96 | LL ~ (0..num_workers).into_iter().try_for_each(|index| { 97 | LL + let item = locals.get(index); 98 | LL + if item.is_none() { 99 | LL + return None; 100 | LL + } 101 | LL + return Some(()); 102 | LL + })?; 103 | | 104 | 105 | warning: use an iterator 106 | --> $DIR/main.rs:118:5 107 | | 108 | LL | / for index in 0..num_workers { 109 | LL | | if index == 1 { 110 | LL | | continue; 111 | LL | | } 112 | LL | | let item = locals.get(index)?; 113 | LL | | } 114 | | |_____^ 115 | | 116 | help: try using an iterator 117 | | 118 | LL ~ (0..num_workers).into_iter().try_for_each(|index| { 119 | LL + if index == 1 { 120 | LL + return Some(()); 121 | LL + } 122 | LL + let item = locals.get(index)?; 123 | LL + return Some(()); 124 | LL + })?; 125 | | 126 | 127 | warning: use an iterator 128 | --> $DIR/main.rs:132:5 129 | | 130 | LL | / for _ in 0..thread_num { 131 | LL | | locals.push(LocalQueue::new()); 132 | LL | | } 133 | | |_____^ 134 | | 135 | help: try using an iterator 136 | | 137 | LL ~ (0..thread_num).into_iter().for_each(|_| { 138 | LL + locals.push(LocalQueue::new()); 139 | LL + }); 140 | | 141 | 142 | warning: use an iterator 143 | --> $DIR/main.rs:140:5 144 | | 145 | LL | / for c in esc { 146 | LL | | if c.is_ascii() { 147 | LL | | return None; 148 | LL | | } 149 | LL | | } 150 | | |_____^ 151 | | 152 | help: try using an iterator 153 | | 154 | LL ~ let mut esc = char.escape_debug(); 155 | LL ~ (esc).try_for_each(|c| { 156 | LL + if c.is_ascii() { 157 | LL + return None; 158 | LL + } 159 | LL + return Some(()); 160 | LL + })?; 161 | | 162 | 163 | warning: 9 warnings emitted 164 | 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mate 2 | 3 | Mate is a linting library for the Rust programming language with a focus on identifying opportunities for parallelization in code. It aims to help developers leverage the power of parallel computing by providing automated suggestions for optimizing their Rust applications. 4 | 5 | ## Features 6 | 7 | - **Automatic Detection**: Mate scans your codebase to find potential areas where parallelization can be applied. 8 | - **Optimization Suggestions**: Provides recommendations on how to modify your code to take advantage of parallel execution. 9 | - **Easy Integration**: Seamlessly integrates with your existing Rust projects and tooling. 10 | - **Automated Suggestions Application**: Mate can automatically implement its parallelization recommendations in your code through rustfix, streamlining the optimization process. 11 | 12 | ## Lints 13 | 14 | - for_each 15 | - filter_simple 16 | - filter_simple_flipped 17 | - fold_simple 18 | - fold_vec 19 | - fold_hashmap 20 | - par_fold_simple 21 | - par_fold_vec 22 | - rayon_prelude 23 | - par_iter 24 | 25 | ## Warnings 26 | 27 | often switching from an iterator to a parallel iterator will result in loss of ordering: 28 | 29 | ```rust 30 | // will print in order from 0 to 100 31 | (0..100).into_iter().for_each(|x| println!("{:?}", x)); // 0 1 2 3 ... 32 | // will print in a random order depending on threads 33 | (0..100).into_par_iter().for_each(|x| println!("{:?}", x)); // 56 87 37 88 ... 34 | ``` 35 | 36 | ## How to run 37 | 38 | The next three steps install Dylint and run all of this repository's lints on a workspace: 39 | prerequisites: - rustup - latest version of rust stable if not run `rustup update` 40 | 41 | 1. Install `cargo-dylint` and `dylint-link`: 42 | 43 | ```sh 44 | cargo install cargo-dylint dylint-link 45 | ``` 46 | 47 | 2. Add the following to the workspace's `Cargo.toml` file: 48 | 49 | ```toml 50 | [workspace.metadata.dylint] 51 | libraries = [ 52 | { git = "https://github.com/trusted-programming/mate"}, 53 | ] 54 | ``` 55 | 56 | 3. Run `cargo-dylint` for linting: 57 | ```sh 58 | # lint only 59 | cargo dylint --all --workspace 60 | # lint and fix, if code breaks(not compiling) it will revert to original 61 | cargo dylint --all --workspace --fix -- --allow-dirty --allow-no-vcs 62 | # lint and fix ignoring errors if there are any 63 | cargo dylint --all --workspace --fix -- --allow-dirty --allow-no-vcs --broken-code 64 | # count warnings by type 65 | cargo dylint --workspace --all 2>&1 | grep -i 'warning' | grep -iv 'generated' | sort | uniq -c | sort -nr 66 | ``` 67 | 68 | In the above example, the libraries are found via [workspace metadata], which is the recommended way. For additional ways of finding libraries, see [How Dylint works]. 69 | 70 | ## Running Tests 71 | 72 | To execute tests in a Rust project, use the following commands: 73 | 74 | ```sh 75 | # Run all tests in the workspace 76 | cargo test --workspace 77 | 78 | # Run tests in a specific nested crate 79 | cargo test -p 80 | ``` 81 | 82 | Replace `` with the name of the nested crate for which you want to run the tests. 83 | 84 | For more information on testing in Rust, refer to the [Rust Book's section on testing](https://doc.rust-lang.org/book/ch11-00-testing.html). 85 | 86 | ### VS Code integration 87 | 88 | Dylint results can be viewed in VS Code using [rust-analyzer]. To do so, add the following to your VS Code `settings.json` file: 89 | 90 | ```json 91 | "rust-analyzer.checkOnSave.overrideCommand": [ 92 | "cargo", 93 | "dylint", 94 | "--all", 95 | "--workspace", 96 | "--", 97 | "--all-targets", 98 | "--message-format=json" 99 | ] 100 | ``` 101 | 102 | If you want to use rust-analyzer inside a lint library, you need to add the following to your VS Code `settings.json` file: 103 | 104 | ```json 105 | "rust-analyzer.rustc.source": "discover", 106 | ``` 107 | 108 | And add the following to the library's `Cargo.toml` file: 109 | 110 | ```toml 111 | [package.metadata.rust-analyzer] 112 | rustc_private = true 113 | ``` 114 | 115 | ## benchmarks 116 | 117 | - https://gitee.com/openharmony/request_request 118 | - https://gitee.com/openharmony/commonlibrary_rust_ylong_runtime 119 | - https://gitee.com/organizations/openharmony/projects?lang=Rust 120 | -------------------------------------------------------------------------------- /lints/par_iter/ui/main.stderr: -------------------------------------------------------------------------------- 1 | warning: found iterator that can be parallelized 2 | --> $DIR/main.rs:64:5 3 | | 4 | LL | (0..100).into_iter().for_each(|x| println!("{:?}", x)); 5 | | ^^^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `(0..100).into_par_iter()` 6 | | 7 | = note: `#[warn(par_iter)]` on by default 8 | 9 | warning: found iterator that can be parallelized 10 | --> $DIR/main.rs:88:5 11 | | 12 | LL | / (0..100) 13 | LL | | .into_iter() 14 | | |____________________^ 15 | | 16 | help: try using a parallel iterator 17 | | 18 | LL ~ (0..100) 19 | LL + .into_par_iter() 20 | | 21 | 22 | warning: found iterator that can be parallelized 23 | --> $DIR/main.rs:123:5 24 | | 25 | LL | list.into_iter().for_each(|x| println!("{:?}", x)); 26 | | ^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `list.into_par_iter()` 27 | 28 | warning: found iterator that can be parallelized 29 | --> $DIR/main.rs:139:5 30 | | 31 | LL | (0..10).into_iter().for_each(|x| { 32 | | ^^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `(0..10).into_par_iter()` 33 | 34 | warning: found iterator that can be parallelized 35 | --> $DIR/main.rs:222:5 36 | | 37 | LL | data.iter() 38 | | ^^^^^^^^^^^ help: try using a parallel iterator: `data.par_iter()` 39 | 40 | warning: found iterator that can be parallelized 41 | --> $DIR/main.rs:249:5 42 | | 43 | LL | numbers.iter().enumerate().for_each(|t| { 44 | | ^^^^^^^^^^^^^^ help: try using a parallel iterator: `numbers.par_iter()` 45 | 46 | warning: found iterator that can be parallelized 47 | --> $DIR/main.rs:345:30 48 | | 49 | LL | let names: Vec = people.iter().map(|p| p.name.clone()).collect(); 50 | | ^^^^^^^^^^^^^ help: try using a parallel iterator: `people.par_iter()` 51 | 52 | warning: found iterator that can be parallelized 53 | --> $DIR/main.rs:401:19 54 | | 55 | LL | let buf = bufs 56 | | ___________________^ 57 | LL | | .iter() 58 | | |___________________^ 59 | | 60 | help: try using a parallel iterator 61 | | 62 | LL ~ let buf = bufs 63 | LL + .par_iter() 64 | | 65 | 66 | warning: found iterator that can be parallelized 67 | --> $DIR/main.rs:417:29 68 | | 69 | LL | let required: Vec = used_filtered 70 | | _____________________________^ 71 | LL | | .iter() 72 | | |_______________^ 73 | | 74 | help: try using a parallel iterator 75 | | 76 | LL ~ let required: Vec = used_filtered 77 | LL + .par_iter() 78 | | 79 | 80 | warning: found iterator that can be parallelized 81 | --> $DIR/main.rs:420:25 82 | | 83 | LL | .flat_map(|arg| arg.requires.iter().map(|item| &item.1)) 84 | | ^^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `arg.requires.par_iter()` 85 | 86 | warning: found iterator that can be parallelized 87 | --> $DIR/main.rs:422:16 88 | | 89 | LL | .chain(used_filtered.iter()) 90 | | ^^^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `used_filtered.par_iter()` 91 | 92 | warning: found iterator that can be parallelized 93 | --> $DIR/main.rs:446:38 94 | | 95 | LL | let names_over_30: Vec = people 96 | | ______________________________________^ 97 | LL | | .iter() 98 | | |_______________^ 99 | | 100 | help: try using a parallel iterator 101 | | 102 | LL ~ let names_over_30: Vec = people 103 | LL + .par_iter() 104 | | 105 | 106 | warning: found iterator that can be parallelized 107 | --> $DIR/main.rs:464:5 108 | | 109 | LL | numbers.iter_mut().for_each(|num| *num *= 2); // Double each number 110 | | ^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `numbers.par_iter_mut()` 111 | 112 | warning: found iterator that can be parallelized 113 | --> $DIR/main.rs:471:37 114 | | 115 | LL | let doubled_numbers: Vec = numbers 116 | | _____________________________________^ 117 | LL | | .into_iter() 118 | | |____________________^ 119 | | 120 | help: try using a parallel iterator 121 | | 122 | LL ~ let doubled_numbers: Vec = numbers 123 | LL + .into_par_iter() 124 | | 125 | 126 | warning: found iterator that can be parallelized 127 | --> $DIR/main.rs:486:5 128 | | 129 | LL | (0..num_workers).into_iter().try_for_each(|index| { 130 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using a parallel iterator: `(0..num_workers).into_par_iter()` 131 | 132 | warning: 15 warnings emitted 133 | 134 | -------------------------------------------------------------------------------- /lints/fold/src/simple.rs: -------------------------------------------------------------------------------- 1 | use rustc_errors::Applicability; 2 | use rustc_hir::{BinOpKind, Expr, ExprKind}; 3 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 | use rustc_session::{declare_lint, declare_lint_pass}; 5 | use rustc_span::Symbol; 6 | use utils::span_to_snippet_macro; 7 | 8 | declare_lint! { 9 | /// ### What it does 10 | /// 11 | /// ### Why is this bad? 12 | /// 13 | /// ### Known problems 14 | /// Remove if none. 15 | /// 16 | /// ### Example 17 | /// ```rust 18 | /// // example code where a warning is issued 19 | /// ``` 20 | /// Use instead: 21 | /// ```rust 22 | /// // example code that does not raise a warning 23 | /// ``` 24 | pub FOLD_SIMPLE, 25 | Warn, 26 | "suggest using explicit fold" 27 | } 28 | declare_lint_pass!(FoldSimple => [FOLD_SIMPLE]); 29 | 30 | enum MonoidType { 31 | Mul, 32 | Add, 33 | } 34 | 35 | impl<'tcx> LateLintPass<'tcx> for FoldSimple { 36 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 37 | // TODO: extract to helper function 38 | let hir_map = cx.tcx.hir(); 39 | // See notes in phase2/simple.rs for limitations here. 40 | if let ExprKind::MethodCall(seg, recv, args, _span) = &expr.kind 41 | && seg.ident.name == Symbol::intern("for_each") 42 | { 43 | assert_eq!(args.len(), 1); 44 | 45 | let ExprKind::Closure(for_each_cls) = &args[0].kind else { 46 | return; 47 | }; 48 | let cls_body = hir_map.body(for_each_cls.body); 49 | 50 | // Collect a set of local definitions, the expression we wish to analyze and 51 | // the statements following it 52 | let Some((Some(pat_expr), local_defs_span, body_span)) = 53 | utils::get_pat_expr_and_spans(cls_body.value) 54 | else { 55 | return; 56 | }; 57 | 58 | // We should only have one statement left 59 | if body_span.is_some() { 60 | return; 61 | } 62 | 63 | // Match an assign operator expression 64 | let ExprKind::AssignOp(op, lhs, rhs) = &pat_expr.kind else { 65 | return; 66 | }; 67 | 68 | // Is the operator additive or multiplicative. 69 | // This effects the choice of identity. 70 | let mon_ty = match op.node { 71 | BinOpKind::Add | BinOpKind::Sub | BinOpKind::BitXor | BinOpKind::BitOr => { 72 | MonoidType::Add 73 | } 74 | BinOpKind::Mul | BinOpKind::BitAnd => MonoidType::Mul, 75 | _ => return, 76 | }; 77 | 78 | // Type check the accumulated parameter and assign the correct identity. 79 | let lhs_ty = cx.tcx.typeck(lhs.hir_id.owner.def_id).node_type(lhs.hir_id); 80 | let id_snip = if lhs_ty.is_integral() { 81 | match mon_ty { 82 | MonoidType::Add => "0", 83 | MonoidType::Mul => "1", 84 | } 85 | } else if lhs_ty.is_bool() { 86 | match mon_ty { 87 | MonoidType::Add => "false", 88 | MonoidType::Mul => "true", 89 | } 90 | } else { 91 | return; 92 | }; 93 | 94 | let src_map = cx.sess().source_map(); 95 | let recv_snip = span_to_snippet_macro(src_map, recv.span); 96 | let local_defs_snip = 97 | local_defs_span.map_or(String::new(), |sp| span_to_snippet_macro(src_map, sp)); 98 | let pat_span = cls_body.params[0] 99 | .span 100 | .to(cls_body.params[cls_body.params.len() - 1].span); 101 | let pat_snip = span_to_snippet_macro(src_map, pat_span); 102 | let rhs_snip = span_to_snippet_macro(src_map, rhs.span); 103 | let op_snip = span_to_snippet_macro(src_map, op.span); 104 | let lhs_snip = span_to_snippet_macro(src_map, lhs.span); 105 | let suggestion = format!("{lhs_snip} {op_snip} {recv_snip}.map(|{pat_snip}| {{{local_defs_snip} {rhs_snip}}}).fold({id_snip}, |mut {lhs_snip}, v| {{ {lhs_snip} {op_snip} v; {lhs_snip} }})"); 106 | 107 | cx.span_lint(FOLD_SIMPLE, expr.span, |diag| { 108 | diag.primary_message("implicit fold"); 109 | diag.span_suggestion( 110 | expr.span, 111 | "try using `fold` instead", 112 | suggestion, 113 | Applicability::MachineApplicable, 114 | ); 115 | }); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lints/filter/src/simple_flipped.rs: -------------------------------------------------------------------------------- 1 | use rustc_errors::Applicability; 2 | use rustc_hir::{Expr, ExprKind, StmtKind}; 3 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 | use rustc_session::{declare_lint, declare_lint_pass}; 5 | use rustc_span::symbol::Symbol; 6 | use utils::span_to_snippet_macro; 7 | 8 | declare_lint! { 9 | pub FILTER_SIMPLE_FLIPPED, 10 | Warn, 11 | "suggest using explicit filter iterator" 12 | } 13 | 14 | declare_lint_pass!(FilterSimpleFlipped => [FILTER_SIMPLE_FLIPPED]); 15 | 16 | impl<'tcx> LateLintPass<'tcx> for FilterSimpleFlipped { 17 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 18 | let hir_map = cx.tcx.hir(); 19 | if let ExprKind::MethodCall(seg, _recv, args, span) = &expr.kind 20 | && seg.ident.name == Symbol::intern("for_each") 21 | { 22 | // A trait check is required here to make sure we are calling 23 | // the Iterator::for_each method instead of some other for_each method. 24 | // For now we just check argument count. 25 | assert_eq!(args.len(), 1); 26 | 27 | // Extract the for_each closure. 28 | let ExprKind::Closure(for_each_cls) = &args[0].kind else { 29 | return; 30 | }; 31 | let cls_body = hir_map.body(for_each_cls.body); 32 | 33 | // Collect a set of local definitions, the expression we wish to analyze and 34 | // the statements following it 35 | let Some((Some(pat_expr), local_defs_span, body_span)) = 36 | utils::get_pat_expr_and_spans(cls_body.value) 37 | else { 38 | return; 39 | }; 40 | 41 | // Check for an if with early return in the success branch. 42 | let ExprKind::If(cond, fail, then) = &pat_expr.kind else { 43 | return; 44 | }; 45 | let ExprKind::Block(fail_block, _) = fail.kind else { 46 | return; 47 | }; 48 | if fail_block.stmts.is_empty() { 49 | return; 50 | } 51 | let StmtKind::Semi(fail_e) = fail_block.stmts[0].kind else { 52 | return; 53 | }; 54 | let ExprKind::Ret(_) = fail_e.kind else { 55 | return; 56 | }; 57 | 58 | let src_map = cx.sess().source_map(); 59 | let body_snip = 60 | body_span.map_or(String::new(), |sp| span_to_snippet_macro(src_map, sp)); 61 | 62 | let then_snip = { 63 | match then { 64 | Some(then) => { 65 | let ExprKind::Block(then_block, _) = then.kind else { 66 | return; 67 | }; 68 | if then_block.stmts.is_empty() { 69 | then_block 70 | .expr 71 | .map_or(String::new(), |e| span_to_snippet_macro(src_map, e.span)) 72 | } else { 73 | let fst_span = then_block.stmts[0].span; 74 | let lst_span = match then_block.expr { 75 | None => then_block.stmts[then_block.stmts.len() - 1].span, 76 | Some(e) => e.span, 77 | }; 78 | span_to_snippet_macro(src_map, fst_span.to(lst_span)) 79 | } 80 | } 81 | None => String::new(), 82 | } 83 | }; 84 | 85 | let local_defs_snip = 86 | local_defs_span.map_or(String::new(), |sp| span_to_snippet_macro(src_map, sp)); 87 | 88 | let pat_snip = if cls_body.params.is_empty() { 89 | String::new() 90 | } else { 91 | let fst_span = cls_body.params[0].span; 92 | let lst_span = cls_body.params[cls_body.params.len() - 1].span; 93 | span_to_snippet_macro(src_map, fst_span.to(lst_span)) 94 | }; 95 | 96 | let cond_snip = span_to_snippet_macro(src_map, cond.span); 97 | let suggestion = format!("filter(|{pat_snip}| {{ {local_defs_snip} !({cond_snip}) }}).for_each(|{pat_snip}| {{ {local_defs_snip} {then_snip} {body_snip} }})"); 98 | cx.span_lint(FILTER_SIMPLE_FLIPPED, *span, |diag| { 99 | diag.primary_message("implicit filter inside `for_each`"); 100 | diag.span_suggestion( 101 | *span, 102 | "try lifting the filter iterator", 103 | suggestion, 104 | Applicability::MachineApplicable, 105 | ); 106 | }); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lints/map/src/builder.rs: -------------------------------------------------------------------------------- 1 | macro_rules! map_collection { 2 | ($struct_name:ident, $lint_name:ident, $type_symbol:ident, $method_name:literal, $type_name:literal) => { 3 | 4 | declare_lint! { 5 | /// ### What it does 6 | /// 7 | /// ### Why is this bad? 8 | /// 9 | /// ### Known problems 10 | /// Remove if none. 11 | /// 12 | /// ### Example 13 | /// ```rust 14 | /// // example code where a warning is issued 15 | /// ``` 16 | /// Use instead: 17 | /// ```rust 18 | /// // example code that does not raise a warning 19 | /// ``` 20 | pub $lint_name, 21 | Warn, 22 | "suggest using a map/collect" 23 | } 24 | 25 | declare_lint_pass!($struct_name => [$lint_name]); 26 | 27 | impl<'tcx> LateLintPass<'tcx> for $struct_name { 28 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 29 | /* 30 | * Intended pattern 31 | * recv.for_each(|pat| { local_defs; c.collection_insert(v); }) 32 | * ---> 33 | * c.extend(recv.map(|pat| { local_defs; v })) 34 | */ 35 | 36 | if let ExprKind::MethodCall(seg, recv, arg, _) = &expr.kind 37 | && seg.ident.name == Symbol::intern("for_each") 38 | { 39 | // for_each will only ever have one argument, and it's a closure 40 | assert_eq!(arg.len(), 1); 41 | let ExprKind::Closure(for_each_cls) = &arg[0].kind else { 42 | return; 43 | }; 44 | 45 | let hir_map = cx.tcx.hir(); 46 | let cls_body = hir_map.body(for_each_cls.body); 47 | 48 | // Collect a set of local definitions, the expression we wish to analyze and 49 | // the statements following it 50 | let Some((Some(pat_expr), local_defs_span, body_span)) = 51 | utils::get_pat_expr_and_spans(cls_body.value) 52 | else { 53 | return; 54 | }; 55 | 56 | // We should only have one statement left 57 | if body_span.is_some() { 58 | return; 59 | } 60 | 61 | // Type check the accumulated parameter and assign the correct identity. 62 | // Attempting to match 'c.push(v);' 63 | 64 | let ExprKind::MethodCall(seg, coll, args, _) = &pat_expr.kind else { 65 | return; 66 | }; 67 | 68 | // Collection update method 69 | if seg.ident.name != Symbol::intern($method_name) { 70 | return; 71 | } 72 | // Make sure the receiver is a variable/path to variable. 73 | let ExprKind::Path(_) = coll.kind else { 74 | return; 75 | }; 76 | 77 | // Make sure the method is actually the collection method 78 | let ty = cx 79 | .tcx 80 | .typeck(coll.hir_id.owner.def_id) 81 | .node_type(coll.hir_id); 82 | let Some(adt) = ty.ty_adt_def() else { 83 | return; 84 | }; 85 | if !cx.tcx.is_diagnostic_item(sym::$type_symbol, adt.did()) { 86 | return; 87 | } 88 | 89 | // Suggestion creation 90 | let src_map = cx.sess().source_map(); 91 | let recv = span_to_snippet_macro(src_map, recv.span); 92 | let coll = span_to_snippet_macro(src_map, coll.span); 93 | let local_defs = 94 | local_defs_span.map_or(String::new(), |sp| span_to_snippet_macro(src_map, sp)); 95 | 96 | let type_args = std::iter::repeat("_").take(args.len()).intersperse(",").collect::(); 97 | 98 | let args_span = args[0] 99 | .span 100 | .to(args[args.len() - 1].span); 101 | let args = { 102 | let snip = span_to_snippet_macro(src_map, args_span); 103 | if args.len() > 1 { 104 | format!("({snip})") 105 | } else { snip } 106 | }; 107 | 108 | let pat_span = cls_body.params[0] 109 | .span 110 | .to(cls_body.params[cls_body.params.len() - 1].span); 111 | let pat = span_to_snippet_macro(src_map, pat_span); 112 | 113 | let suggestion = 114 | format!("{coll}.extend({recv}.map(|{pat}| {{ {local_defs} {args} }}).collect::<{}<{type_args}>>())", $type_name); 115 | 116 | cx.span_lint($lint_name, expr.span, |diag| { 117 | diag.primary_message("implicit map"); 118 | diag.span_suggestion( 119 | expr.span, 120 | "try using `map` instead", 121 | suggestion, 122 | Applicability::MachineApplicable, 123 | ); 124 | }); 125 | } 126 | } 127 | } 128 | 129 | };} 130 | 131 | pub(crate) use map_collection; 132 | -------------------------------------------------------------------------------- /lints/for_each/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | #![feature(unwrap_infallible)] 5 | 6 | extern crate rustc_errors; 7 | extern crate rustc_hash; 8 | extern crate rustc_hir; 9 | extern crate rustc_hir_typeck; 10 | extern crate rustc_middle; 11 | 12 | mod variable_check; 13 | 14 | use clippy_utils::higher::ForLoop; 15 | use rustc_errors::Applicability; 16 | use rustc_hir::{ 17 | intravisit::{walk_expr, Visitor}, 18 | Expr, ExprKind, 19 | }; 20 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 21 | use utils::span_to_snippet_macro; 22 | use variable_check::check_variables; 23 | 24 | dylint_linting::declare_late_lint! { 25 | /// ### What it does 26 | /// parallelize iterators using rayon 27 | /// ### Why is this bad? 28 | /// parallel iters are often faster 29 | /// ### Known problems 30 | /// lots 31 | /// 32 | /// ### Example 33 | /// ```rust 34 | /// // example code where a warning is issued 35 | /// ``` 36 | /// Use instead: 37 | /// ```rust 38 | /// // example code that does not raise a warning 39 | /// ``` 40 | pub FOR_EACH, 41 | Warn, 42 | "suggest using for each" 43 | } 44 | 45 | impl<'tcx> LateLintPass<'tcx> for ForEach { 46 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 47 | if let Some(ForLoop { 48 | pat, 49 | arg, 50 | body, 51 | loop_id: _loop_id, 52 | span: _span, 53 | }) = ForLoop::hir(expr) 54 | { 55 | let src_map = cx.sess().source_map(); 56 | 57 | // Make sure we ignore cases that require a try_foreach 58 | let mut validator = Validator { 59 | is_valid: true, 60 | has_continue: false, 61 | }; 62 | validator.visit_expr(body); 63 | if !validator.is_valid || !check_variables(cx, body) { 64 | return; 65 | } 66 | // Check whether the iter is explicit 67 | // NOTE: since this is a syntax only check we are bound to miss cases. 68 | let mut explorer = IterExplorer::default(); 69 | explorer.visit_expr(arg); 70 | let mc_snip: String = explorer.to_snip(); 71 | 72 | let iter_snip = span_to_snippet_macro(src_map, arg.span); 73 | let pat_snip = span_to_snippet_macro(src_map, pat.span); 74 | let mut body_snip = span_to_snippet_macro(src_map, body.span); 75 | 76 | // TODO: this needs to be improved 77 | if validator.has_continue { 78 | body_snip = body_snip.replace("continue", "return"); 79 | } 80 | cx.span_lint(FOR_EACH, expr.span, |diag| { 81 | diag.primary_message("use a for_each to enable iterator refinement"); 82 | diag.multipart_suggestion( 83 | "try using `for_each` on the iterator", 84 | vec![( 85 | expr.span, 86 | format!("({iter_snip}){mc_snip}.for_each(|{pat_snip}| {body_snip});"), 87 | )], 88 | Applicability::MachineApplicable, 89 | ); 90 | }); 91 | } 92 | } 93 | } 94 | 95 | #[derive(Default)] 96 | struct IterExplorer { 97 | is_iter: bool, 98 | } 99 | 100 | impl IterExplorer { 101 | fn to_snip(&self) -> String { 102 | if self.is_iter { 103 | String::new() 104 | } else { 105 | ".into_iter()".to_string() 106 | } 107 | } 108 | } 109 | 110 | impl Visitor<'_> for IterExplorer { 111 | fn visit_expr(&mut self, ex: &'_ Expr) { 112 | if let ExprKind::MethodCall(path, expr, _expr_list, _span) = &ex.kind { 113 | // Get method identifier 114 | let mid = path.ident; 115 | // Check if it's an iter method 116 | // In theory, we could check all iter method names here. 117 | // Perhaps a hashset could be used. 118 | match mid.as_str() { 119 | "into_iter" | "iter" | "iter_mut" => self.is_iter = true, 120 | _ => {} 121 | } 122 | self.visit_expr(expr); 123 | } 124 | } 125 | } 126 | 127 | struct Validator { 128 | is_valid: bool, 129 | has_continue: bool, 130 | } 131 | 132 | impl Visitor<'_> for Validator { 133 | fn visit_expr(&mut self, ex: &Expr) { 134 | match &ex.kind { 135 | ExprKind::Loop(_, _, _, _) 136 | | ExprKind::Closure(_) 137 | | ExprKind::Ret(_) 138 | | ExprKind::Break(_, _) => self.is_valid = false, 139 | ExprKind::Continue(_) => self.has_continue = true, 140 | _ => walk_expr(self, ex), 141 | } 142 | } 143 | } 144 | 145 | #[test] 146 | fn ui() { 147 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 148 | } 149 | -------------------------------------------------------------------------------- /lints/for_each/src/variable_check.rs: -------------------------------------------------------------------------------- 1 | use clippy_utils::visitors::for_each_expr; 2 | use rustc_hash::FxHashSet; 3 | use rustc_hir::{self as hir, def_id::LocalDefId}; 4 | use rustc_hir_typeck::expr_use_visitor::{self as euv}; 5 | use rustc_lint::LateContext; 6 | use rustc_middle::{ 7 | mir::FakeReadCause, 8 | ty::{self, Ty, UpvarId, UpvarPath}, 9 | }; 10 | use std::{collections::HashSet, ops::ControlFlow}; 11 | 12 | pub struct MutablyUsedVariablesCtxt<'tcx> { 13 | all_vars: FxHashSet>, 14 | copy_vars: FxHashSet>, 15 | prev_bind: Option, 16 | /// In async functions, the inner AST is composed of multiple layers until we reach the code 17 | /// defined by the user. Because of that, some variables are marked as mutably borrowed even 18 | /// though they're not. This field lists the `HirId` that should not be considered as mutable 19 | /// use of a variable. 20 | prev_move_to_closure: hir::HirIdSet, 21 | } 22 | 23 | // TODO: remove repetation is this two function almost identical 24 | pub fn check_variables<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx hir::Expr) -> bool { 25 | let MutablyUsedVariablesCtxt { 26 | mut all_vars, 27 | copy_vars, 28 | .. 29 | } = { 30 | let body_owner = ex.hir_id.owner.def_id; 31 | 32 | let mut ctx = MutablyUsedVariablesCtxt { 33 | all_vars: FxHashSet::default(), 34 | copy_vars: FxHashSet::default(), 35 | prev_bind: None, 36 | prev_move_to_closure: hir::HirIdSet::default(), 37 | }; 38 | 39 | euv::ExprUseVisitor::for_clippy(cx, body_owner, &mut ctx) 40 | .walk_expr(ex) 41 | .into_ok(); 42 | let mut checked_closures = FxHashSet::default(); 43 | 44 | // We retrieve all the closures declared in the function because they will not be found 45 | // by `euv::Delegate`. 46 | let mut closures: FxHashSet = FxHashSet::default(); 47 | for_each_expr(cx, ex, |expr| { 48 | if let hir::ExprKind::Closure(closure) = expr.kind { 49 | closures.insert(closure.def_id); 50 | } 51 | ControlFlow::<()>::Continue(()) 52 | }); 53 | check_closures(&mut ctx, cx, &mut checked_closures, closures); 54 | 55 | ctx 56 | }; 57 | all_vars.retain(|var| !copy_vars.contains(var)); 58 | all_vars.is_empty() 59 | } 60 | 61 | pub fn check_closures<'tcx, S: ::std::hash::BuildHasher>( 62 | ctx: &mut MutablyUsedVariablesCtxt<'tcx>, 63 | cx: &LateContext<'tcx>, 64 | checked_closures: &mut HashSet, 65 | closures: HashSet, 66 | ) { 67 | let hir = cx.tcx.hir(); 68 | for closure in closures { 69 | if !checked_closures.insert(closure) { 70 | continue; 71 | } 72 | ctx.prev_bind = None; 73 | ctx.prev_move_to_closure.clear(); 74 | if let Some(body) = cx 75 | .tcx 76 | .hir_node_by_def_id(closure) 77 | .associated_body() 78 | .map(|(_, body_id)| hir.body(body_id)) 79 | { 80 | euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx) 81 | .consume_body(body) 82 | .into_ok(); 83 | } 84 | } 85 | } 86 | 87 | impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { 88 | #[allow(clippy::if_same_then_else)] 89 | fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: hir::HirId) { 90 | if let euv::Place { 91 | base: 92 | euv::PlaceBase::Local(_) 93 | | euv::PlaceBase::Upvar(UpvarId { 94 | var_path: UpvarPath { hir_id: _ }, 95 | .. 96 | }), 97 | base_ty, 98 | .. 99 | } = &cmt.place 100 | { 101 | self.all_vars.insert(*base_ty); 102 | } 103 | } 104 | 105 | #[allow(clippy::if_same_then_else)] 106 | fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: hir::HirId, _: ty::BorrowKind) {} 107 | 108 | fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _id: hir::HirId) {} 109 | fn copy(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: hir::HirId) { 110 | if let euv::Place { 111 | base: 112 | euv::PlaceBase::Local(_) 113 | | euv::PlaceBase::Upvar(UpvarId { 114 | var_path: UpvarPath { hir_id: _ }, 115 | .. 116 | }), 117 | base_ty, 118 | .. 119 | } = &cmt.place 120 | { 121 | self.copy_vars.insert(*base_ty); 122 | } 123 | } 124 | fn fake_read( 125 | &mut self, 126 | _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, 127 | _: FakeReadCause, 128 | _id: hir::HirId, 129 | ) { 130 | } 131 | 132 | fn bind(&mut self, _: &euv::PlaceWithHirId<'tcx>, id: hir::HirId) { 133 | self.prev_bind = Some(id); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lints/to_iter/src/variable_check.rs: -------------------------------------------------------------------------------- 1 | use clippy_utils::visitors::for_each_expr; 2 | use rustc_hash::FxHashSet; 3 | use rustc_hir as hir; 4 | use rustc_hir_typeck::expr_use_visitor as euv; 5 | use rustc_lint::LateContext; 6 | use rustc_middle::{ 7 | mir::FakeReadCause, 8 | ty::{self, Ty, UpvarId, UpvarPath}, 9 | }; 10 | use rustc_span::def_id::LocalDefId; 11 | use std::{collections::HashSet, ops::ControlFlow}; 12 | 13 | pub struct UsedVariablesCtxt<'tcx> { 14 | pub all_vars: FxHashSet>, 15 | pub copy_vars: FxHashSet>, 16 | mut_vars: FxHashSet>, 17 | borrow_vars: FxHashSet>, 18 | prev_bind: Option, 19 | /// In async functions, the inner AST is composed of multiple layers until we reach the code 20 | /// defined by the user. Because of that, some variables are marked as mutably borrowed even 21 | /// though they're not. This field lists the `HirId` that should not be considered as mutable 22 | /// use of a variable. 23 | prev_move_to_closure: hir::HirIdSet, 24 | } 25 | 26 | // TODO: remove repetation is this two function almost identical 27 | pub fn check_variables<'tcx>( 28 | cx: &LateContext<'tcx>, 29 | ex: &'tcx hir::Expr, 30 | ) -> UsedVariablesCtxt<'tcx> { 31 | let body_owner = ex.hir_id.owner.def_id; 32 | 33 | let mut ctx = UsedVariablesCtxt { 34 | all_vars: FxHashSet::default(), 35 | copy_vars: FxHashSet::default(), 36 | borrow_vars: FxHashSet::default(), 37 | mut_vars: FxHashSet::default(), 38 | prev_bind: None, 39 | prev_move_to_closure: hir::HirIdSet::default(), 40 | }; 41 | 42 | euv::ExprUseVisitor::for_clippy(cx, body_owner, &mut ctx) 43 | .walk_expr(ex) 44 | .into_ok(); 45 | 46 | let mut checked_closures = FxHashSet::default(); 47 | 48 | // We retrieve all the closures declared in the function because they will not be found 49 | // by `euv::Delegate`. 50 | let mut closures: FxHashSet = FxHashSet::default(); 51 | for_each_expr(cx, ex, |expr| { 52 | if let hir::ExprKind::Closure(closure) = expr.kind { 53 | closures.insert(closure.def_id); 54 | } 55 | ControlFlow::<()>::Continue(()) 56 | }); 57 | check_closures(&mut ctx, cx, &mut checked_closures, closures); 58 | 59 | ctx 60 | } 61 | 62 | pub fn check_closures<'tcx, S: ::std::hash::BuildHasher>( 63 | ctx: &mut UsedVariablesCtxt<'tcx>, 64 | cx: &LateContext<'tcx>, 65 | checked_closures: &mut HashSet, 66 | closures: HashSet, 67 | ) { 68 | let hir = cx.tcx.hir(); 69 | for closure in closures { 70 | if !checked_closures.insert(closure) { 71 | continue; 72 | } 73 | ctx.prev_bind = None; 74 | ctx.prev_move_to_closure.clear(); 75 | if let Some(body) = cx 76 | .tcx 77 | .hir_node_by_def_id(closure) 78 | .associated_body() 79 | .map(|(_, body_id)| hir.body(body_id)) 80 | { 81 | euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx) 82 | .consume_body(body) 83 | .into_ok(); 84 | } 85 | } 86 | } 87 | 88 | impl<'tcx> euv::Delegate<'tcx> for UsedVariablesCtxt<'tcx> { 89 | #[allow(clippy::if_same_then_else)] 90 | fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: hir::HirId) { 91 | if let euv::Place { 92 | base: 93 | euv::PlaceBase::Local(_) 94 | | euv::PlaceBase::Upvar(UpvarId { 95 | var_path: UpvarPath { hir_id: _ }, 96 | .. 97 | }), 98 | base_ty, 99 | .. 100 | } = &cmt.place 101 | { 102 | self.all_vars.insert(*base_ty); 103 | } 104 | } 105 | 106 | #[allow(clippy::if_same_then_else)] 107 | fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: hir::HirId, _: ty::BorrowKind) { 108 | if let euv::Place { 109 | base: 110 | euv::PlaceBase::Local(_) 111 | | euv::PlaceBase::Upvar(UpvarId { 112 | var_path: UpvarPath { hir_id: _ }, 113 | .. 114 | }), 115 | base_ty, 116 | .. 117 | } = &cmt.place 118 | { 119 | self.borrow_vars.insert(*base_ty); 120 | } 121 | } 122 | 123 | fn mutate(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: hir::HirId) { 124 | if let euv::Place { 125 | base: 126 | euv::PlaceBase::Local(_) 127 | | euv::PlaceBase::Upvar(UpvarId { 128 | var_path: UpvarPath { hir_id: _ }, 129 | .. 130 | }), 131 | base_ty, 132 | .. 133 | } = &cmt.place 134 | { 135 | self.mut_vars.insert(*base_ty); 136 | } 137 | } 138 | fn copy(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: hir::HirId) { 139 | if let euv::Place { 140 | base: 141 | euv::PlaceBase::Local(_) 142 | | euv::PlaceBase::Upvar(UpvarId { 143 | var_path: UpvarPath { hir_id: _ }, 144 | .. 145 | }), 146 | base_ty, 147 | .. 148 | } = &cmt.place 149 | { 150 | self.copy_vars.insert(*base_ty); 151 | } 152 | } 153 | fn fake_read( 154 | &mut self, 155 | _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, 156 | _: FakeReadCause, 157 | _id: hir::HirId, 158 | ) { 159 | } 160 | 161 | fn bind(&mut self, _: &euv::PlaceWithHirId<'tcx>, id: hir::HirId) { 162 | self.prev_bind = Some(id); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lints/par_iter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | #![feature(unwrap_infallible)] 5 | 6 | extern crate rustc_data_structures; 7 | extern crate rustc_errors; 8 | extern crate rustc_hash; 9 | extern crate rustc_hir; 10 | extern crate rustc_hir_typeck; 11 | extern crate rustc_infer; 12 | extern crate rustc_middle; 13 | extern crate rustc_span; 14 | extern crate rustc_trait_selection; 15 | 16 | mod constants; 17 | mod variable_check; 18 | 19 | use clippy_utils::{get_parent_expr, get_trait_def_id}; 20 | use rustc_data_structures::fx::FxHashSet; 21 | use rustc_errors::Applicability; 22 | use rustc_hir::intravisit::{walk_expr, Visitor}; 23 | use rustc_hir::{self as hir}; 24 | use rustc_infer::infer::TyCtxtInferExt; 25 | use rustc_infer::traits::{Obligation, ObligationCause}; 26 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 27 | use rustc_middle::ty::{self, GenericArgs}; 28 | use rustc_span::sym; 29 | use rustc_trait_selection::traits::ObligationCtxt; 30 | use variable_check::{ 31 | check_implements_par_iter, check_trait_impl, check_variables, generate_suggestion, 32 | is_type_valid, 33 | }; 34 | 35 | dylint_linting::declare_late_lint! { 36 | /// ### What it does 37 | /// parallelize iterators using rayon 38 | /// ### Why is this bad? 39 | /// parallel iters are often faster 40 | /// ### Known problems 41 | /// lots 42 | /// changing to par iterators will cause the loss of ordering 43 | /// ### Example 44 | /// ```rust 45 | /// (0..100).into_iter().for_each(|x| println!("{:?}", x)); 46 | /// ``` 47 | /// Use instead: 48 | /// ```rust 49 | /// use rayon::iter::*; 50 | /// 51 | /// (0..100).into_par_iter().for_each(|x| println!("{:?}", x)); 52 | /// ``` 53 | pub PAR_ITER, 54 | Warn, 55 | "suggest using par iter" 56 | } 57 | 58 | impl<'tcx> LateLintPass<'tcx> for ParIter { 59 | // TODO: implement check crate to check if rayon is present 60 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { 61 | if let hir::ExprKind::MethodCall(path, recv, _args, _span) = &expr.kind 62 | && let Some(suggestion) = generate_suggestion(cx, expr, path) 63 | { 64 | let par_iter_traits = check_implements_par_iter(cx, recv); 65 | if !par_iter_traits.is_empty() && is_type_valid(cx, cx.typeck_results().expr_ty(recv)) { 66 | // TODO: issue with into_par_iter() need to check directly with 67 | // parallel iterator 68 | 69 | let mut allowed_methods: FxHashSet<&str> = 70 | ["into_iter", "iter", "iter_mut", "map_or"] 71 | .into_iter() 72 | .collect(); 73 | for into_par_iter_trait in par_iter_traits { 74 | allowed_methods.extend(get_all_methods(cx, into_par_iter_trait, recv)); 75 | } 76 | 77 | let mut top_expr = *recv; 78 | let mut found_iter_method = false; 79 | let mut is_mut = false; 80 | 81 | while let Some(parent_expr) = get_parent_expr(cx, top_expr) { 82 | match parent_expr.kind { 83 | hir::ExprKind::MethodCall(method_name, _, _, _) => { 84 | if ["into_iter", "iter", "iter_mut"] 85 | .contains(&method_name.ident.as_str()) 86 | { 87 | if found_iter_method { 88 | break; 89 | } 90 | found_iter_method = true; 91 | if method_name.ident.as_str() == "iter_mut" { 92 | is_mut = true; 93 | } 94 | } 95 | if !allowed_methods.contains(method_name.ident.as_str()) { 96 | return; 97 | } 98 | top_expr = parent_expr; 99 | } 100 | hir::ExprKind::Closure(_) => top_expr = parent_expr, 101 | _ => break, 102 | } 103 | } 104 | 105 | // TODO: find a way to deal with iterators returns 106 | if check_trait_impl(cx, cx.typeck_results().expr_ty(top_expr), sym::Iterator) { 107 | return; 108 | } 109 | 110 | let mut validator = Validator { 111 | cx, 112 | is_valid: true, 113 | is_mut, 114 | }; 115 | validator.visit_expr(top_expr); 116 | if !validator.is_valid { 117 | return; 118 | } 119 | 120 | cx.span_lint(PAR_ITER, expr.span, |diag| { 121 | diag.primary_message("found iterator that can be parallelized"); 122 | diag.multipart_suggestion( 123 | "try using a parallel iterator", 124 | vec![(expr.span, suggestion)], 125 | Applicability::MachineApplicable, 126 | ); 127 | }); 128 | } 129 | } 130 | } 131 | } 132 | 133 | struct Validator<'a, 'tcx> { 134 | cx: &'a LateContext<'tcx>, 135 | is_valid: bool, 136 | is_mut: bool, 137 | } 138 | 139 | impl<'a, 'tcx> hir::intravisit::Visitor<'_> for Validator<'a, 'tcx> { 140 | fn visit_expr(&mut self, ex: &hir::Expr) { 141 | if let hir::ExprKind::MethodCall(_method_name, _receiver, args, _span) = ex.kind { 142 | if !self.is_valid { 143 | return; 144 | } 145 | let ex_ty = self.cx.typeck_results().expr_ty(ex); 146 | self.is_valid &= is_type_valid(self.cx, ex_ty); 147 | 148 | for arg in args { 149 | if let hir::ExprKind::Closure(closure) = arg.kind { 150 | let mut params = hir::HirIdSet::default(); 151 | let body = self.cx.tcx.hir().body(closure.body); 152 | 153 | for param in body.params { 154 | if let hir::PatKind::Binding(_, hir_id, _, _) = param.pat.kind { 155 | params.insert(hir_id); 156 | } 157 | } 158 | 159 | self.is_valid &= 160 | check_variables(self.cx, closure.def_id, body, ¶ms, self.is_mut); 161 | } 162 | } 163 | } 164 | walk_expr(self, ex) 165 | } 166 | } 167 | 168 | fn get_all_methods<'tcx>( 169 | cx: &LateContext<'tcx>, 170 | into_iter_trait: hir::def_id::DefId, 171 | original_expr: &hir::Expr, 172 | ) -> Vec<&'tcx str> { 173 | let mut res = Vec::new(); 174 | if let (Some(parallel_iterator_def_id), Some(parallel_indexed_iterator_def_id)) = ( 175 | get_trait_def_id(cx.tcx, &["rayon", "iter", "ParallelIterator"]), 176 | get_trait_def_id(cx.tcx, &["rayon", "iter", "IndexedParallelIterator"]), 177 | ) { 178 | let tcx = cx.tcx; 179 | let infcx = tcx.infer_ctxt().build(); 180 | let ocx = ObligationCtxt::new(&infcx); 181 | let param_env = tcx.param_env(into_iter_trait); 182 | 183 | // Create a new inference variable, ?new 184 | let ty = infcx.next_ty_var(original_expr.span); 185 | 186 | let projection = ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::Projection( 187 | ty::ProjectionPredicate { 188 | projection_term: ty::AliasTerm::new(tcx, into_iter_trait, GenericArgs::empty()), 189 | term: ty.into(), 190 | }, 191 | ))); 192 | 193 | let obligation = Obligation::new(tcx, ObligationCause::dummy(), param_env, projection); 194 | 195 | ocx.register_obligation(obligation); 196 | 197 | // let errors = ocx.select_where_possible(); 198 | // if errors.is_empty() { 199 | // dbg!("no errors"); // TODO: do something else here 200 | // } 201 | 202 | // TODO: use the previous steps to determine which ids should be run 203 | let ids = &[parallel_iterator_def_id, parallel_indexed_iterator_def_id]; 204 | for def_id in ids { 205 | let associated_items = cx.tcx.associated_items(def_id); 206 | // Filter out only methods from the associated items 207 | let methods: Vec<&str> = associated_items 208 | .in_definition_order() 209 | .filter(|item| matches!(item.kind, ty::AssocKind::Fn)) 210 | .map(|item| item.name.as_str()) 211 | .collect(); 212 | res.extend(methods); 213 | } 214 | } 215 | 216 | res 217 | } 218 | 219 | #[test] 220 | fn ui() { 221 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 222 | } 223 | -------------------------------------------------------------------------------- /lints/to_iter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | #![warn(unused_extern_crates)] 3 | #![feature(let_chains)] 4 | #![feature(unwrap_infallible)] 5 | 6 | extern crate rustc_errors; 7 | extern crate rustc_hash; 8 | extern crate rustc_hir; 9 | extern crate rustc_hir_typeck; 10 | extern crate rustc_middle; 11 | extern crate rustc_span; 12 | 13 | mod variable_check; 14 | 15 | use clippy_utils::{higher::ForLoop, source::snippet_indent, ty::implements_trait}; 16 | use clippy_utils::{is_lang_item_or_ctor, is_res_lang_ctor, ty}; 17 | 18 | use rustc_errors::Applicability; 19 | use rustc_hir::{ 20 | def::Res, 21 | intravisit::{walk_expr, Visitor}, 22 | ByRef, Expr, ExprKind, LangItem, Node, PatKind, QPath, 23 | }; 24 | 25 | use rustc_lint::{LateContext, LateLintPass, LintContext}; 26 | use rustc_middle::ty::Ty; 27 | use rustc_span::symbol::sym; 28 | 29 | use utils::span_to_snippet_macro; 30 | use variable_check::check_variables; 31 | 32 | dylint_linting::declare_late_lint! { 33 | /// ### What it does 34 | /// Convert a for loop into it's for_each equivalent 35 | /// ### Why is this bad? 36 | /// Offers opportunities for parallelisms 37 | /// ### Known problems 38 | /// lots 39 | /// 40 | /// ### Example 41 | /// ```rust 42 | /// // example code where a warning is issued 43 | /// ``` 44 | /// Use instead: 45 | /// ```rust 46 | /// // example code that does not raise a warning 47 | /// ``` 48 | pub TO_ITER, 49 | Warn, 50 | "suggest using `(try_)for_each`" 51 | } 52 | 53 | impl<'tcx> LateLintPass<'tcx> for ToIter { 54 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { 55 | if let Some(ForLoop { 56 | pat, 57 | arg, 58 | body, 59 | loop_id: _loop_id, 60 | span: _span, 61 | }) = ForLoop::hir(expr) 62 | { 63 | let mut validator = Validator { 64 | cx, 65 | is_valid: true, 66 | has_continue: false, 67 | ret_ty: None, 68 | }; 69 | validator.visit_expr(body); 70 | if !validator.is_valid { 71 | return; 72 | } 73 | 74 | let src_map = cx.sess().source_map(); 75 | 76 | // If the argument is a variable, we need to make sure it is mutable so that it works 77 | // with try_for_each. 78 | let mut add_mut_sugg = None; 79 | 80 | if validator.ret_ty.is_some() 81 | && let ExprKind::Path(ref qpath) = arg.kind 82 | && let Res::Local(hir_id) = cx.qpath_res(qpath, expr.hir_id) 83 | && let Node::LetStmt(stmt) = cx.tcx.parent_hir_node(hir_id) 84 | && let PatKind::Binding(m, _, id, op) = stmt.pat.kind 85 | && m.1.is_not() 86 | { 87 | let ref_snip = match m.0 { 88 | ByRef::Yes(_) => "ref ", 89 | ByRef::No => "", 90 | }; 91 | let ident_snip = span_to_snippet_macro(src_map, id.span); 92 | let osp_snip = op.map_or(String::new(), |p| span_to_snippet_macro(src_map, p.span)); 93 | add_mut_sugg = Some(( 94 | stmt.pat.span, 95 | format!("{ref_snip}mut {ident_snip}{osp_snip}"), 96 | )); 97 | } 98 | 99 | let mut used_vars = check_variables(cx, body); 100 | used_vars 101 | .all_vars 102 | .retain(|v| !used_vars.copy_vars.contains(v)); 103 | if !used_vars.all_vars.is_empty() { 104 | return; 105 | } 106 | 107 | // Check if we need to convert to an iterator. 108 | // We explicitly call into_iter on Range to allow for better linting with par_iter. 109 | // TODO: When do we need extra parens 110 | let mut iter_snip = span_to_snippet_macro(src_map, arg.span); 111 | let ty = cx.typeck_results().expr_ty(arg); 112 | if !cx 113 | .tcx 114 | .lang_items() 115 | .iterator_trait() 116 | .map_or(false, |id| implements_trait(cx, ty, id, &[])) 117 | || is_range_expr(cx, arg) 118 | { 119 | iter_snip = format!("({iter_snip}).into_iter()"); 120 | } else { 121 | iter_snip = format!("({iter_snip})"); 122 | } 123 | let pat_snip = span_to_snippet_macro(src_map, pat.span); 124 | 125 | // Compute the body span for the inner stmts of the block. 126 | // This is required in the case of try_for_each so we can add the extra return 127 | // statement. 128 | let body_span = if let ExprKind::Block(block, _) = &body.kind { 129 | let first_span = if block.stmts.is_empty() { 130 | block.expr.map(|e| e.span) 131 | } else { 132 | Some(block.stmts[0].span) 133 | }; 134 | if let Some(sp) = first_span { 135 | let last_span = if let Some(e) = block.expr { 136 | e.span 137 | } else { 138 | block.stmts[block.stmts.len() - 1].span 139 | }; 140 | Some(sp.to(last_span)) 141 | } else { 142 | None 143 | } 144 | } else { 145 | Some(body.span) 146 | }; 147 | 148 | let mut body_snip = 149 | body_span.map_or(String::new(), |s| span_to_snippet_macro(src_map, s)); 150 | // Make sure to terminate the last statement with a semicolon 151 | // TODO: Are we missing anything here 152 | if !body_snip.trim_end().ends_with([';', '}']) { 153 | body_snip = format!("{};", body_snip.trim_end()); 154 | } 155 | 156 | // Acquire the indentation of the loop expr and it's body for nicer formatting in the 157 | // sugggestion 158 | let outer_indent = snippet_indent(cx, expr.span).unwrap_or_default(); 159 | let indent = body_span 160 | .and_then(|s| snippet_indent(cx, s)) 161 | .unwrap_or(format!("{outer_indent} ")); 162 | 163 | let sugg_msg = if let Some(ty) = validator.ret_ty { 164 | let constr = if ty::is_type_diagnostic_item(cx, ty, sym::Option) { 165 | "Some" 166 | } else if ty::is_type_diagnostic_item(cx, ty, sym::Result) { 167 | "Ok" 168 | } else { 169 | return; 170 | }; 171 | if validator.has_continue { 172 | body_snip = body_snip.replace("continue", &format!("return {constr}(())")); 173 | } 174 | format!( 175 | "{iter_snip}.try_for_each(|{pat_snip}| {{\n{indent}{body_snip}\n{indent}return {constr}(());\n{outer_indent}}})?;" 176 | ) 177 | } else { 178 | if validator.has_continue { 179 | body_snip = body_snip.replace("continue", "return"); 180 | } 181 | format!( 182 | "{iter_snip}.for_each(|{pat_snip}| {{\n{indent}{body_snip}\n{outer_indent}}});" 183 | ) 184 | }; 185 | let mut suggs = vec![(expr.span, sugg_msg)]; 186 | if let Some(add_mut) = add_mut_sugg { 187 | suggs.push(add_mut); 188 | } 189 | 190 | cx.span_lint(TO_ITER, expr.span, |diag| { 191 | diag.primary_message("use an iterator"); 192 | diag.multipart_suggestion( 193 | "try using an iterator", 194 | suggs, 195 | Applicability::MachineApplicable, 196 | ); 197 | }); 198 | } 199 | } 200 | } 201 | 202 | fn is_range_expr(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool { 203 | let range_items = [ 204 | LangItem::Range, 205 | LangItem::RangeTo, 206 | LangItem::RangeFrom, 207 | LangItem::RangeToInclusive, 208 | LangItem::RangeInclusiveNew, 209 | ]; 210 | 211 | let langs = cx.tcx.lang_items(); 212 | let mut range_langs = langs.iter().filter(|(li, _)| range_items.contains(li)); 213 | match &arg.kind { 214 | ExprKind::Struct(QPath::LangItem(li, _), _, _) => range_langs.any(|(ri, _)| ri == *li), 215 | ExprKind::Call(l, _) => { 216 | if let ExprKind::Path(QPath::LangItem(li, _)) = &l.kind { 217 | range_langs.any(|(ri, _)| ri == *li) 218 | } else { 219 | false 220 | } 221 | } 222 | _ => false, 223 | } 224 | } 225 | 226 | struct Validator<'a, 'tcx> { 227 | cx: &'a LateContext<'tcx>, 228 | is_valid: bool, 229 | has_continue: bool, 230 | ret_ty: Option>, 231 | } 232 | 233 | impl<'a, 'tcx> Visitor<'_> for Validator<'a, 'tcx> { 234 | fn visit_expr(&mut self, ex: &Expr) { 235 | match &ex.kind { 236 | ExprKind::Loop(_, _, _, _) | ExprKind::Closure(_) | ExprKind::Break(_, _) => { 237 | self.is_valid = false 238 | } 239 | ExprKind::Continue(d) => { 240 | // We don't support skipping outer loops 241 | if d.label.is_some() { 242 | self.is_valid = false; 243 | } else { 244 | self.has_continue = true; 245 | } 246 | } 247 | ExprKind::Ret(v) => { 248 | let Some(v) = v else { 249 | self.is_valid = false; 250 | return; 251 | }; 252 | let v_ty = self.cx.typeck_results().expr_ty(v); 253 | 254 | // Check if implements Try trait 255 | if !self 256 | .cx 257 | .tcx 258 | .lang_items() 259 | .try_trait() 260 | .map_or(false, |id| ty::implements_trait(self.cx, v_ty, id, &[])) 261 | { 262 | self.is_valid = false; 263 | return; 264 | } 265 | 266 | // Brute forces a check that the return type is the associated type Output. 267 | // TODO: Probably a nicer way to do this. 268 | match &v.kind { 269 | // Must statically know that the return value is None 270 | ExprKind::Path(qp) => { 271 | let res = self.cx.typeck_results().qpath_res(qp, v.hir_id); 272 | let is_opt = ty::is_type_diagnostic_item(self.cx, v_ty, sym::Option); 273 | let is_none = is_res_lang_ctor(self.cx, res, LangItem::OptionNone); 274 | if !(is_opt && is_none) { 275 | self.is_valid = false; 276 | return; 277 | } 278 | } 279 | ExprKind::Call(l, _) => { 280 | if let ExprKind::Path(qp) = &l.kind { 281 | // Must statically know that the return value is 282 | // Err(_) or Try::from_residual(_) 283 | let res = self.cx.typeck_results().qpath_res(qp, l.hir_id); 284 | let is_err = ty::is_type_diagnostic_item(self.cx, v_ty, sym::Result) 285 | && is_res_lang_ctor(self.cx, res, LangItem::ResultErr); 286 | let is_from_residual = is_lang_item_or_ctor( 287 | self.cx, 288 | res.def_id(), 289 | LangItem::TryTraitFromResidual, 290 | ); 291 | if !is_err && !is_from_residual { 292 | self.is_valid = false; 293 | return; 294 | } 295 | } else { 296 | self.is_valid = false; 297 | return; 298 | } 299 | } 300 | _ => { 301 | self.is_valid = false; 302 | return; 303 | } 304 | } 305 | self.ret_ty = Some(v_ty); 306 | } 307 | _ => walk_expr(self, ex), 308 | } 309 | } 310 | } 311 | 312 | #[test] 313 | fn ui() { 314 | dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); 315 | } 316 | -------------------------------------------------------------------------------- /lints/par_iter/ui/main.rs: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables, deprecated)] 3 | 4 | use core::ascii; 5 | use futures::io::{self, AsyncWrite, IoSlice}; 6 | use futures::task::{Context, Poll}; 7 | use rayon::prelude::*; 8 | use std::collections::{HashMap, HashSet, LinkedList}; 9 | use std::ops::Range; 10 | use std::pin::Pin; 11 | use std::rc::Rc; 12 | 13 | struct LocalQueue {} 14 | 15 | struct Person { 16 | name: String, 17 | age: u32, 18 | } 19 | 20 | struct Pantheon { 21 | tasks: Vec, 22 | } 23 | 24 | #[derive(Debug, PartialEq)] 25 | struct Case { 26 | uid: u32, 27 | mode: u32, 28 | priority: u32, 29 | } 30 | 31 | #[derive(Clone)] 32 | struct QosCase { 33 | uid: u64, // Identifier for the Quality of Service case 34 | } 35 | 36 | struct ApplicationState { 37 | foreground_high_qos_cases: Vec, 38 | background_high_qos_cases: Vec, 39 | } 40 | 41 | struct MyWriter; 42 | 43 | #[derive(Hash, Eq, PartialEq, Clone)] 44 | struct Id(String); 45 | 46 | struct Cmd { 47 | args: HashMap, 48 | } 49 | 50 | impl Cmd { 51 | fn find(&self, key: &Id) -> Option<&Arg> { 52 | self.args.get(key) 53 | } 54 | } 55 | 56 | struct Arg { 57 | requires: Vec<(String, Id)>, 58 | } 59 | 60 | fn main() {} 61 | 62 | // should parallelize 63 | fn simple() { 64 | (0..100).into_iter().for_each(|x| println!("{:?}", x)); 65 | } 66 | 67 | // no 68 | fn simple_no_send() { 69 | let list: Vec> = (0..100).map(Rc::new).collect(); 70 | list.iter().for_each(|y| println!("{:?}", y)); 71 | } 72 | 73 | // no 74 | fn simple_no_send_in_closure_body() { 75 | let mut list: Vec> = (0..100).map(Rc::new).collect(); 76 | (0..100).into_iter().for_each(|x| list.push(Rc::new(x))); 77 | } 78 | 79 | // no 80 | fn simple_no_send_in_closure_body_from_inside() { 81 | let mut list = Vec::new(); 82 | (0..100).into_iter().for_each(|x| list.push(Rc::new(x))); 83 | } 84 | 85 | // should parallelize 86 | fn simple_move_inside_closure() { 87 | let y = 100; 88 | (0..100) 89 | .into_iter() 90 | .for_each(|x| println!("{:?}{:?}", x, y)); 91 | } 92 | 93 | // no 94 | fn simple_no_push_to_vec() { 95 | let thread_num = 10; 96 | let mut locals = Vec::new(); 97 | (0..thread_num).into_iter().for_each(|_| { 98 | locals.push(LocalQueue {}); 99 | }); 100 | } 101 | 102 | // no 103 | fn simple_no_push_to_linked_list() { 104 | let thread_num = 10; 105 | let mut locals = LinkedList::new(); 106 | (0..thread_num).into_iter().for_each(|_| { 107 | locals.push_back(2); 108 | }); 109 | } 110 | 111 | // no 112 | fn simple_no_insert_to_usize() { 113 | let thread_num = 10; 114 | let mut num = 0; 115 | (0..thread_num).into_iter().for_each(|i| { 116 | num += i; 117 | }); 118 | } 119 | 120 | // should parallelize 121 | fn simple_into_parallel_ref_iterator() { 122 | let list: LinkedList = (0..100).collect(); 123 | list.into_iter().for_each(|x| println!("{:?}", x)); 124 | } 125 | 126 | // should parallelize 127 | fn complex() { 128 | let vec = vec![1, 2, 3, 4, 5, 6]; 129 | let a = 10; 130 | let b = 20; 131 | let c = "Hello"; 132 | let d = 3.14; 133 | let e = true; 134 | let person = Person { 135 | name: String::from("Alice"), 136 | age: 30, 137 | }; 138 | 139 | (0..10).into_iter().for_each(|x| { 140 | let sum = x + a + b; 141 | let message = if e { c } else { "Goodbye" }; 142 | let product = d * (x as f64); 143 | let person_info = format!("{} is {} years old.", person.name, person.age); 144 | println!( 145 | "Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}, Vec: {vec:?}", 146 | ); 147 | }); 148 | } 149 | 150 | // no 151 | fn complex_no_send() { 152 | let a = Rc::new(10); 153 | let b = Rc::new(20); 154 | let c = Rc::new("Hello"); 155 | let d = Rc::new(3.14); 156 | let e = Rc::new(true); 157 | let person = Rc::new(Person { 158 | name: String::from("Alice"), 159 | age: 30, 160 | }); 161 | 162 | (0..10).into_iter().for_each(|x| { 163 | let sum = *a + *b; 164 | let message = if *e { *c } else { "Goodbye" }; 165 | let product = *d * (x as f64); 166 | let person_info = format!("{} is {} years old.", person.name, person.age); 167 | println!("Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}",); 168 | }); 169 | } 170 | 171 | // no 172 | fn complex_type_no_trait() { 173 | let len = 10; 174 | let path = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 175 | let name = &path[1..len]; 176 | for byte in name.iter().cloned().flat_map(ascii::escape_default) { 177 | println!("{}", byte as char); 178 | } 179 | } 180 | 181 | // no 182 | pub fn iter_returned() -> Range { 183 | (0..100).into_iter() 184 | } 185 | 186 | // no 187 | pub fn iter_returned_not_top_level() { 188 | match core::char::decode_utf16([1234 as u16, 1232 as u16].iter().copied()).next() { 189 | Some(Ok(code)) => code, 190 | _ => return, 191 | }; 192 | } 193 | 194 | // no 195 | impl Pantheon { 196 | fn do_something(&mut self) { 197 | let tasks = vec!["go", "come", "listen"]; 198 | tasks.into_iter().for_each(|task| { 199 | self.process(task.to_string()); 200 | }); 201 | } 202 | fn process(&mut self, task: String) { 203 | self.tasks.push(task); 204 | } 205 | } 206 | 207 | #[derive(Debug)] 208 | enum MyEnum { 209 | A, 210 | B, 211 | C, 212 | } 213 | 214 | // should parallelize 215 | pub fn complex_long_chain() { 216 | let data = vec![1, 2, 3, 4, 5]; 217 | let multiplier = 2; 218 | let threshold = 5; 219 | let my_string = "Hello".to_string(); 220 | let my_enum: MyEnum = MyEnum::B; // Construct the enum variant 221 | 222 | data.iter() 223 | .map(|&x| { 224 | let transformed = (x * multiplier + my_string.len() as i32) / 2; 225 | transformed 226 | }) 227 | .filter(|&x| { 228 | let result = x > threshold && x % my_string.len() as i32 == 0; 229 | result 230 | }) 231 | .map(|x| { 232 | let result = match my_enum { 233 | MyEnum::A => x + 1, 234 | MyEnum::B => x + 2, 235 | MyEnum::C => x + 3, 236 | }; 237 | result 238 | }) 239 | .filter_map(|x| if x % 3 == 0 { Some(x) } else { None }) 240 | .for_each(|x| { 241 | println!("{}", x); 242 | }); 243 | } 244 | 245 | // should parallelize 246 | fn enumerate_par_iter() { 247 | let numbers = vec![1, 2, 3, 4, 5]; 248 | 249 | numbers.iter().enumerate().for_each(|t| { 250 | dbg!(t); 251 | }); 252 | } 253 | 254 | // no 255 | fn non_allowed_method() { 256 | let numbers = vec![1, 2, 3]; 257 | numbers.iter().cycle().enumerate().take(10).for_each(|t| { 258 | dbg!(t); 259 | }); 260 | } 261 | 262 | // no 263 | fn simple_fold() { 264 | let sum; 265 | let numbers = vec![1, 2, 3, 4, 5]; 266 | sum = numbers.iter().map(|&num| num).fold(0, |mut sum, v| { 267 | sum += v; 268 | sum 269 | }); 270 | println!("Sum: {}", sum); 271 | } 272 | 273 | // no 274 | fn request_request_filter() { 275 | let case = Case { 276 | uid: 1, 277 | mode: 10, 278 | priority: 20, 279 | }; 280 | 281 | let high_qos_cases = vec![ 282 | Case { 283 | uid: 2, 284 | mode: 15, 285 | priority: 25, 286 | }, 287 | Case { 288 | uid: 1, 289 | mode: 5, 290 | priority: 30, 291 | }, 292 | Case { 293 | uid: 3, 294 | mode: 20, 295 | priority: 10, 296 | }, 297 | ]; 298 | 299 | let mut down_grade_case = &case; 300 | let mut swap_case_index_opt: Option = None; 301 | (high_qos_cases.iter().enumerate()) 302 | .filter(|(i, swap_case)| { 303 | down_grade_case.uid == swap_case.uid 304 | && (down_grade_case.mode < swap_case.mode 305 | || down_grade_case.priority < swap_case.priority) 306 | }) 307 | .for_each(|(i, swap_case)| { 308 | down_grade_case = swap_case; 309 | swap_case_index_opt = Some(i) 310 | }); 311 | 312 | println!("Downgrade case: {:?}", down_grade_case); 313 | println!("Swap case index: {:?}", swap_case_index_opt); 314 | } 315 | 316 | // no 317 | impl ApplicationState { 318 | fn transition_to_background(&mut self, target_uid: u64) { 319 | let change_state_cases = self 320 | .background_high_qos_cases 321 | .iter() 322 | .cloned() 323 | .filter(|case| case.uid == target_uid); 324 | self.foreground_high_qos_cases.extend(change_state_cases); 325 | } 326 | } 327 | 328 | // should parallelize 329 | fn collect_at_end() { 330 | let people = vec![ 331 | Person { 332 | name: "Alice".to_string(), 333 | age: 25, 334 | }, 335 | Person { 336 | name: "Bob".to_string(), 337 | age: 35, 338 | }, 339 | Person { 340 | name: "Carol".to_string(), 341 | age: 32, 342 | }, 343 | ]; 344 | 345 | let names: Vec = people.iter().map(|p| p.name.clone()).collect(); 346 | 347 | println!("{:?}", names); 348 | } 349 | 350 | struct Tsize { 351 | send: usize, 352 | } 353 | 354 | impl Tsize { 355 | fn to_no_send(&self) -> TsizeNoSend { 356 | TsizeNoSend { 357 | no_send: Rc::new(self.send), 358 | } 359 | } 360 | } 361 | 362 | #[derive(Debug)] 363 | struct TsizeNoSend { 364 | no_send: Rc, 365 | } 366 | 367 | // no 368 | fn collect_at_end_no_par() { 369 | let t_size_vec: Vec = vec![Tsize { send: 32 }, Tsize { send: 42 }]; 370 | let t_size_vec_no_send: Vec = t_size_vec.iter().map(|t| t.to_no_send()).collect(); 371 | 372 | println!("{:?}", t_size_vec_no_send); 373 | } 374 | 375 | // should parallelize 376 | impl AsyncWrite for MyWriter { 377 | fn poll_write( 378 | self: Pin<&mut Self>, 379 | _cx: &mut Context<'_>, 380 | buf: &[u8], 381 | ) -> Poll> { 382 | // Dummy implementation: Pretend we've written the whole buffer 383 | Poll::Ready(Ok(buf.len())) 384 | } 385 | 386 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 387 | // Dummy implementation: Always say it's flushed 388 | Poll::Ready(Ok(())) 389 | } 390 | 391 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 392 | // Dummy implementation: Always say it's closed 393 | Poll::Ready(Ok(())) 394 | } 395 | 396 | fn poll_write_vectored( 397 | self: Pin<&mut Self>, 398 | cx: &mut Context<'_>, 399 | bufs: &[IoSlice<'_>], 400 | ) -> Poll> { 401 | let buf = bufs 402 | .iter() 403 | .find(|b| !b.is_empty()) 404 | .map_or(&[][..], |b| &**b); 405 | self.poll_write(cx, buf) 406 | } 407 | } 408 | 409 | //should parallelize 410 | fn nested_pars() { 411 | let used_filtered: HashSet = HashSet::new(); 412 | let conflicting_keys: HashSet = HashSet::new(); 413 | let cmd = Cmd { 414 | args: HashMap::new(), 415 | }; 416 | 417 | let required: Vec = used_filtered 418 | .iter() 419 | .filter_map(|key| cmd.find(key)) 420 | .flat_map(|arg| arg.requires.iter().map(|item| &item.1)) 421 | .filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key)) 422 | .chain(used_filtered.iter()) 423 | .cloned() 424 | .collect(); 425 | } 426 | 427 | // 1st should parallelize, 2nd no 428 | fn multiple_iter_one_chain() { 429 | let people = vec![ 430 | Person { 431 | name: "Alice".to_string(), 432 | age: 25, 433 | }, 434 | Person { 435 | name: "Bob".to_string(), 436 | age: 35, 437 | }, 438 | Person { 439 | name: "Carol".to_string(), 440 | age: 32, 441 | }, 442 | ]; 443 | 444 | let mut counter = 0; 445 | 446 | let names_over_30: Vec = people 447 | .iter() 448 | .filter(|p| p.age > 30) 449 | .map(|p| p.name.clone()) 450 | .collect::>() 451 | .into_iter() 452 | .map(|name| { 453 | counter += 1; 454 | format!("{}: {}", counter, name) 455 | }) 456 | .collect(); 457 | 458 | println!("{:?}", names_over_30); 459 | } 460 | 461 | // should parallelize 462 | fn simple_iter_mut() { 463 | let mut numbers = vec![1, 2, 3, 4, 5]; 464 | numbers.iter_mut().for_each(|num| *num *= 2); // Double each number 465 | println!("{:?}", numbers); 466 | } 467 | 468 | // should parallelize 469 | fn mut_var_declared_in_closure() { 470 | let numbers = vec![1, 2, 3, 4, 5]; 471 | let doubled_numbers: Vec = numbers 472 | .into_iter() 473 | .map(|num| { 474 | let mut doubled = num * 2; // Mutable variable inside the closure 475 | doubled += 1; // Modify the mutable variable 476 | doubled // Return the modified value 477 | }) 478 | .collect(); 479 | println!("{:?}", doubled_numbers); 480 | } 481 | 482 | // should parallelize 483 | fn return_loop() -> Option<()> { 484 | let num_workers = 10; 485 | let locals = vec![1, 2, 3, 4, 5]; 486 | (0..num_workers).into_iter().try_for_each(|index| { 487 | let item = locals.get(index)?; 488 | return Some(()); 489 | })?; 490 | Some(()) 491 | } 492 | -------------------------------------------------------------------------------- /lints/par_iter/ui/main.fixed: -------------------------------------------------------------------------------- 1 | // run-rustfix 2 | #![allow(dead_code, unused_imports, unused_variables, deprecated)] 3 | 4 | use core::ascii; 5 | use futures::io::{self, AsyncWrite, IoSlice}; 6 | use futures::task::{Context, Poll}; 7 | use rayon::prelude::*; 8 | use std::collections::{HashMap, HashSet, LinkedList}; 9 | use std::ops::Range; 10 | use std::pin::Pin; 11 | use std::rc::Rc; 12 | 13 | struct LocalQueue {} 14 | 15 | struct Person { 16 | name: String, 17 | age: u32, 18 | } 19 | 20 | struct Pantheon { 21 | tasks: Vec, 22 | } 23 | 24 | #[derive(Debug, PartialEq)] 25 | struct Case { 26 | uid: u32, 27 | mode: u32, 28 | priority: u32, 29 | } 30 | 31 | #[derive(Clone)] 32 | struct QosCase { 33 | uid: u64, // Identifier for the Quality of Service case 34 | } 35 | 36 | struct ApplicationState { 37 | foreground_high_qos_cases: Vec, 38 | background_high_qos_cases: Vec, 39 | } 40 | 41 | struct MyWriter; 42 | 43 | #[derive(Hash, Eq, PartialEq, Clone)] 44 | struct Id(String); 45 | 46 | struct Cmd { 47 | args: HashMap, 48 | } 49 | 50 | impl Cmd { 51 | fn find(&self, key: &Id) -> Option<&Arg> { 52 | self.args.get(key) 53 | } 54 | } 55 | 56 | struct Arg { 57 | requires: Vec<(String, Id)>, 58 | } 59 | 60 | fn main() {} 61 | 62 | // should parallelize 63 | fn simple() { 64 | (0..100).into_par_iter().for_each(|x| println!("{:?}", x)); 65 | } 66 | 67 | // no 68 | fn simple_no_send() { 69 | let list: Vec> = (0..100).map(Rc::new).collect(); 70 | list.iter().for_each(|y| println!("{:?}", y)); 71 | } 72 | 73 | // no 74 | fn simple_no_send_in_closure_body() { 75 | let mut list: Vec> = (0..100).map(Rc::new).collect(); 76 | (0..100).into_iter().for_each(|x| list.push(Rc::new(x))); 77 | } 78 | 79 | // no 80 | fn simple_no_send_in_closure_body_from_inside() { 81 | let mut list = Vec::new(); 82 | (0..100).into_iter().for_each(|x| list.push(Rc::new(x))); 83 | } 84 | 85 | // should parallelize 86 | fn simple_move_inside_closure() { 87 | let y = 100; 88 | (0..100) 89 | .into_par_iter() 90 | .for_each(|x| println!("{:?}{:?}", x, y)); 91 | } 92 | 93 | // no 94 | fn simple_no_push_to_vec() { 95 | let thread_num = 10; 96 | let mut locals = Vec::new(); 97 | (0..thread_num).into_iter().for_each(|_| { 98 | locals.push(LocalQueue {}); 99 | }); 100 | } 101 | 102 | // no 103 | fn simple_no_push_to_linked_list() { 104 | let thread_num = 10; 105 | let mut locals = LinkedList::new(); 106 | (0..thread_num).into_iter().for_each(|_| { 107 | locals.push_back(2); 108 | }); 109 | } 110 | 111 | // no 112 | fn simple_no_insert_to_usize() { 113 | let thread_num = 10; 114 | let mut num = 0; 115 | (0..thread_num).into_iter().for_each(|i| { 116 | num += i; 117 | }); 118 | } 119 | 120 | // should parallelize 121 | fn simple_into_parallel_ref_iterator() { 122 | let list: LinkedList = (0..100).collect(); 123 | list.into_par_iter().for_each(|x| println!("{:?}", x)); 124 | } 125 | 126 | // should parallelize 127 | fn complex() { 128 | let vec = vec![1, 2, 3, 4, 5, 6]; 129 | let a = 10; 130 | let b = 20; 131 | let c = "Hello"; 132 | let d = 3.14; 133 | let e = true; 134 | let person = Person { 135 | name: String::from("Alice"), 136 | age: 30, 137 | }; 138 | 139 | (0..10).into_par_iter().for_each(|x| { 140 | let sum = x + a + b; 141 | let message = if e { c } else { "Goodbye" }; 142 | let product = d * (x as f64); 143 | let person_info = format!("{} is {} years old.", person.name, person.age); 144 | println!( 145 | "Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}, Vec: {vec:?}", 146 | ); 147 | }); 148 | } 149 | 150 | // no 151 | fn complex_no_send() { 152 | let a = Rc::new(10); 153 | let b = Rc::new(20); 154 | let c = Rc::new("Hello"); 155 | let d = Rc::new(3.14); 156 | let e = Rc::new(true); 157 | let person = Rc::new(Person { 158 | name: String::from("Alice"), 159 | age: 30, 160 | }); 161 | 162 | (0..10).into_iter().for_each(|x| { 163 | let sum = *a + *b; 164 | let message = if *e { *c } else { "Goodbye" }; 165 | let product = *d * (x as f64); 166 | let person_info = format!("{} is {} years old.", person.name, person.age); 167 | println!("Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}",); 168 | }); 169 | } 170 | 171 | // no 172 | fn complex_type_no_trait() { 173 | let len = 10; 174 | let path = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 175 | let name = &path[1..len]; 176 | for byte in name.iter().cloned().flat_map(ascii::escape_default) { 177 | println!("{}", byte as char); 178 | } 179 | } 180 | 181 | // no 182 | pub fn iter_returned() -> Range { 183 | (0..100).into_iter() 184 | } 185 | 186 | // no 187 | pub fn iter_returned_not_top_level() { 188 | match core::char::decode_utf16([1234 as u16, 1232 as u16].iter().copied()).next() { 189 | Some(Ok(code)) => code, 190 | _ => return, 191 | }; 192 | } 193 | 194 | // no 195 | impl Pantheon { 196 | fn do_something(&mut self) { 197 | let tasks = vec!["go", "come", "listen"]; 198 | tasks.into_iter().for_each(|task| { 199 | self.process(task.to_string()); 200 | }); 201 | } 202 | fn process(&mut self, task: String) { 203 | self.tasks.push(task); 204 | } 205 | } 206 | 207 | #[derive(Debug)] 208 | enum MyEnum { 209 | A, 210 | B, 211 | C, 212 | } 213 | 214 | // should parallelize 215 | pub fn complex_long_chain() { 216 | let data = vec![1, 2, 3, 4, 5]; 217 | let multiplier = 2; 218 | let threshold = 5; 219 | let my_string = "Hello".to_string(); 220 | let my_enum: MyEnum = MyEnum::B; // Construct the enum variant 221 | 222 | data.par_iter() 223 | .map(|&x| { 224 | let transformed = (x * multiplier + my_string.len() as i32) / 2; 225 | transformed 226 | }) 227 | .filter(|&x| { 228 | let result = x > threshold && x % my_string.len() as i32 == 0; 229 | result 230 | }) 231 | .map(|x| { 232 | let result = match my_enum { 233 | MyEnum::A => x + 1, 234 | MyEnum::B => x + 2, 235 | MyEnum::C => x + 3, 236 | }; 237 | result 238 | }) 239 | .filter_map(|x| if x % 3 == 0 { Some(x) } else { None }) 240 | .for_each(|x| { 241 | println!("{}", x); 242 | }); 243 | } 244 | 245 | // should parallelize 246 | fn enumerate_par_iter() { 247 | let numbers = vec![1, 2, 3, 4, 5]; 248 | 249 | numbers.par_iter().enumerate().for_each(|t| { 250 | dbg!(t); 251 | }); 252 | } 253 | 254 | // no 255 | fn non_allowed_method() { 256 | let numbers = vec![1, 2, 3]; 257 | numbers.iter().cycle().enumerate().take(10).for_each(|t| { 258 | dbg!(t); 259 | }); 260 | } 261 | 262 | // no 263 | fn simple_fold() { 264 | let sum; 265 | let numbers = vec![1, 2, 3, 4, 5]; 266 | sum = numbers.iter().map(|&num| num).fold(0, |mut sum, v| { 267 | sum += v; 268 | sum 269 | }); 270 | println!("Sum: {}", sum); 271 | } 272 | 273 | // no 274 | fn request_request_filter() { 275 | let case = Case { 276 | uid: 1, 277 | mode: 10, 278 | priority: 20, 279 | }; 280 | 281 | let high_qos_cases = vec![ 282 | Case { 283 | uid: 2, 284 | mode: 15, 285 | priority: 25, 286 | }, 287 | Case { 288 | uid: 1, 289 | mode: 5, 290 | priority: 30, 291 | }, 292 | Case { 293 | uid: 3, 294 | mode: 20, 295 | priority: 10, 296 | }, 297 | ]; 298 | 299 | let mut down_grade_case = &case; 300 | let mut swap_case_index_opt: Option = None; 301 | (high_qos_cases.iter().enumerate()) 302 | .filter(|(i, swap_case)| { 303 | down_grade_case.uid == swap_case.uid 304 | && (down_grade_case.mode < swap_case.mode 305 | || down_grade_case.priority < swap_case.priority) 306 | }) 307 | .for_each(|(i, swap_case)| { 308 | down_grade_case = swap_case; 309 | swap_case_index_opt = Some(i) 310 | }); 311 | 312 | println!("Downgrade case: {:?}", down_grade_case); 313 | println!("Swap case index: {:?}", swap_case_index_opt); 314 | } 315 | 316 | // no 317 | impl ApplicationState { 318 | fn transition_to_background(&mut self, target_uid: u64) { 319 | let change_state_cases = self 320 | .background_high_qos_cases 321 | .iter() 322 | .cloned() 323 | .filter(|case| case.uid == target_uid); 324 | self.foreground_high_qos_cases.extend(change_state_cases); 325 | } 326 | } 327 | 328 | // should parallelize 329 | fn collect_at_end() { 330 | let people = vec![ 331 | Person { 332 | name: "Alice".to_string(), 333 | age: 25, 334 | }, 335 | Person { 336 | name: "Bob".to_string(), 337 | age: 35, 338 | }, 339 | Person { 340 | name: "Carol".to_string(), 341 | age: 32, 342 | }, 343 | ]; 344 | 345 | let names: Vec = people.par_iter().map(|p| p.name.clone()).collect(); 346 | 347 | println!("{:?}", names); 348 | } 349 | 350 | struct Tsize { 351 | send: usize, 352 | } 353 | 354 | impl Tsize { 355 | fn to_no_send(&self) -> TsizeNoSend { 356 | TsizeNoSend { 357 | no_send: Rc::new(self.send), 358 | } 359 | } 360 | } 361 | 362 | #[derive(Debug)] 363 | struct TsizeNoSend { 364 | no_send: Rc, 365 | } 366 | 367 | // no 368 | fn collect_at_end_no_par() { 369 | let t_size_vec: Vec = vec![Tsize { send: 32 }, Tsize { send: 42 }]; 370 | let t_size_vec_no_send: Vec = t_size_vec.iter().map(|t| t.to_no_send()).collect(); 371 | 372 | println!("{:?}", t_size_vec_no_send); 373 | } 374 | 375 | // should parallelize 376 | impl AsyncWrite for MyWriter { 377 | fn poll_write( 378 | self: Pin<&mut Self>, 379 | _cx: &mut Context<'_>, 380 | buf: &[u8], 381 | ) -> Poll> { 382 | // Dummy implementation: Pretend we've written the whole buffer 383 | Poll::Ready(Ok(buf.len())) 384 | } 385 | 386 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 387 | // Dummy implementation: Always say it's flushed 388 | Poll::Ready(Ok(())) 389 | } 390 | 391 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 392 | // Dummy implementation: Always say it's closed 393 | Poll::Ready(Ok(())) 394 | } 395 | 396 | fn poll_write_vectored( 397 | self: Pin<&mut Self>, 398 | cx: &mut Context<'_>, 399 | bufs: &[IoSlice<'_>], 400 | ) -> Poll> { 401 | let buf = bufs 402 | .par_iter() 403 | .find(|b| !b.is_empty()) 404 | .map_or(&[][..], |b| &**b); 405 | self.poll_write(cx, buf) 406 | } 407 | } 408 | 409 | //should parallelize 410 | fn nested_pars() { 411 | let used_filtered: HashSet = HashSet::new(); 412 | let conflicting_keys: HashSet = HashSet::new(); 413 | let cmd = Cmd { 414 | args: HashMap::new(), 415 | }; 416 | 417 | let required: Vec = used_filtered 418 | .par_iter() 419 | .filter_map(|key| cmd.find(key)) 420 | .flat_map(|arg| arg.requires.par_iter().map(|item| &item.1)) 421 | .filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key)) 422 | .chain(used_filtered.par_iter()) 423 | .cloned() 424 | .collect(); 425 | } 426 | 427 | // 1st should parallelize, 2nd no 428 | fn multiple_iter_one_chain() { 429 | let people = vec![ 430 | Person { 431 | name: "Alice".to_string(), 432 | age: 25, 433 | }, 434 | Person { 435 | name: "Bob".to_string(), 436 | age: 35, 437 | }, 438 | Person { 439 | name: "Carol".to_string(), 440 | age: 32, 441 | }, 442 | ]; 443 | 444 | let mut counter = 0; 445 | 446 | let names_over_30: Vec = people 447 | .par_iter() 448 | .filter(|p| p.age > 30) 449 | .map(|p| p.name.clone()) 450 | .collect::>() 451 | .into_iter() 452 | .map(|name| { 453 | counter += 1; 454 | format!("{}: {}", counter, name) 455 | }) 456 | .collect(); 457 | 458 | println!("{:?}", names_over_30); 459 | } 460 | 461 | // should parallelize 462 | fn simple_iter_mut() { 463 | let mut numbers = vec![1, 2, 3, 4, 5]; 464 | numbers.par_iter_mut().for_each(|num| *num *= 2); // Double each number 465 | println!("{:?}", numbers); 466 | } 467 | 468 | // should parallelize 469 | fn mut_var_declared_in_closure() { 470 | let numbers = vec![1, 2, 3, 4, 5]; 471 | let doubled_numbers: Vec = numbers 472 | .into_par_iter() 473 | .map(|num| { 474 | let mut doubled = num * 2; // Mutable variable inside the closure 475 | doubled += 1; // Modify the mutable variable 476 | doubled // Return the modified value 477 | }) 478 | .collect(); 479 | println!("{:?}", doubled_numbers); 480 | } 481 | 482 | // should parallelize 483 | fn return_loop() -> Option<()> { 484 | let num_workers = 10; 485 | let locals = vec![1, 2, 3, 4, 5]; 486 | (0..num_workers).into_par_iter().try_for_each(|index| { 487 | let item = locals.get(index)?; 488 | return Some(()); 489 | })?; 490 | Some(()) 491 | } 492 | -------------------------------------------------------------------------------- /lints/par_iter/src/variable_check.rs: -------------------------------------------------------------------------------- 1 | use clippy_utils::{get_trait_def_id, ty::implements_trait, visitors::for_each_expr}; 2 | 3 | use rustc_hash::FxHashSet; 4 | use rustc_hir as hir; 5 | use rustc_hir_typeck::expr_use_visitor as euv; 6 | use rustc_infer::infer::TyCtxtInferExt; 7 | use rustc_lint::{LateContext, LintContext}; 8 | use rustc_middle::{ 9 | mir::FakeReadCause, 10 | ty::{self, Ty, TyCtxt, UpvarId, UpvarPath}, 11 | }; 12 | use rustc_span::{def_id::LocalDefId, sym, Symbol}; 13 | use rustc_trait_selection::infer::InferCtxtExt; 14 | 15 | use core::ops::ControlFlow; 16 | 17 | use crate::constants::TRAIT_PATHS; 18 | 19 | pub(crate) struct MutablyUsedVariablesCtxt<'tcx> { 20 | mutably_used_vars: hir::HirIdSet, 21 | locally_bind_vars: hir::HirIdSet, 22 | all_vars: FxHashSet>, 23 | prev_bind: Option, 24 | /// In async functions, the inner AST is composed of multiple layers until we reach the code 25 | /// defined by the user. Because of that, some variables are marked as mutably borrowed even 26 | /// though they're not. This field lists the `HirId` that should not be considered as mutable 27 | /// use of a variable. 28 | prev_move_to_closure: hir::HirIdSet, 29 | aliases: hir::HirIdMap, 30 | async_closures: FxHashSet, 31 | tcx: TyCtxt<'tcx>, 32 | } 33 | 34 | pub(crate) fn check_variables<'tcx>( 35 | cx: &LateContext<'tcx>, 36 | body_owner: hir::def_id::LocalDefId, 37 | body: &'tcx hir::Body<'_>, 38 | params: &hir::HirIdSet, 39 | is_mut: bool, 40 | ) -> bool { 41 | let MutablyUsedVariablesCtxt { 42 | mut mutably_used_vars, 43 | all_vars, 44 | mut locally_bind_vars, 45 | .. 46 | } = { 47 | let mut ctx = MutablyUsedVariablesCtxt { 48 | mutably_used_vars: hir::HirIdSet::default(), 49 | locally_bind_vars: hir::HirIdSet::default(), 50 | all_vars: FxHashSet::default(), 51 | prev_bind: None, 52 | prev_move_to_closure: hir::HirIdSet::default(), 53 | aliases: hir::HirIdMap::default(), 54 | async_closures: FxHashSet::default(), 55 | tcx: cx.tcx, 56 | }; 57 | 58 | euv::ExprUseVisitor::for_clippy(cx, body_owner, &mut ctx) 59 | .consume_body(body) 60 | .into_ok(); 61 | 62 | let mut checked_closures = FxHashSet::default(); 63 | 64 | // We retrieve all the closures declared in the function because they will not be found 65 | // by `euv::Delegate`. 66 | let mut closures: FxHashSet = FxHashSet::default(); 67 | for_each_expr(cx, body, |expr| { 68 | if let hir::ExprKind::Closure(closure) = expr.kind { 69 | closures.insert(closure.def_id); 70 | } 71 | ControlFlow::<()>::Continue(()) 72 | }); 73 | check_closures(&mut ctx, cx, &mut checked_closures, closures); 74 | 75 | ctx 76 | }; 77 | let mut res = true; 78 | for ty in all_vars { 79 | res &= is_type_valid(cx, ty); 80 | } 81 | locally_bind_vars.retain(|&item| !params.contains(&item)); 82 | if is_mut { 83 | mutably_used_vars.retain(|&item| !params.contains(&item)); 84 | } 85 | mutably_used_vars.retain(|&item| !locally_bind_vars.contains(&item)); 86 | 87 | res &= mutably_used_vars.is_empty(); 88 | res 89 | } 90 | 91 | pub(crate) fn check_closures<'tcx>( 92 | ctx: &mut MutablyUsedVariablesCtxt<'tcx>, 93 | cx: &LateContext<'tcx>, 94 | checked_closures: &mut FxHashSet, 95 | closures: FxHashSet, 96 | ) { 97 | let hir = cx.tcx.hir(); 98 | for closure in closures { 99 | if !checked_closures.insert(closure) { 100 | continue; 101 | } 102 | ctx.prev_bind = None; 103 | ctx.prev_move_to_closure.clear(); 104 | if let Some(body) = cx 105 | .tcx 106 | .hir_node_by_def_id(closure) 107 | .associated_body() 108 | .map(|(_, body_id)| hir.body(body_id)) 109 | { 110 | euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx) 111 | .consume_body(body) 112 | .into_ok(); 113 | } 114 | } 115 | } 116 | 117 | impl<'tcx> MutablyUsedVariablesCtxt<'tcx> { 118 | fn add_mutably_used_var(&mut self, mut used_id: hir::HirId) { 119 | while let Some(id) = self.aliases.get(&used_id) { 120 | self.mutably_used_vars.insert(used_id); 121 | used_id = *id; 122 | } 123 | self.mutably_used_vars.insert(used_id); 124 | } 125 | 126 | fn would_be_alias_cycle(&self, alias: hir::HirId, mut target: hir::HirId) -> bool { 127 | while let Some(id) = self.aliases.get(&target) { 128 | if *id == alias { 129 | return true; 130 | } 131 | target = *id; 132 | } 133 | false 134 | } 135 | 136 | fn add_alias(&mut self, alias: hir::HirId, target: hir::HirId) { 137 | // This is to prevent alias loop. 138 | if alias == target || self.would_be_alias_cycle(alias, target) { 139 | return; 140 | } 141 | self.aliases.insert(alias, target); 142 | } 143 | 144 | // The goal here is to find if the current scope is unsafe or not. It stops when it finds 145 | // a function or an unsafe block. 146 | fn is_in_unsafe_block(&self, item: hir::HirId) -> bool { 147 | let hir = self.tcx.hir(); 148 | for (parent, node) in hir.parent_iter(item) { 149 | if let Some(fn_sig) = hir.fn_sig_by_hir_id(parent) { 150 | return fn_sig.header.is_unsafe(); 151 | } else if let hir::Node::Block(block) = node { 152 | if matches!(block.rules, hir::BlockCheckMode::UnsafeBlock(_)) { 153 | return true; 154 | } 155 | } 156 | } 157 | false 158 | } 159 | } 160 | 161 | impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { 162 | #[allow(clippy::if_same_then_else)] 163 | fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: hir::HirId) { 164 | if let euv::Place { 165 | base: 166 | euv::PlaceBase::Local(vid) 167 | | euv::PlaceBase::Upvar(UpvarId { 168 | var_path: UpvarPath { hir_id: vid }, 169 | .. 170 | }), 171 | base_ty, 172 | .. 173 | } = &cmt.place 174 | { 175 | self.all_vars.insert(*base_ty); 176 | if let Some(bind_id) = self.prev_bind.take() { 177 | if bind_id != *vid { 178 | self.add_alias(bind_id, *vid); 179 | } 180 | } else if !self.prev_move_to_closure.contains(vid) 181 | && matches!(base_ty.ref_mutability(), Some(ty::Mutability::Mut)) 182 | { 183 | self.add_mutably_used_var(*vid); 184 | } else if self.is_in_unsafe_block(id) { 185 | // If we are in an unsafe block, any operation on this variable must not be warned 186 | // upon! 187 | self.add_mutably_used_var(*vid); 188 | } 189 | self.prev_bind = None; 190 | // FIXME(rust/#120456) - is `swap_remove` correct? 191 | self.prev_move_to_closure.swap_remove(vid); 192 | } 193 | } 194 | 195 | #[allow(clippy::if_same_then_else)] 196 | fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: hir::HirId, borrow: ty::BorrowKind) { 197 | self.prev_bind = None; 198 | if let euv::Place { 199 | base: 200 | euv::PlaceBase::Local(vid) 201 | | euv::PlaceBase::Upvar(UpvarId { 202 | var_path: UpvarPath { hir_id: vid }, 203 | .. 204 | }), 205 | base_ty, 206 | .. 207 | } = &cmt.place 208 | { 209 | self.all_vars.insert(*base_ty); 210 | 211 | // If this is a mutable borrow, it was obviously used mutably so we add it. However 212 | // for `UniqueImmBorrow`, it's interesting because if you do: `array[0] = value` inside 213 | // a closure, it'll return this variant whereas if you have just an index access, it'll 214 | // return `ImmBorrow`. So if there is "Unique" and it's a mutable reference, we add it 215 | // to the mutably used variables set. 216 | if borrow == ty::BorrowKind::MutBorrow 217 | || (borrow == ty::BorrowKind::UniqueImmBorrow 218 | && base_ty.ref_mutability() == Some(ty::Mutability::Mut)) 219 | { 220 | self.add_mutably_used_var(*vid); 221 | } else if self.is_in_unsafe_block(id) { 222 | // If we are in an unsafe block, any operation on this variable must not be warned 223 | // upon! 224 | self.add_mutably_used_var(*vid); 225 | } 226 | } else if borrow == ty::ImmBorrow { 227 | // If there is an `async block`, it'll contain a call to a closure which we need to 228 | // go into to ensure all "mutate" checks are found. 229 | if let hir::Node::Expr(hir::Expr { 230 | kind: 231 | hir::ExprKind::Call( 232 | _, 233 | [hir::Expr { 234 | kind: hir::ExprKind::Closure(hir::Closure { def_id, .. }), 235 | .. 236 | }], 237 | ), 238 | .. 239 | }) = self.tcx.hir_node(cmt.hir_id) 240 | { 241 | self.async_closures.insert(*def_id); 242 | } 243 | } 244 | } 245 | 246 | fn mutate(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: hir::HirId) { 247 | self.prev_bind = None; 248 | if let euv::Place { 249 | projections: _, 250 | base: 251 | euv::PlaceBase::Local(vid) 252 | | euv::PlaceBase::Upvar(UpvarId { 253 | var_path: UpvarPath { hir_id: vid }, 254 | .. 255 | }), 256 | base_ty, 257 | .. 258 | } = &cmt.place 259 | { 260 | self.all_vars.insert(*base_ty); 261 | self.add_mutably_used_var(*vid); 262 | } 263 | } 264 | 265 | fn copy(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: hir::HirId) { 266 | if let euv::Place { 267 | base: 268 | euv::PlaceBase::Local(vid) 269 | | euv::PlaceBase::Upvar(UpvarId { 270 | var_path: UpvarPath { hir_id: vid }, 271 | .. 272 | }), 273 | .. 274 | } = &cmt.place 275 | { 276 | if self.is_in_unsafe_block(id) { 277 | self.add_mutably_used_var(*vid); 278 | } 279 | } 280 | self.prev_bind = None; 281 | } 282 | 283 | fn fake_read( 284 | &mut self, 285 | cmt: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, 286 | cause: FakeReadCause, 287 | _id: hir::HirId, 288 | ) { 289 | if let euv::Place { 290 | base: 291 | euv::PlaceBase::Upvar(UpvarId { 292 | var_path: UpvarPath { hir_id: vid }, 293 | .. 294 | }), 295 | base_ty, 296 | .. 297 | } = &cmt.place 298 | { 299 | self.all_vars.insert(*base_ty); 300 | 301 | if let FakeReadCause::ForLet(Some(inner)) = cause { 302 | // Seems like we are inside an async function. We need to store the closure `DefId` 303 | // to go through it afterwards. 304 | self.async_closures.insert(inner); 305 | self.add_alias(cmt.hir_id, *vid); 306 | self.prev_move_to_closure.insert(*vid); 307 | self.prev_bind = None; 308 | } 309 | } 310 | } 311 | 312 | fn bind(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: hir::HirId) { 313 | self.prev_bind = Some(id); 314 | if let euv::Place { 315 | base: 316 | euv::PlaceBase::Local(vid) 317 | | euv::PlaceBase::Upvar(UpvarId { 318 | var_path: UpvarPath { hir_id: vid }, 319 | .. 320 | }), 321 | .. 322 | } = &cmt.place 323 | { 324 | self.locally_bind_vars.insert(*vid); 325 | if self.is_in_unsafe_block(id) { 326 | self.add_mutably_used_var(*vid); 327 | } 328 | } 329 | } 330 | } 331 | 332 | pub(crate) fn check_implements_par_iter<'tcx>( 333 | cx: &'tcx LateContext, 334 | expr: &'tcx hir::Expr<'_>, 335 | ) -> Vec { 336 | let ty = cx.typeck_results().expr_ty(expr); 337 | let mut implemented_traits = Vec::new(); 338 | 339 | for trait_path in TRAIT_PATHS { 340 | if let Some(trait_def_id) = get_trait_def_id(cx.tcx, trait_path) { 341 | if cx 342 | .tcx 343 | .infer_ctxt() 344 | .build() 345 | .type_implements_trait(trait_def_id, [ty], cx.param_env) 346 | .must_apply_modulo_regions() 347 | { 348 | implemented_traits.push(trait_def_id); 349 | } 350 | } 351 | } 352 | implemented_traits 353 | } 354 | 355 | pub(crate) fn check_trait_impl<'tcx>( 356 | cx: &LateContext<'tcx>, 357 | ty: Ty<'tcx>, 358 | trait_name: Symbol, 359 | ) -> bool { 360 | cx.tcx 361 | .get_diagnostic_item(trait_name) 362 | .map_or(false, |trait_id| implements_trait(cx, ty, trait_id, &[])) 363 | } 364 | 365 | pub(crate) fn is_type_valid<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { 366 | let is_send = check_trait_impl(cx, ty, sym::Send); 367 | let is_sync = check_trait_impl(cx, ty, sym::Sync); 368 | let is_copy = check_trait_impl(cx, ty, sym::Copy); 369 | is_copy || (is_send && is_sync) 370 | } 371 | 372 | pub(crate) fn generate_suggestion( 373 | cx: &LateContext<'_>, 374 | expr: &hir::Expr<'_>, 375 | path: &hir::PathSegment, 376 | ) -> Option { 377 | let method_name = &*path.ident.name.to_string(); 378 | let replacement = match method_name { 379 | "into_iter" => Some("into_par_iter"), 380 | "iter" => Some("par_iter"), 381 | "iter_mut" => Some("par_iter_mut"), 382 | _ => None, 383 | }; 384 | 385 | if let Some(r) = replacement { 386 | cx.sess() 387 | .source_map() 388 | .span_to_snippet(expr.span) 389 | .map_or_else(|_| None, |s| Some(s.replace(method_name, r))) 390 | } else { 391 | None 392 | } 393 | } 394 | --------------------------------------------------------------------------------