1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: msrvs:: { self , Msrv } ;
3
3
use clippy_utils:: source:: snippet_with_applicability;
4
- use clippy_utils:: ty:: { is_copy, is_type_diagnostic_item} ;
4
+ use clippy_utils:: ty:: { get_type_diagnostic_name, is_copy} ;
5
+ use clippy_utils:: { is_res_lang_ctor, path_res} ;
5
6
use rustc_data_structures:: fx:: FxHashSet ;
6
7
use rustc_errors:: Applicability ;
7
8
use rustc_hir:: def:: Res ;
8
9
use rustc_hir:: intravisit:: { Visitor , walk_path} ;
9
- use rustc_hir:: { ExprKind , HirId , Node , PatKind , Path , QPath } ;
10
+ use rustc_hir:: { ExprKind , HirId , LangItem , Node , PatKind , Path , QPath } ;
10
11
use rustc_lint:: LateContext ;
11
12
use rustc_middle:: hir:: nested_filter;
12
13
use rustc_span:: { Span , sym} ;
@@ -27,116 +28,120 @@ pub(super) fn check<'tcx>(
27
28
msrv : Msrv ,
28
29
) {
29
30
let recv_ty = cx. typeck_results ( ) . expr_ty ( recv) . peel_refs ( ) ;
30
- let is_option = is_type_diagnostic_item ( cx, recv_ty, sym:: Option ) ;
31
- let is_result = is_type_diagnostic_item ( cx, recv_ty, sym:: Result ) ;
31
+ let is_option = match get_type_diagnostic_name ( cx, recv_ty) {
32
+ Some ( sym:: Option ) => true ,
33
+ Some ( sym:: Result ) if msrv. meets ( cx, msrvs:: RESULT_MAP_OR ) => false ,
34
+ _ => return ,
35
+ } ;
36
+
37
+ let unwrap_arg_ty = cx. typeck_results ( ) . expr_ty ( unwrap_arg) ;
38
+ if !is_copy ( cx, unwrap_arg_ty) {
39
+ // Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
40
+ // borrowck errors, see #10579 for one such instance.
41
+ // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
42
+ // ```
43
+ // let x = vec![1, 2];
44
+ // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
45
+ // ```
46
+ // This compiles, but changing it to `map_or` will produce a compile error:
47
+ // ```
48
+ // let x = vec![1, 2];
49
+ // x.get(0..1).map_or(x, |s| s.to_vec())
50
+ // ^ moving `x` here
51
+ // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
52
+ // ```
53
+ // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
54
+ // before the call to `unwrap_or`.
55
+
56
+ let mut unwrap_visitor = UnwrapVisitor {
57
+ cx,
58
+ identifiers : FxHashSet :: default ( ) ,
59
+ } ;
60
+ unwrap_visitor. visit_expr ( unwrap_arg) ;
32
61
33
- if is_result && !msrv. meets ( cx, msrvs:: RESULT_MAP_OR ) {
34
- return ;
35
- }
62
+ let mut reference_visitor = ReferenceVisitor {
63
+ cx,
64
+ identifiers : unwrap_visitor. identifiers ,
65
+ unwrap_or_span : unwrap_arg. span ,
66
+ } ;
36
67
37
- // lint if the caller of `map()` is an `Option`
38
- if is_option || is_result {
39
- if !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( unwrap_arg) ) {
40
- // Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
41
- // borrowck errors, see #10579 for one such instance.
42
- // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
43
- // ```
44
- // let x = vec![1, 2];
45
- // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
46
- // ```
47
- // This compiles, but changing it to `map_or` will produce a compile error:
48
- // ```
49
- // let x = vec![1, 2];
50
- // x.get(0..1).map_or(x, |s| s.to_vec())
51
- // ^ moving `x` here
52
- // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
53
- // ```
54
- // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
55
- // before the call to `unwrap_or`.
56
-
57
- let mut unwrap_visitor = UnwrapVisitor {
58
- cx,
59
- identifiers : FxHashSet :: default ( ) ,
60
- } ;
61
- unwrap_visitor. visit_expr ( unwrap_arg) ;
62
-
63
- let mut reference_visitor = ReferenceVisitor {
64
- cx,
65
- identifiers : unwrap_visitor. identifiers ,
66
- unwrap_or_span : unwrap_arg. span ,
67
- } ;
68
-
69
- let body = cx. tcx . hir_body_owned_by ( cx. tcx . hir_enclosing_body_owner ( expr. hir_id ) ) ;
70
-
71
- // Visit the body, and return if we've found a reference
72
- if reference_visitor. visit_body ( body) . is_break ( ) {
73
- return ;
74
- }
75
- }
68
+ let body = cx. tcx . hir_body_owned_by ( cx. tcx . hir_enclosing_body_owner ( expr. hir_id ) ) ;
76
69
77
- if !unwrap_arg. span . eq_ctxt ( map_span) {
70
+ // Visit the body, and return if we've found a reference
71
+ if reference_visitor. visit_body ( body) . is_break ( ) {
78
72
return ;
79
73
}
74
+ }
75
+
76
+ if !unwrap_arg. span . eq_ctxt ( map_span) {
77
+ return ;
78
+ }
80
79
81
- // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
82
- let suggest_is_some_and = matches ! ( & unwrap_arg. kind, ExprKind :: Lit ( lit)
80
+ // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
81
+ let suggest_is_some_and = matches ! ( & unwrap_arg. kind, ExprKind :: Lit ( lit)
83
82
if matches!( lit. node, rustc_ast:: LitKind :: Bool ( false ) ) )
84
- && msrv. meets ( cx, msrvs:: OPTION_RESULT_IS_VARIANT_AND ) ;
85
-
86
- let mut applicability = Applicability :: MachineApplicable ;
87
- // get snippet for unwrap_or()
88
- let unwrap_snippet = snippet_with_applicability ( cx, unwrap_arg. span , ".." , & mut applicability) ;
89
- // lint message
90
- // comparing the snippet from source to raw text ("None") below is safe
91
- // because we already have checked the type.
92
- let unwrap_snippet_none = is_option && unwrap_snippet == "None" ;
93
- let arg = if unwrap_snippet_none {
94
- "None"
95
- } else if suggest_is_some_and {
96
- "false"
97
- } else {
98
- "<a>"
99
- } ;
100
- let suggest = if unwrap_snippet_none {
101
- "and_then(<f>)"
102
- } else if suggest_is_some_and {
103
- if is_result {
104
- "is_ok_and(<f>)"
105
- } else {
106
- "is_some_and(<f>)"
107
- }
83
+ && msrv. meets ( cx, msrvs:: OPTION_RESULT_IS_VARIANT_AND ) ;
84
+
85
+ let mut applicability = Applicability :: MachineApplicable ;
86
+ // get snippet for unwrap_or()
87
+ let unwrap_snippet = snippet_with_applicability ( cx, unwrap_arg. span , ".." , & mut applicability) ;
88
+ // lint message
89
+ // comparing the snippet from source to raw text ("None") below is safe
90
+ // because we already have checked the type.
91
+ let unwrap_snippet_none = is_option && is_res_lang_ctor ( cx, path_res ( cx, unwrap_arg) , LangItem :: OptionNone ) ;
92
+ let arg = if unwrap_snippet_none {
93
+ "None"
94
+ } else if suggest_is_some_and {
95
+ "false"
96
+ } else {
97
+ "<a>"
98
+ } ;
99
+ let suggest = if unwrap_snippet_none {
100
+ "and_then(<f>)"
101
+ } else if suggest_is_some_and {
102
+ if is_option {
103
+ "is_some_and(<f>)"
108
104
} else {
109
- "map_or(<a>, <f>)"
110
- } ;
111
- let msg = format ! (
112
- "called `map(<f>).unwrap_or({arg})` on an `{}` value" ,
113
- if is_option { "Option" } else { "Result" }
114
- ) ;
115
-
116
- span_lint_and_then ( cx, MAP_UNWRAP_OR , expr. span , msg, |diag| {
117
- let map_arg_span = map_arg. span ;
118
-
119
- let mut suggestion = vec ! [
120
- (
121
- map_span,
122
- String :: from( if unwrap_snippet_none {
123
- "and_then"
124
- } else if suggest_is_some_and {
125
- if is_result { "is_ok_and" } else { "is_some_and" }
126
- } else {
127
- "map_or"
128
- } ) ,
129
- ) ,
130
- ( expr. span. with_lo( unwrap_recv. span. hi( ) ) , String :: new( ) ) ,
131
- ] ;
132
-
133
- if !unwrap_snippet_none && !suggest_is_some_and {
134
- suggestion. push ( ( map_arg_span. with_hi ( map_arg_span. lo ( ) ) , format ! ( "{unwrap_snippet}, " ) ) ) ;
135
- }
136
-
137
- diag. multipart_suggestion ( format ! ( "use `{suggest}` instead" ) , suggestion, applicability) ;
138
- } ) ;
139
- }
105
+ "is_ok_and(<f>)"
106
+ }
107
+ } else {
108
+ "map_or(<a>, <f>)"
109
+ } ;
110
+ let msg = format ! (
111
+ "called `map(<f>).unwrap_or({arg})` on an `{}` value" ,
112
+ if is_option { "Option" } else { "Result" }
113
+ ) ;
114
+
115
+ span_lint_and_then ( cx, MAP_UNWRAP_OR , expr. span , msg, |diag| {
116
+ let map_arg_span = map_arg. span ;
117
+
118
+ let mut suggestion = vec ! [
119
+ (
120
+ map_span,
121
+ String :: from( if unwrap_snippet_none {
122
+ "and_then"
123
+ } else if suggest_is_some_and {
124
+ if is_option { "is_some_and" } else { "is_ok_and" }
125
+ } else {
126
+ let unwrap_arg_ty = unwrap_arg_ty. peel_refs( ) ;
127
+ if unwrap_arg_ty. is_array( )
128
+ && let unwrap_arg_ty_adj = cx. typeck_results( ) . expr_ty_adjusted( unwrap_arg) . peel_refs( )
129
+ && unwrap_arg_ty_adj. is_slice( )
130
+ {
131
+ return ;
132
+ }
133
+ "map_or"
134
+ } ) ,
135
+ ) ,
136
+ ( expr. span. with_lo( unwrap_recv. span. hi( ) ) , String :: new( ) ) ,
137
+ ] ;
138
+
139
+ if !unwrap_snippet_none && !suggest_is_some_and {
140
+ suggestion. push ( ( map_arg_span. with_hi ( map_arg_span. lo ( ) ) , format ! ( "{unwrap_snippet}, " ) ) ) ;
141
+ }
142
+
143
+ diag. multipart_suggestion ( format ! ( "use `{suggest}` instead" ) , suggestion, applicability) ;
144
+ } ) ;
140
145
}
141
146
142
147
struct UnwrapVisitor < ' a , ' tcx > {
0 commit comments