]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/option_map_unwrap_or.rs
Rustup to rust-lang/rust#66942
[rust.git] / clippy_lints / src / methods / option_map_unwrap_or.rs
1 use crate::utils::paths;
2 use crate::utils::{is_copy, match_type, snippet, span_lint, span_note_and_lint};
3 use rustc::hir::intravisit::{walk_path, NestedVisitorMap, Visitor};
4 use rustc::hir::{self, *};
5 use rustc::lint::LateContext;
6 use rustc_data_structures::fx::FxHashSet;
7 use syntax_pos::symbol::Symbol;
8
9 use super::OPTION_MAP_UNWRAP_OR;
10
11 /// lint use of `map().unwrap_or()` for `Option`s
12 pub(super) fn lint<'a, 'tcx>(
13     cx: &LateContext<'a, 'tcx>,
14     expr: &hir::Expr<'_>,
15     map_args: &'tcx [hir::Expr<'_>],
16     unwrap_args: &'tcx [hir::Expr<'_>],
17 ) {
18     // lint if the caller of `map()` is an `Option`
19     if match_type(cx, cx.tables.expr_ty(&map_args[0]), &paths::OPTION) {
20         if !is_copy(cx, cx.tables.expr_ty(&unwrap_args[1])) {
21             // Do not lint if the `map` argument uses identifiers in the `map`
22             // argument that are also used in the `unwrap_or` argument
23
24             let mut unwrap_visitor = UnwrapVisitor {
25                 cx,
26                 identifiers: FxHashSet::default(),
27             };
28             unwrap_visitor.visit_expr(&unwrap_args[1]);
29
30             let mut map_expr_visitor = MapExprVisitor {
31                 cx,
32                 identifiers: unwrap_visitor.identifiers,
33                 found_identifier: false,
34             };
35             map_expr_visitor.visit_expr(&map_args[1]);
36
37             if map_expr_visitor.found_identifier {
38                 return;
39             }
40         }
41
42         // get snippets for args to map() and unwrap_or()
43         let map_snippet = snippet(cx, map_args[1].span, "..");
44         let unwrap_snippet = snippet(cx, unwrap_args[1].span, "..");
45         // lint message
46         // comparing the snippet from source to raw text ("None") below is safe
47         // because we already have checked the type.
48         let arg = if unwrap_snippet == "None" { "None" } else { "a" };
49         let suggest = if unwrap_snippet == "None" {
50             "and_then(f)"
51         } else {
52             "map_or(a, f)"
53         };
54         let msg = &format!(
55             "called `map(f).unwrap_or({})` on an Option value. \
56              This can be done more directly by calling `{}` instead",
57             arg, suggest
58         );
59         // lint, with note if neither arg is > 1 line and both map() and
60         // unwrap_or() have the same span
61         let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
62         let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt();
63         if same_span && !multiline {
64             let suggest = if unwrap_snippet == "None" {
65                 format!("and_then({})", map_snippet)
66             } else {
67                 format!("map_or({}, {})", unwrap_snippet, map_snippet)
68             };
69             let note = format!(
70                 "replace `map({}).unwrap_or({})` with `{}`",
71                 map_snippet, unwrap_snippet, suggest
72             );
73             span_note_and_lint(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, expr.span, &note);
74         } else if same_span && multiline {
75             span_lint(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg);
76         };
77     }
78 }
79
80 struct UnwrapVisitor<'a, 'tcx> {
81     cx: &'a LateContext<'a, 'tcx>,
82     identifiers: FxHashSet<Symbol>,
83 }
84
85 impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
86     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
87         self.identifiers.insert(ident(path));
88         walk_path(self, path);
89     }
90
91     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
92         NestedVisitorMap::All(&self.cx.tcx.hir())
93     }
94 }
95
96 struct MapExprVisitor<'a, 'tcx> {
97     cx: &'a LateContext<'a, 'tcx>,
98     identifiers: FxHashSet<Symbol>,
99     found_identifier: bool,
100 }
101
102 impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
103     fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
104         if self.identifiers.contains(&ident(path)) {
105             self.found_identifier = true;
106             return;
107         }
108         walk_path(self, path);
109     }
110
111     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
112         NestedVisitorMap::All(&self.cx.tcx.hir())
113     }
114 }
115
116 fn ident(path: &Path<'_>) -> Symbol {
117     path.segments
118         .last()
119         .expect("segments should be composed of at least 1 element")
120         .ident
121         .name
122 }