1 use crate::utils::{differing_macro_contexts, paths, snippet_with_applicability, span_lint_and_then};
2 use crate::utils::{is_copy, match_type};
3 use rustc::hir::intravisit::{walk_path, NestedVisitorMap, Visitor};
4 use rustc::lint::LateContext;
5 use rustc_data_structures::fx::FxHashSet;
6 use rustc_errors::Applicability;
7 use rustc_hir::{self, *};
8 use rustc_span::source_map::Span;
9 use rustc_span::symbol::Symbol;
11 use super::OPTION_MAP_UNWRAP_OR;
13 /// lint use of `map().unwrap_or()` for `Option`s
14 pub(super) fn lint<'a, 'tcx>(
15 cx: &LateContext<'a, 'tcx>,
16 expr: &rustc_hir::Expr<'_>,
17 map_args: &'tcx [rustc_hir::Expr<'_>],
18 unwrap_args: &'tcx [rustc_hir::Expr<'_>],
21 // lint if the caller of `map()` is an `Option`
22 if match_type(cx, cx.tables.expr_ty(&map_args[0]), &paths::OPTION) {
23 if !is_copy(cx, cx.tables.expr_ty(&unwrap_args[1])) {
24 // Do not lint if the `map` argument uses identifiers in the `map`
25 // argument that are also used in the `unwrap_or` argument
27 let mut unwrap_visitor = UnwrapVisitor {
29 identifiers: FxHashSet::default(),
31 unwrap_visitor.visit_expr(&unwrap_args[1]);
33 let mut map_expr_visitor = MapExprVisitor {
35 identifiers: unwrap_visitor.identifiers,
36 found_identifier: false,
38 map_expr_visitor.visit_expr(&map_args[1]);
40 if map_expr_visitor.found_identifier {
45 if differing_macro_contexts(unwrap_args[1].span, map_span) {
49 let mut applicability = Applicability::MachineApplicable;
50 // get snippet for unwrap_or()
51 let unwrap_snippet = snippet_with_applicability(cx, unwrap_args[1].span, "..", &mut applicability);
53 // comparing the snippet from source to raw text ("None") below is safe
54 // because we already have checked the type.
55 let arg = if unwrap_snippet == "None" { "None" } else { "a" };
56 let unwrap_snippet_none = unwrap_snippet == "None";
57 let suggest = if unwrap_snippet_none {
63 "called `map(f).unwrap_or({})` on an Option value. \
64 This can be done more directly by calling `{}` instead",
68 span_lint_and_then(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, |db| {
69 let map_arg_span = map_args[1].span;
71 let mut suggestion = vec![
74 String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
76 (expr.span.with_lo(unwrap_args[0].span.hi()), String::from("")),
79 if !unwrap_snippet_none {
80 suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)));
83 db.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability);
88 struct UnwrapVisitor<'a, 'tcx> {
89 cx: &'a LateContext<'a, 'tcx>,
90 identifiers: FxHashSet<Symbol>,
93 impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
94 fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
95 self.identifiers.insert(ident(path));
96 walk_path(self, path);
99 fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
100 NestedVisitorMap::All(&self.cx.tcx.hir())
104 struct MapExprVisitor<'a, 'tcx> {
105 cx: &'a LateContext<'a, 'tcx>,
106 identifiers: FxHashSet<Symbol>,
107 found_identifier: bool,
110 impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
111 fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
112 if self.identifiers.contains(&ident(path)) {
113 self.found_identifier = true;
116 walk_path(self, path);
119 fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
120 NestedVisitorMap::All(&self.cx.tcx.hir())
124 fn ident(path: &Path<'_>) -> Symbol {
127 .expect("segments should be composed of at least 1 element")