Skip to content

Commit e201ee0

Browse files
authored
Merge pull request #1 from trusted-programming/par_iter
Par iter
2 parents 98759ad + 428b07c commit e201ee0

File tree

10 files changed

+476
-0
lines changed

10 files changed

+476
-0
lines changed

Cargo.lock

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ for_each = { path = "lints/for_each", features = ["rlib"] }
1515
filter = { path = "lints/filter", features = ["rlib"] }
1616
fold = { path = "lints/fold", features = ["rlib"] }
1717
par_fold = { path = "lints/par_fold", features = ["rlib"] }
18+
par_iter = { path = "lints/par_iter", features = ["rlib"] }
1819

1920
dylint_linting = { version = "2.5.0" }
2021

@@ -27,6 +28,7 @@ members = [
2728
"lints/filter",
2829
"lints/fold",
2930
"lints/par_fold",
31+
"lints/par_iter",
3032
"utils",
3133
]
3234

lints/par_iter/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

lints/par_iter/Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "par_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+
clippy_utils = { workspace = true }
14+
utils = { workspace = true }
15+
dylint_linting = "2.5.0"
16+
17+
[dev-dependencies]
18+
dylint_testing = "2.5.0"
19+
rayon = "1.8.0"
20+
21+
[package.metadata.rust-analyzer]
22+
rustc_private = true
23+
24+
[features]
25+
rlib = ["dylint_linting/constituent"]
26+
27+
[[example]]
28+
name = "main"
29+
path = "ui/main.rs"

