|
| 1 | +use std::ops::ControlFlow; |
| 2 | + |
1 | 3 | use clippy_utils::diagnostics::span_lint_and_sugg;
|
2 | 4 | use clippy_utils::sugg::Sugg;
|
| 5 | +use clippy_utils::ty::is_copy; |
3 | 6 | use clippy_utils::{
|
4 | 7 | CaptureKind, can_move_expr_to_closure, eager_or_lazy, higher, is_else_clause, is_in_const_context,
|
5 | 8 | is_res_lang_ctor, peel_blocks, peel_hir_expr_while,
|
6 | 9 | };
|
| 10 | +use rustc_data_structures::fx::FxHashSet; |
7 | 11 | use rustc_errors::Applicability;
|
8 | 12 | use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
|
9 | 13 | use rustc_hir::def::Res;
|
| 14 | +use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; |
10 | 15 | use rustc_hir::{
|
11 |
| - Arm, BindingMode, Expr, ExprKind, MatchSource, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, UnOp, |
| 16 | + Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path, |
| 17 | + QPath, UnOp, |
12 | 18 | };
|
13 | 19 | use rustc_lint::{LateContext, LateLintPass};
|
| 20 | +use rustc_middle::hir::nested_filter; |
14 | 21 | use rustc_session::declare_lint_pass;
|
15 | 22 | use rustc_span::SyntaxContext;
|
16 | 23 |
|
@@ -110,11 +117,12 @@ fn format_option_in_sugg(cond_sugg: Sugg<'_>, as_ref: bool, as_mut: bool) -> Str
|
110 | 117 | )
|
111 | 118 | }
|
112 | 119 |
|
| 120 | +#[expect(clippy::too_many_lines)] |
113 | 121 | fn try_get_option_occurrence<'tcx>(
|
114 | 122 | cx: &LateContext<'tcx>,
|
115 | 123 | ctxt: SyntaxContext,
|
116 | 124 | pat: &Pat<'tcx>,
|
117 |
| - expr: &Expr<'_>, |
| 125 | + expr: &'tcx Expr<'_>, |
118 | 126 | if_then: &'tcx Expr<'_>,
|
119 | 127 | if_else: &'tcx Expr<'_>,
|
120 | 128 | ) -> Option<OptionOccurrence> {
|
@@ -182,6 +190,26 @@ fn try_get_option_occurrence<'tcx>(
|
182 | 190 | Some(CaptureKind::Ref(Mutability::Not)) | None => (),
|
183 | 191 | }
|
184 | 192 | }
|
| 193 | + } else if !is_copy(cx, cx.typeck_results().expr_ty(expr)) |
| 194 | + // TODO: Cover more match cases |
| 195 | + && matches!( |
| 196 | + expr.kind, |
| 197 | + ExprKind::Field(_, _) | ExprKind::Path(_) | ExprKind::Index(_, _, _) |
| 198 | + ) |
| 199 | + { |
| 200 | + let mut condition_visitor = ConditionVisitor { |
| 201 | + cx, |
| 202 | + identifiers: FxHashSet::default(), |
| 203 | + }; |
| 204 | + condition_visitor.visit_expr(cond_expr); |
| 205 | + |
| 206 | + let mut reference_visitor = ReferenceVisitor { |
| 207 | + cx, |
| 208 | + identifiers: condition_visitor.identifiers, |
| 209 | + }; |
| 210 | + if reference_visitor.visit_expr(none_body).is_break() { |
| 211 | + return None; |
| 212 | + } |
185 | 213 | }
|
186 | 214 |
|
187 | 215 | let mut app = Applicability::Unspecified;
|
@@ -219,6 +247,60 @@ fn try_get_option_occurrence<'tcx>(
|
219 | 247 | None
|
220 | 248 | }
|
221 | 249 |
|
| 250 | +/// This visitor looks for bindings in the <then> block that mention a local variable. Then gets the |
| 251 | +/// identifiers. The list of identifiers will then be used to check if the <none> block mentions the |
| 252 | +/// same local. See [`ReferenceVisitor`] for more. |
| 253 | +struct ConditionVisitor<'a, 'tcx> { |
| 254 | + cx: &'a LateContext<'tcx>, |
| 255 | + identifiers: FxHashSet<HirId>, |
| 256 | +} |
| 257 | + |
| 258 | +impl<'tcx> Visitor<'tcx> for ConditionVisitor<'_, 'tcx> { |
| 259 | + type NestedFilter = nested_filter::All; |
| 260 | + |
| 261 | + fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) { |
| 262 | + if let Res::Local(local_id) = path.res |
| 263 | + && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) |
| 264 | + && let PatKind::Binding(_, local_id, ..) = pat.kind |
| 265 | + { |
| 266 | + self.identifiers.insert(local_id); |
| 267 | + } |
| 268 | + walk_path(self, path); |
| 269 | + } |
| 270 | + |
| 271 | + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { |
| 272 | + self.cx.tcx |
| 273 | + } |
| 274 | +} |
| 275 | + |
| 276 | +/// This visitor checks if the <none> block contains references to the local variables that are |
| 277 | +/// used in the <then> block. See [`ConditionVisitor`] for more. |
| 278 | +struct ReferenceVisitor<'a, 'tcx> { |
| 279 | + cx: &'a LateContext<'tcx>, |
| 280 | + identifiers: FxHashSet<HirId>, |
| 281 | +} |
| 282 | + |
| 283 | +impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { |
| 284 | + type NestedFilter = nested_filter::All; |
| 285 | + type Result = ControlFlow<()>; |
| 286 | + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> ControlFlow<()> { |
| 287 | + if let ExprKind::Path(ref path) = expr.kind |
| 288 | + && let QPath::Resolved(_, path) = path |
| 289 | + && let Res::Local(local_id) = path.res |
| 290 | + && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) |
| 291 | + && let PatKind::Binding(_, local_id, ..) = pat.kind |
| 292 | + && self.identifiers.contains(&local_id) |
| 293 | + { |
| 294 | + return ControlFlow::Break(()); |
| 295 | + } |
| 296 | + walk_expr(self, expr) |
| 297 | + } |
| 298 | + |
| 299 | + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { |
| 300 | + self.cx.tcx |
| 301 | + } |
| 302 | +} |
| 303 | + |
222 | 304 | fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> {
|
223 | 305 | if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind {
|
224 | 306 | let res = cx.qpath_res(qpath, pat.hir_id);
|
|
0 commit comments