]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/option_map_unwrap_or.rs
Auto merge of #88175 - camsteffen:let-desugar-span, r=Manishearth
[rust.git] / clippy_lints / src / methods / option_map_unwrap_or.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::differing_macro_contexts;
3 use clippy_utils::source::snippet_with_applicability;
4 use clippy_utils::ty::is_copy;
5 use clippy_utils::ty::is_type_diagnostic_item;
6 use rustc_data_structures::fx::FxHashSet;
7 use rustc_errors::Applicability;
8 use rustc_hir::intravisit::{walk_path, NestedVisitorMap, Visitor};
9 use rustc_hir::{self, HirId, Path};
10 use rustc_lint::LateContext;
11 use rustc_middle::hir::map::Map;
12 use rustc_span::source_map::Span;
13 use rustc_span::{sym, Symbol};
14
15 use super::MAP_UNWRAP_OR;
16
17 /// lint use of `map().unwrap_or()` for `Option`s
18 pub(super) fn check<'tcx>(
19     cx: &LateContext<'tcx>,
20     expr: &rustc_hir::Expr<'_>,
21     recv: &rustc_hir::Expr<'_>,
22     map_arg: &'tcx rustc_hir::Expr<'_>,
23     unwrap_recv: &rustc_hir::Expr<'_>,
24     unwrap_arg: &'tcx rustc_hir::Expr<'_>,
25     map_span: Span,
26 ) {
27     // lint if the caller of `map()` is an `Option`
28     if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
29         if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
30             // Do not lint if the `map` argument uses identifiers in the `map`
31             // argument that are also used in the `unwrap_or` argument
32
33             let mut unwrap_visitor = UnwrapVisitor {
34                 cx,
35                 identifiers: FxHashSet::default(),
36             };
37             unwrap_visitor.visit_expr(unwrap_arg);
38
39             let mut map_expr_visitor = MapExprVisitor {
40                 cx,
41                 identifiers: unwrap_visitor.identifiers,
42                 found_identifier: false,
43             };
44             map_expr_visitor.visit_expr(map_arg);
45
46             if map_expr_visitor.found_identifier {
47                 return;
48             }
49         }
50
51         if differing_macro_contexts(unwrap_arg.span, map_span) {
52             return;
53         }
54
55         let mut applicability = Applicability::MachineApplicable;
56         // get snippet for unwrap_or()
57         let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
58         // lint message
59         // comparing the snippet from source to raw text ("None") below is safe
60         // because we already have checked the type.
61         let arg = if unwrap_snippet == "None" { "None" } else { "<a>" };
62         let unwrap_snippet_none = unwrap_snippet == "None";
63         let suggest = if unwrap_snippet_none {
64             "and_then(<f>)"
65         } else {
66             "map_or(<a>, <f>)"
67         };
68         let msg = &format!(
69             "called `map(<f>).unwrap_or({})` on an `Option` value. \
70             This can be done more directly by calling `{}` instead",
71             arg, suggest
72         );
73
74         span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
75             let map_arg_span = map_arg.span;
76
77             let mut suggestion = vec![
78                 (
79                     map_span,
80                     String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
81                 ),
82                 (expr.span.with_lo(unwrap_recv.span.hi()), String::from("")),
83             ];
84
85             if !unwrap_snippet_none {
86                 suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)));
87             }
88
89             diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability);
90         });
91     }
92 }
93
94 struct UnwrapVisitor<'a, 'tcx> {
95     cx: &'a LateContext<'tcx>,
96     identifiers: FxHashSet<Symbol>,
97 }
98
99 impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
100     type Map = Map<'tcx>;
101
102     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
103         self.identifiers.insert(ident(path));
104         walk_path(self, path);
105     }
106
107     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
108         NestedVisitorMap::All(self.cx.tcx.hir())
109     }
110 }
111
112 struct MapExprVisitor<'a, 'tcx> {
113     cx: &'a LateContext<'tcx>,
114     identifiers: FxHashSet<Symbol>,
115     found_identifier: bool,
116 }
117
118 impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
119     type Map = Map<'tcx>;
120
121     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
122         if self.identifiers.contains(&ident(path)) {
123             self.found_identifier = true;
124             return;
125         }
126         walk_path(self, path);
127     }
128
129     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
130         NestedVisitorMap::All(self.cx.tcx.hir())
131     }
132 }
133
134 fn ident(path: &Path<'_>) -> Symbol {
135     path.segments
136         .last()
137         .expect("segments should be composed of at least 1 element")
138         .ident
139         .name
140 }