lints/par_iter/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
```

lints/par_iter/src/lib.rs

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#![feature(rustc_private)]
2+
#![warn(unused_extern_crates)]
3+
#![feature(let_chains)]
4+
5+
extern crate rustc_errors;
6+
extern crate rustc_hir;
7+
extern crate rustc_span;
8+
9+
use clippy_utils::{get_trait_def_id, ty::implements_trait};
10+
use rustc_errors::Applicability;
11+
use rustc_hir::{
12+
def::Res,
13+
intravisit::{walk_expr, Visitor},
14+
Expr, ExprKind, Node,
15+
};
16+
use rustc_lint::{LateContext, LateLintPass, LintContext};
17+
use rustc_span::sym;
18+
use utils::span_to_snippet_macro;
19+
20+
dylint_linting::declare_late_lint! {
21+
/// ### What it does
22+
///
23+
/// ### Why is this bad?
24+
///
25+
/// ### Known problems
26+
/// Remove if none.
27+
///
28+
/// ### Example
29+
/// ```rust
30+
/// // example code where a warning is issued
31+
/// ```
32+
/// Use instead:
33+
/// ```rust
34+
/// // example code that does not raise a warning
35+
/// ```
36+
pub PAR_ITER,
37+
Warn,
38+
"suggest using par iter"
39+
}
40+
41+
struct ClosureVisitor<'a, 'tcx> {
42+
cx: &'a LateContext<'tcx>,
43+
is_valid: bool,
44+
}
45+
46+
struct Validator<'a, 'tcx> {
47+
cx: &'a LateContext<'tcx>,
48+
is_valid: bool,
49+
}
50+
impl<'a, 'tcx> Visitor<'_> for ClosureVisitor<'a, 'tcx> {
51+
fn visit_expr(&mut self, ex: &Expr) {
52+
match ex.kind {
53+
ExprKind::Path(path) => {
54+
let res: Res = self.cx.typeck_results().qpath_res(&path, ex.hir_id);
55+
56+
if let Res::Local(hir_id) = res {
57+
let parent = self.cx.tcx.hir().get_parent(hir_id);
58+
match parent {
59+
Node::Local(local) => {
60+
if let Some(expr) = local.init {
61+
let ty =
62+
self.cx.tcx.typeck(expr.hir_id.owner).node_type(expr.hir_id);
63+
let implements_send = self
64+
.cx
65+
.tcx
66+
.get_diagnostic_item(sym::Send)
67+
.map_or(false, |id| implements_trait(self.cx, ty, id, &[]));
68+
let implements_sync = self
69+
.cx
70+
.tcx
71+
.get_diagnostic_item(sym::Sync)
72+
.map_or(false, |id| implements_trait(self.cx, ty, id, &[]));
73+
let is_copy: bool = self
74+
.cx
75+
.tcx
76+
.get_diagnostic_item(sym::Copy)
77+
.map_or(false, |id| implements_trait(self.cx, ty, id, &[]));
78+
let valid = is_copy || (implements_send && implements_sync);
79+
self.is_valid = self.is_valid && valid;
80+
};
81+
}
82+
_ => {}
83+
}
84+
}
85+
}
86+
_ => walk_expr(self, ex),
87+
}
88+
}
89+
}
90+
91+
impl<'a, 'tcx> Visitor<'_> for Validator<'a, 'tcx> {
92+
fn visit_expr(&mut self, ex: &Expr) {
93+
match ex.kind {
94+
ExprKind::Closure(closure) => {
95+
let hir = self.cx.tcx.hir();
96+
let node = hir.get(closure.body.hir_id);
97+
if let Node::Expr(expr) = node {
98+
let mut closure_visitor = ClosureVisitor {
99+
cx: self.cx,
100+
is_valid: true,
101+
};
102+
closure_visitor.visit_expr(expr);
103+
self.is_valid = self.is_valid && closure_visitor.is_valid;
104+
}
105+
}
106+
_ => walk_expr(self, ex),
107+
}
108+
}
109+
}
110+
111+
impl<'tcx> LateLintPass<'tcx> for ParIter {
112+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
113+
if let ExprKind::MethodCall(path, _recv, _args, _span) = &expr.kind {
114+
let ident_name = &path.ident.name.to_string()[..];
115+
let src_map = cx.sess().source_map();
116+
let mut suggestion = span_to_snippet_macro(src_map, expr.span);
117+
match ident_name {
118+
"into_iter" => suggestion = suggestion.replace("into_iter", "into_par_iter"),
119+
120+
"iter" => suggestion = suggestion.replace("iter", "par_iter"),
121+
"iter_mut" => suggestion = suggestion.replace("iter_mut", "par_iter_mut"),
122+
_ => return,
123+
}
124+
125+
let ty = cx.typeck_results().expr_ty(expr);
126+
127+
let mut implements_par_iter = false;
128+
129+
let trait_defs = vec![
130+
get_trait_def_id(cx, &["rayon", "iter", "IntoParallelIterator"]),
131+
get_trait_def_id(cx, &["rayon", "iter", "ParallelIterator"]),
132+
// @todo get_trait_def_id(cx, &["rayon", "iter", "IndexedParallelIterator"]),
133+
// @todo get_trait_def_id(cx, &["rayon", "iter", "IntoParallelRefIterator"]),
134+
// @todo get_trait_def_id(cx, &["rayon", "iter", "IntoParallelRefMutIterator"]),
135+
];
136+
137+
for t in trait_defs {
138+
if let Some(trait_def_id) = t {
139+
implements_par_iter =
140+
implements_par_iter || implements_trait(cx, ty, trait_def_id, &[]);
141+
}
142+
}
143+
144+
if !implements_par_iter {
145+
return;
146+
}
147+
148+
// check that all types inside the closures are Send and sync or Copy
149+
let mut validator = Validator { cx, is_valid: true };
150+
151+
let parent_node = cx.tcx.hir().get_parent(expr.hir_id);
152+
match parent_node {
153+
Node::Expr(expr) => {
154+
validator.visit_expr(expr);
155+
}
156+
// Handle other kinds of parent nodes as needed
157+
_ => {}
158+
}
159+
if !validator.is_valid {
160+
return;
161+
}
162+
163+
cx.struct_span_lint(
164+
PAR_ITER,
165+
expr.span,
166+
"found iterator that can be parallelized",
167+
|diag| {
168+
diag.multipart_suggestion(
169+
"try using a parallel iterator",
170+
vec![(expr.span, suggestion)],
171+
Applicability::MachineApplicable,
172+
)
173+
},
174+
);
175+
}
176+
}
177+
}
178+
179+
#[test]
180+
fn ui() {
181+
dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME"));
182+
}

