|
| 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::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, ¬e); |
| 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: 'a> { |
| 81 | + cx: &'a LateContext<'a, 'tcx>, |
| 82 | + identifiers: FxHashSet<Symbol>, |
| 83 | +} |
| 84 | + |
| 85 | +impl<'a, 'tcx: 'a> 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: 'a> { |
| 97 | + cx: &'a LateContext<'a, 'tcx>, |
| 98 | + identifiers: FxHashSet<Symbol>, |
| 99 | + found_identifier: bool, |
| 100 | +} |
| 101 | + |
| 102 | +impl<'a, 'tcx: 'a> 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 | +} |
0 commit comments