|
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}; |
12 |
| -use rustc_lint::{LateContext, LateLintPass}; |
13 |
| -use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 8 | +use rustc_hir::{ |
| 9 | + Body, BodyId, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, ImplItem, ImplItemKind, |
| 10 | + Item, ItemKind, Node, TraitItem, TraitItemKind, |
| 11 | +}; |
| 12 | +use rustc_lint::{LateContext, LateLintPass, LintContext}; |
| 13 | +use rustc_middle::lint::in_external_macro; |
| 14 | +use rustc_session::impl_lint_pass; |
14 | 15 | use rustc_span::Span;
|
15 | 16 |
|
16 | 17 | declare_clippy_lint! {
|
@@ -43,131 +44,118 @@ declare_clippy_lint! {
|
43 | 44 | /// ```
|
44 | 45 | #[clippy::version = "1.74.0"]
|
45 | 46 | pub UNNECESSARY_BLOCKING_OPS,
|
46 |
| - nursery, |
| 47 | + pedantic, |
47 | 48 | "blocking operations in an async context"
|
48 | 49 | }
|
49 | 50 |
|
50 | 51 | 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 |
| 52 | + blocking_ops: Vec<SuggestedPath>, |
| 53 | + /// Map of resolved funtion `def_id` with suggestion string after checking crate |
54 | 54 | 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, |
| 55 | + /// Tracking whether a body is async after entering it. |
| 56 | + body_asyncness: Vec<bool>, |
57 | 57 | }
|
58 | 58 |
|
59 | 59 | impl UnnecessaryBlockingOps {
|
60 |
| - pub(crate) fn new(blocking_ops: Vec<String>, blocking_ops_with_suggs: Vec<[String; 2]>) -> Self { |
| 60 | + pub(crate) fn new(blocking_ops: Vec<SuggestedPath>) -> Self { |
61 | 61 | Self {
|
62 | 62 | blocking_ops,
|
63 |
| - blocking_ops_with_suggs, |
64 | 63 | id_with_suggs: FxHashMap::default(),
|
65 |
| - visited_block: HirIdSet::default(), |
| 64 | + body_asyncness: vec![], |
66 | 65 | }
|
67 | 66 | }
|
68 | 67 | }
|
69 | 68 |
|
70 | 69 | impl_lint_pass!(UnnecessaryBlockingOps => [UNNECESSARY_BLOCKING_OPS]);
|
71 | 70 |
|
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 | 71 | impl<'tcx> LateLintPass<'tcx> for UnnecessaryBlockingOps {
|
99 | 72 | 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 { |
| 73 | + let full_fn_list = self.blocking_ops.iter().map(|p| (p.path(), p.suggestion())); |
| 74 | + for (path_str, maybe_sugg_str) in full_fn_list { |
| 75 | + let path: Vec<&str> = path_str.split("::").collect(); |
121 | 76 | for did in def_path_def_ids(cx, &path) {
|
122 | 77 | self.id_with_suggs.insert(did, maybe_sugg_str.map(ToOwned::to_owned));
|
123 | 78 | }
|
124 | 79 | }
|
125 | 80 | }
|
126 | 81 |
|
127 | 82 | 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 |
| - { |
| 83 | + if is_lint_allowed(cx, UNNECESSARY_BLOCKING_OPS, body.value.hir_id) { |
131 | 84 | return;
|
132 | 85 | }
|
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 |
| - ); |
| 86 | + self.body_asyncness.push(in_async_body(cx, body.id())); |
| 87 | + } |
| 88 | + |
| 89 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 90 | + if !in_external_macro(cx.sess(), expr.span) |
| 91 | + && matches!(self.body_asyncness.last(), Some(true)) |
| 92 | + && let ExprKind::Call(call, _) = &expr.kind |
| 93 | + && let Some(call_did) = fn_def_id(cx, expr) |
| 94 | + && let Some(maybe_sugg) = self.id_with_suggs.get(&call_did) |
| 95 | + { |
| 96 | + span_lint_and_then( |
| 97 | + cx, |
| 98 | + UNNECESSARY_BLOCKING_OPS, |
| 99 | + call.span, |
| 100 | + "blocking function call detected in an async body", |
| 101 | + |diag| { |
| 102 | + if let Some(sugg_fn_path) = maybe_sugg { |
| 103 | + make_suggestion(diag, cx, expr, call.span, sugg_fn_path); |
153 | 104 | }
|
154 |
| - _ => {} |
155 |
| - } |
156 |
| - ControlFlow::<()>::Continue(()) |
157 |
| - }); |
| 105 | + }, |
| 106 | + ); |
158 | 107 | }
|
159 | 108 | }
|
| 109 | + |
| 110 | + fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &'tcx Body<'tcx>) { |
| 111 | + self.body_asyncness.pop(); |
| 112 | + } |
160 | 113 | }
|
161 | 114 |
|
162 |
| -fn make_suggestion(diag: &mut Diagnostic, cx: &LateContext<'_>, expr: &Expr<'_>, fn_span: Span, sugg_fn_path: &str) { |
163 |
| - let mut applicability = Applicability::Unspecified; |
| 115 | +fn make_suggestion(diag: &mut Diag<'_, ()>, cx: &LateContext<'_>, expr: &Expr<'_>, fn_span: Span, sugg_fn_path: &str) { |
| 116 | + // Suggestion should only be offered when user specified it in the configuration file, |
| 117 | + // so we only assume it can be fixed here if only the path could be found. |
| 118 | + let mut applicability = if def_path_def_ids(cx, &sugg_fn_path.split("::").collect::<Vec<_>>()) |
| 119 | + .next() |
| 120 | + .is_some() |
| 121 | + { |
| 122 | + Applicability::MaybeIncorrect |
| 123 | + } else { |
| 124 | + Applicability::Unspecified |
| 125 | + }; |
| 126 | + |
164 | 127 | let args_span = expr.span.with_lo(fn_span.hi());
|
165 | 128 | let args_snippet = snippet_with_applicability(cx, args_span, "..", &mut applicability);
|
166 | 129 | 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 |
| - ); |
| 130 | + diag.span_suggestion(expr.span, "try using its async counterpart", suggestion, applicability); |
| 131 | +} |
| 132 | + |
| 133 | +/// Check whether a body is from an async function/closure. |
| 134 | +fn in_async_body(cx: &LateContext<'_>, body_id: BodyId) -> bool { |
| 135 | + let parent_node = cx.tcx.parent_hir_node(body_id.hir_id); |
| 136 | + match parent_node { |
| 137 | + Node::Expr(expr) => matches!( |
| 138 | + expr.kind, |
| 139 | + ExprKind::Closure(Closure { |
| 140 | + kind: ClosureKind::Coroutine(CoroutineKind::Desugared( |
| 141 | + CoroutineDesugaring::Async | CoroutineDesugaring::AsyncGen, |
| 142 | + _ |
| 143 | + )), |
| 144 | + .. |
| 145 | + }) |
| 146 | + ), |
| 147 | + Node::Item(Item { |
| 148 | + kind: ItemKind::Fn(fn_sig, ..), |
| 149 | + .. |
| 150 | + }) |
| 151 | + | Node::ImplItem(ImplItem { |
| 152 | + kind: ImplItemKind::Fn(fn_sig, _), |
| 153 | + .. |
| 154 | + }) |
| 155 | + | Node::TraitItem(TraitItem { |
| 156 | + kind: TraitItemKind::Fn(fn_sig, _), |
| 157 | + .. |
| 158 | + }) => fn_sig.header.is_async(), |
| 159 | + _ => false, |
| 160 | + } |
173 | 161 | }
|
0 commit comments