lints/par_iter/ui/main.fixed

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// run-rustfix
2+
3+
#[allow(unused_imports)]
4+
use rayon::prelude::*;
5+
// use std::collections::LinkedList;
6+
use std::rc::Rc;
7+
8+
fn main() {
9+
warn_par_iter_simple();
10+
// warn_par_iter_simple_no_send();
11+
warn_par_iter_simple_no_send_in_closure_body();
12+
move_inside_closure();
13+
// warn_par_iter_simple_into_parallel_ref_iterator();
14+
// warn_par_iter();
15+
warn_par_complex();
16+
warn_par_complex_no_send()
17+
}
18+
19+
fn warn_par_iter_simple() {
20+
(0..100).into_par_iter().for_each(|x| println!("{:?}", x));
21+
}
22+
23+
fn move_inside_closure() {
24+
let y = 100;
25+
(0..100)
26+
.into_par_iter()
27+
.for_each(|x| println!("{:?}{:?}", x, y));
28+
}
29+
// fn warn_par_iter_simple_no_send() {
30+
// let list: Vec<Rc<i32>> = (0..100).map(Rc::new).collect();
31+
// list.iter().for_each(|y| println!("{:?}", y));
32+
// }
33+
34+
fn warn_par_iter_simple_no_send_in_closure_body() {
35+
let mut list: Vec<Rc<i32>> = (0..100).map(Rc::new).collect();
36+
(0..100).into_iter().for_each(|x| list.push(Rc::new(x)));
37+
}
38+
39+
// fn warn_par_iter_simple_into_parallel_ref_iterator() {
40+
// let list: LinkedList<i32> = (0..100).collect();
41+
// list.into_iter().for_each(|x| println!("{:?}", x));
42+
// }
43+
44+
struct Person {
45+
name: String,
46+
age: u32,
47+
}
48+
49+
fn warn_par_complex() {
50+
let vec = vec![1, 2, 3, 4, 5, 6];
51+
let a = 10;
52+
let b = 20;
53+
let c = "Hello";
54+
let d = 3.14;
55+
let e = true;
56+
let person = Person {
57+
name: String::from("Alice"),
58+
age: 30,
59+
};
60+
61+
(0..10).into_par_iter().for_each(|x| {
62+
let sum = x + a + b;
63+
let message = if e { c } else { "Goodbye" };
64+
let product = d * (x as f64);
65+
let person_info = format!("{} is {} years old.", person.name, person.age);
66+
println!(
67+
"Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}, Vec: {vec:?}",
68+
);
69+
});
70+
}
71+
72+
fn warn_par_complex_no_send() {
73+
let a = Rc::new(10);
74+
let b = Rc::new(20);
75+
let c = Rc::new("Hello");
76+
let d = Rc::new(3.14);
77+
let e = Rc::new(true);
78+
let person = Rc::new(Person {
79+
name: String::from("Alice"),
80+
age: 30,
81+
});
82+
83+
(0..10).into_iter().for_each(|x| {
84+
let sum = *a + *b;
85+
let message = if *e { *c } else { "Goodbye" };
86+
let product = *d * (x as f64);
87+
let person_info = format!("{} is {} years old.", person.name, person.age);
88+
println!("Sum: {sum}, Message: {message}, Product: {product}, Person: {person_info}",);
89+
});
90+
}
91+
92+
// struct LocalQueue {}
93+
94+
// fn warn_par_iter() {
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+
// }

0 commit comments

Comments
 (0)