|
| 1 | +// noise reduction, remove before committing! |
| 2 | +#![allow(unused_variables)] |
| 3 | + |
| 4 | +use rustc_errors::Applicability; |
| 5 | +use rustc_hir::*; |
| 6 | +use rustc_hir::def::Res; |
| 7 | +use rustc_lint::{LateContext, LateLintPass}; |
| 8 | +use rustc_session::declare_lint_pass; |
| 9 | +use rustc_span::Span; |
| 10 | +use clippy_utils::{match_def_path, fn_def_id}; |
| 11 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 12 | +use clippy_utils::source::snippet_with_applicability; |
| 13 | + |
| 14 | +declare_clippy_lint! { |
| 15 | + /// ### What it does |
| 16 | + /// Detects usage of `Option::and_then` and `bool::then_some` that could |
| 17 | + /// be replaced with `Option::filter`. |
| 18 | + /// |
| 19 | + /// ### Why is this bad? |
| 20 | + /// Needless complexity, uses recent and uncommon stdlib funtions instead of |
| 21 | + /// one older function. |
| 22 | + /// |
| 23 | + /// ### Example |
| 24 | + /// ```no_run |
| 25 | + /// // example code where clippy issues a warning |
| 26 | + /// ``` |
| 27 | + /// Use instead: |
| 28 | + /// ```no_run |
| 29 | + /// // example code which does not raise clippy warning |
| 30 | + /// ``` |
| 31 | + #[clippy::version = "1.81.0"] |
| 32 | + pub AND_THEN_THEN_SOME, |
| 33 | + nursery, |
| 34 | + "default lint description" |
| 35 | +} |
| 36 | + |
| 37 | +// note: `Option::filter` is older than `bool::then_some`, |
| 38 | +// so no msrv check is required. |
| 39 | +declare_lint_pass!(AndThenThenSome => [AND_THEN_THEN_SOME]); |
| 40 | + |
| 41 | +impl<'tcx> LateLintPass<'tcx> for AndThenThenSome { |
| 42 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { |
| 43 | + match expr.kind { |
| 44 | + ExprKind::MethodCall(method_name, selfarg, [ arg ], span) => { |
| 45 | + //let option_id = cx.tcx.get_diagnostic_item(sym::Option); |
| 46 | + // TODO: check if type of reciever is diagnostic item Option. |
| 47 | + let tckr = cx.typeck_results(); |
| 48 | + let def_id = tckr.type_dependent_def_id(expr.hir_id).unwrap(); |
| 49 | + //dbg!(method_name, selfarg, arg); |
| 50 | + if match_def_path(cx, def_id, |
| 51 | + &["core", "option", "Option", "and_then"]) |
| 52 | + { |
| 53 | + if let Some((closure_args, predicate)) = then_some_closure_arg(cx, arg) { |
| 54 | + //dbg!(predicate); |
| 55 | + show_sugg(cx, expr.span, selfarg, closure_args, predicate); |
| 56 | + } |
| 57 | + } |
| 58 | + } |
| 59 | + // TODO: check for call as associated function |
| 60 | + _ => {}, |
| 61 | + } |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +// `|v| X.then_some(v)` -> Some((span"|v|", X)) |
| 66 | +fn then_some_closure_arg<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) |
| 67 | + -> Option<(Span, &'tcx Expr<'tcx>)> |
| 68 | +{ |
| 69 | + match expr.kind { |
| 70 | + ExprKind::Closure(Closure{ |
| 71 | + fn_decl: FnDecl{ inputs: [ Ty{ hir_id: arg_id, ..} ], .. }, |
| 72 | + body, |
| 73 | + .. |
| 74 | + }) => { |
| 75 | + if let Node::Expr(expr) = cx.tcx.hir_node(body.hir_id) { |
| 76 | + //dbg!(arg_id); |
| 77 | + if let Some(body) = peel_closure_body(cx, expr, *arg_id) { |
| 78 | + Some((cx.tcx.hir().span(*arg_id), body)) |
| 79 | + } else { |
| 80 | + None |
| 81 | + } |
| 82 | + } else { |
| 83 | + None |
| 84 | + } |
| 85 | + }, |
| 86 | + _ => None, |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +fn peel_closure_body<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, closure_arg_id: HirId) -> Option<&'tcx Expr<'tcx>> { |
| 91 | + |
| 92 | + //dbg!(cx.tcx.hir_node(closure_arg_id)); |
| 93 | + match expr.kind { |
| 94 | + ExprKind::Block(_block, _) => |
| 95 | + // recurse somehow, maybe lift { x; y.a() } into { x; y }.a() |
| 96 | + todo!(), |
| 97 | + ExprKind::MethodCall(_path, selfarg, [ arg ], _span) => { |
| 98 | + if is_then_some(cx, expr) && |
| 99 | + is_local_defined_at(cx, arg, closure_arg_id) |
| 100 | + { |
| 101 | + // the argument to then_some is the same as that given to the closure |
| 102 | + Some(selfarg) |
| 103 | + } else { |
| 104 | + None |
| 105 | + } |
| 106 | + } |
| 107 | + ExprKind::Call(func /*@ Expr{ kind: ExprKind::Path(QPath::Resolved(_, Path{ res: Res::Local(local_hid), ..})), ..},*/, [ pred, arg ]) => { |
| 108 | + //dbg!(func, fn_def_id(cx, expr)); |
| 109 | + //todo!(); |
| 110 | + if is_then_some(cx, func) && dbg!(is_local_defined_at(cx, arg, closure_arg_id)) { |
| 111 | + //todo!("it worked!!"); |
| 112 | + Some(pred) |
| 113 | + |
| 114 | + } else { |
| 115 | + //todo!("nope"); |
| 116 | + None |
| 117 | + } |
| 118 | + } |
| 119 | + _ => None, |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +fn is_local_defined_at<'tcx>(cx: &LateContext<'tcx>, local: &Expr<'_>, arg_hid: HirId) -> bool { |
| 124 | + //dbg!(local); |
| 125 | + match local.kind { |
| 126 | + ExprKind::Path(QPath::Resolved(_, Path{ res: Res::Local(local_hid), .. })) => { |
| 127 | + // XXX" this is the best way i could find to compare if a local refers to a specific closure argument. |
| 128 | + if let Node::Pat(Pat{ span: local_span, .. }) = cx.tcx.hir_node(*local_hid) && |
| 129 | + let Node::Ty(Ty{ span: arg_span, .. }) = cx.tcx.hir_node(arg_hid) && |
| 130 | + local_span == arg_span |
| 131 | + { |
| 132 | + true |
| 133 | + } else { |
| 134 | + false |
| 135 | + } |
| 136 | + } |
| 137 | + // is not local at all, so definitly isn't a local defined at the given position |
| 138 | + _ => false, |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +fn show_sugg(cx: &LateContext<'_>, span: Span, selfarg: &Expr<'_>, closure_args: Span, predicate: &Expr<'_>) { |
| 143 | + let mut appl = Applicability::MachineApplicable; |
| 144 | + let sugg = format!( |
| 145 | + "{}.filter(|{}| {})", |
| 146 | + snippet_with_applicability(cx, selfarg.span, "<OPTION>", &mut appl), |
| 147 | + snippet_with_applicability(cx, closure_args, "<ARGS>", &mut appl), |
| 148 | + snippet_with_applicability(cx, predicate.span, "<PREDICATE>", &mut appl)); |
| 149 | + span_lint_and_sugg(cx, AND_THEN_THEN_SOME, span, |
| 150 | + "use of `and_then` + `then_some` is equivelent to `filter`", |
| 151 | + "use `Option::filter` instead", |
| 152 | + sugg, appl); |
| 153 | +} |
| 154 | + |
| 155 | +fn is_then_some(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| 156 | + if let Some(def_id) = fn_def_id(cx, expr) { |
| 157 | + match_def_path( |
| 158 | + cx, dbg!(def_id), |
| 159 | + &["core", "bool", "<impl bool>", "then_some"]) |
| 160 | + } else { |
| 161 | + //todo!("not type dependent"); |
| 162 | + false |
| 163 | + } |
| 164 | +} |
| 165 | + |
0 commit comments