|
1 |
| -use std::ops::ControlFlow; |
2 |
| - |
| 1 | +use clippy_config::types::SuggestedPath; |
3 | 2 | use clippy_utils::diagnostics::span_lint_and_then;
|
4 | 3 | use clippy_utils::source::snippet_with_applicability;
|
5 |
| -use clippy_utils::visitors::for_each_expr_with_closures; |
6 | 4 | use clippy_utils::{def_path_def_ids, fn_def_id, is_lint_allowed};
|
7 | 5 | use rustc_data_structures::fx::FxHashMap;
|
8 |
| -use rustc_errors::{Applicability, Diagnostic}; |
| 6 | +use rustc_errors::{Applicability, Diag}; |
9 | 7 | use rustc_hir::def_id::DefId;
|
10 |
| -use rustc_hir::hir_id::CRATE_HIR_ID; |
11 |
| -use rustc_hir::{Body, Expr, ExprKind, GeneratorKind, HirIdSet}; |
| 8 | +use rustc_hir::{ |
| 9 | + Body, BodyId, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, ImplItem, ImplItemKind, |
| 10 | + Item, ItemKind, Node, TraitItem, TraitItemKind, |
| 11 | +}; |
12 | 12 | use rustc_lint::{LateContext, LateLintPass};
|
13 |
| -use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 13 | +use rustc_session::impl_lint_pass; |
14 | 14 | use rustc_span::Span;
|
15 | 15 |
|
16 | 16 | declare_clippy_lint! {
|
@@ -43,131 +43,118 @@ declare_clippy_lint! {
|
43 | 43 | /// ```
|
44 | 44 | #[clippy::version = "1.74.0"]
|
45 | 45 | pub UNNECESSARY_BLOCKING_OPS,
|
46 |
| - nursery, |
| 46 | + pedantic, |
47 | 47 | "blocking operations in an async context"
|
48 | 48 | }
|
49 | 49 |
|
50 | 50 | pub(crate) struct UnnecessaryBlockingOps {
|
51 |
| - blocking_ops: Vec<String>, |
52 |
| - blocking_ops_with_suggs: Vec<[String; 2]>, |
53 |
| - /// Map of resolved funtion def_id with suggestion string after checking crate |
| 51 | + blocking_ops: Vec<SuggestedPath>, |
| 52 | + /// Map of resolved funtion `def_id` with suggestion string after checking crate |
54 | 53 | id_with_suggs: FxHashMap<DefId, Option<String>>,
|
55 |
| - /// Keep track of visited block ids to skip checking the same bodies in `check_body` calls |
56 |
| - visited_block: HirIdSet, |
| 54 | + /// Tracking whether a body is async after entering it. |
| 55 | + body_asyncness: Vec<bool>, |
57 | 56 | }
|
58 | 57 |
|
59 | 58 | impl UnnecessaryBlockingOps {
|
60 |
| - pub(crate) fn new(blocking_ops: Vec<String>, blocking_ops_with_suggs: Vec<[String; 2]>) -> Self { |
| 59 | + pub(crate) fn new(blocking_ops: Vec<SuggestedPath>) -> Self { |
61 | 60 | Self {
|
62 | 61 | blocking_ops,
|
63 |
| - blocking_ops_with_suggs, |
64 | 62 | id_with_suggs: FxHashMap::default(),
|
65 |
| - visited_block: HirIdSet::default(), |
| 63 | + body_asyncness: vec![], |
66 | 64 | }
|
67 | 65 | }
|
68 | 66 | }
|
69 | 67 |
|
70 | 68 | impl_lint_pass!(UnnecessaryBlockingOps => [UNNECESSARY_BLOCKING_OPS]);
|
71 | 69 |
|
72 |
| -static HARD_CODED_BLOCKING_OPS: [&[&str]; 21] = [ |
73 |
| - &["std", "thread", "sleep"], |
74 |
| - // Filesystem functions |
75 |
| - &["std", "fs", "try_exists"], |
76 |
| - &["std", "fs", "canonicalize"], |
77 |
| - &["std", "fs", "copy"], |
78 |
| - &["std", "fs", "create_dir"], |
79 |
| - &["std", "fs", "create_dir_all"], |
80 |
| - &["std", "fs", "hard_link"], |
81 |
| - &["std", "fs", "metadata"], |
82 |
| - &["std", "fs", "read"], |
83 |
| - &["std", "fs", "read_dir"], |
84 |
| - &["std", "fs", "read_link"], |
85 |
| - &["std", "fs", "read_to_string"], |
86 |
| - &["std", "fs", "remove_dir"], |
87 |
| - &["std", "fs", "remove_dir_all"], |
88 |
| - &["std", "fs", "remove_file"], |
89 |
| - &["std", "fs", "rename"], |
90 |
| - &["std", "fs", "set_permissions"], |
91 |
| - &["std", "fs", "symlink_metadata"], |
92 |
| - &["std", "fs", "write"], |
93 |
| - // IO functions |
94 |
| - &["std", "io", "copy"], |
95 |
| - &["std", "io", "read_to_string"], |
96 |
| -]; |
97 |
| - |
98 | 70 | impl<'tcx> LateLintPass<'tcx> for UnnecessaryBlockingOps {
|
99 | 71 | fn check_crate(&mut self, cx: &LateContext<'tcx>) {
|
100 |
| - // Avoids processing and storing a long list of paths if this lint was allowed entirely |
101 |
| - if is_lint_allowed(cx, UNNECESSARY_BLOCKING_OPS, CRATE_HIR_ID) { |
102 |
| - return; |
103 |
| - } |
104 |
| - |
105 |
| - let full_fn_list = HARD_CODED_BLOCKING_OPS |
106 |
| - .into_iter() |
107 |
| - .map(|p| (p.to_vec(), None)) |
108 |
| - // Chain configured functions without suggestions |
109 |
| - .chain( |
110 |
| - self.blocking_ops |
111 |
| - .iter() |
112 |
| - .map(|p| (p.split("::").collect::<Vec<_>>(), None)), |
113 |
| - ) |
114 |
| - // Chain configured functions with suggestions |
115 |
| - .chain( |
116 |
| - self.blocking_ops_with_suggs |
117 |
| - .iter() |
118 |
| - .map(|[p, s]| (p.split("::").collect::<Vec<_>>(), Some(s.as_str()))), |
119 |
| - ); |
120 |
| - for (path, maybe_sugg_str) in full_fn_list { |
| 72 | + let full_fn_list = self.blocking_ops.iter().map(|p| (p.path(), p.suggestion())); |
| 73 | + for (path_str, maybe_sugg_str) in full_fn_list { |
| 74 | + let path: Vec<&str> = path_str.split("::").collect(); |
121 | 75 | for did in def_path_def_ids(cx, &path) {
|
122 | 76 | self.id_with_suggs.insert(did, maybe_sugg_str.map(ToOwned::to_owned));
|
123 | 77 | }
|
124 | 78 | }
|
125 | 79 | }
|
126 | 80 |
|
127 | 81 | fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) {
|
128 |
| - if is_lint_allowed(cx, UNNECESSARY_BLOCKING_OPS, body.value.hir_id) |
129 |
| - || self.visited_block.contains(&body.value.hir_id) |
130 |
| - { |
| 82 | + if is_lint_allowed(cx, UNNECESSARY_BLOCKING_OPS, body.value.hir_id) { |
131 | 83 | return;
|
132 | 84 | }
|
133 |
| - if let Some(GeneratorKind::Async(_)) = body.generator_kind() { |
134 |
| - for_each_expr_with_closures(cx, body, |ex| { |
135 |
| - match ex.kind { |
136 |
| - ExprKind::Block(block, _) => { |
137 |
| - self.visited_block.insert(block.hir_id); |
138 |
| - } |
139 |
| - ExprKind::Call(call, _) |
140 |
| - if let Some(call_did) = fn_def_id(cx, ex) && |
141 |
| - let Some(maybe_sugg) = self.id_with_suggs.get(&call_did) => { |
142 |
| - span_lint_and_then( |
143 |
| - cx, |
144 |
| - UNNECESSARY_BLOCKING_OPS, |
145 |
| - call.span, |
146 |
| - "blocking function call detected in an async body", |
147 |
| - |diag| { |
148 |
| - if let Some(sugg_fn_path) = maybe_sugg { |
149 |
| - make_suggestion(diag, cx, ex, call.span, sugg_fn_path); |
150 |
| - } |
151 |
| - } |
152 |
| - ); |
| 85 | + self.body_asyncness.push(in_async_body(cx, body.id())); |
| 86 | + } |
| 87 | + |
| 88 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 89 | + if matches!(self.body_asyncness.last(), Some(true)) |
| 90 | + && let ExprKind::Call(call, _) = &expr.kind |
| 91 | + && let Some(call_did) = fn_def_id(cx, expr) |
| 92 | + && let Some(maybe_sugg) = self.id_with_suggs.get(&call_did) |
| 93 | + { |
| 94 | + span_lint_and_then( |
| 95 | + cx, |
| 96 | + UNNECESSARY_BLOCKING_OPS, |
| 97 | + call.span, |
| 98 | + "blocking function call detected in an async body", |
| 99 | + |diag| { |
| 100 | + if let Some(sugg_fn_path) = maybe_sugg { |
| 101 | + make_suggestion(diag, cx, expr, call.span, sugg_fn_path); |
153 | 102 | }
|
154 |
| - _ => {} |
155 |
| - } |
156 |
| - ControlFlow::<()>::Continue(()) |
157 |
| - }); |
| 103 | + }, |
| 104 | + ); |
158 | 105 | }
|
159 | 106 | }
|
| 107 | + |
| 108 | + fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &'tcx Body<'tcx>) { |
| 109 | + self.body_asyncness.pop(); |
| 110 | + } |
160 | 111 | }
|
161 | 112 |
|
162 |
| -fn make_suggestion(diag: &mut Diagnostic, cx: &LateContext<'_>, expr: &Expr<'_>, fn_span: Span, sugg_fn_path: &str) { |
163 |
| - let mut applicability = Applicability::Unspecified; |
| 113 | +fn make_suggestion(diag: &mut Diag<'_, ()>, cx: &LateContext<'_>, expr: &Expr<'_>, fn_span: Span, sugg_fn_path: &str) { |
| 114 | + // Suggestion should only be offered when user specified it in the configuration file, |
| 115 | + // so we only assume it can be fixed here if only the path can be found |
| 116 | + // (but it may behind feature gate or the arg is different), otherwise don't try to fix it. |
| 117 | + let mut applicability = if def_path_def_ids(cx, &sugg_fn_path.split("::").collect::<Vec<_>>()) |
| 118 | + .next() |
| 119 | + .is_some() |
| 120 | + { |
| 121 | + Applicability::MaybeIncorrect |
| 122 | + } else { |
| 123 | + Applicability::Unspecified |
| 124 | + }; |
| 125 | + |
164 | 126 | let args_span = expr.span.with_lo(fn_span.hi());
|
165 | 127 | let args_snippet = snippet_with_applicability(cx, args_span, "..", &mut applicability);
|
166 | 128 | let suggestion = format!("{sugg_fn_path}{args_snippet}.await");
|
167 |
| - diag.span_suggestion( |
168 |
| - expr.span, |
169 |
| - "try using its async counterpart", |
170 |
| - suggestion, |
171 |
| - Applicability::Unspecified, |
172 |
| - ); |
| 129 | + diag.span_suggestion(expr.span, "try using its async counterpart", suggestion, applicability); |
| 130 | +} |
| 131 | + |
| 132 | +/// Check whether a body is from an async function/closure. |
| 133 | +fn in_async_body(cx: &LateContext<'_>, body_id: BodyId) -> bool { |
| 134 | + let parent_node = cx.tcx.parent_hir_node(body_id.hir_id); |
| 135 | + match parent_node { |
| 136 | + Node::Expr(expr) => matches!( |
| 137 | + expr.kind, |
| 138 | + ExprKind::Closure(Closure { |
| 139 | + kind: ClosureKind::Coroutine(CoroutineKind::Desugared( |
| 140 | + CoroutineDesugaring::Async | CoroutineDesugaring::AsyncGen, |
| 141 | + _ |
| 142 | + )), |
| 143 | + .. |
| 144 | + }) |
| 145 | + ), |
| 146 | + Node::Item(Item { |
| 147 | + kind: ItemKind::Fn(fn_sig, ..), |
| 148 | + .. |
| 149 | + }) |
| 150 | + | Node::ImplItem(ImplItem { |
| 151 | + kind: ImplItemKind::Fn(fn_sig, _), |
| 152 | + .. |
| 153 | + }) |
| 154 | + | Node::TraitItem(TraitItem { |
| 155 | + kind: TraitItemKind::Fn(fn_sig, _), |
| 156 | + .. |
| 157 | + }) => fn_sig.header.is_async(), |
| 158 | + _ => false, |
| 159 | + } |
173 | 160 | }
|
0 commit comments