@@ -4,8 +4,9 @@ use clippy_utils::in_macro;
4
4
use clippy_utils:: source:: snippet_with_macro_callsite;
5
5
use if_chain:: if_chain;
6
6
use rustc_errors:: Applicability ;
7
+ use rustc_hir:: intravisit:: FnKind ;
7
8
use rustc_hir:: ExprKind ;
8
- use rustc_hir:: { Block , BodyOwnerKind , StmtKind } ;
9
+ use rustc_hir:: { Block , Body , Expr , FnDecl , HirId , StmtKind } ;
9
10
use rustc_lint:: { LateContext , LateLintPass } ;
10
11
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
11
12
use rustc_span:: BytePos ;
@@ -36,94 +37,118 @@ declare_clippy_lint! {
36
37
37
38
declare_lint_pass ! ( SemicolonOutsideBlock => [ SEMICOLON_OUTSIDE_BLOCK ] ) ;
38
39
39
- /// Checks if an ExprKind is of an illegal variant (aka blocks that we don't want)
40
- /// to lint on as it's illegal or unnecessary to put a semicolon afterwards.
41
- /// This macro then inserts a `return` statement to return out of the check_block
42
- /// method.
43
- macro_rules! check_expr_return {
44
- ( $l: expr) => {
45
- if let ExprKind :: If ( ..) | ExprKind :: Loop ( ..) | ExprKind :: DropTemps ( ..) | ExprKind :: Match ( ..) = $l {
46
- return ;
47
- }
48
- } ;
49
- }
50
-
51
40
impl LateLintPass < ' _ > for SemicolonOutsideBlock {
52
- fn check_block ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx Block < ' tcx > ) {
53
- if_chain ! {
54
- if !in_macro( block. span) ;
55
- if block. expr. is_none( ) ;
56
- if let Some ( last) = block. stmts. last( ) ;
57
- if let StmtKind :: Semi ( expr) = last. kind;
58
- let t_expr = cx. typeck_results( ) . expr_ty( expr) ;
59
- if t_expr. is_unit( ) ;
60
- then {
61
- // make sure that the block does not belong to a function by iterating over the parents
62
- for ( hir_id, _) in cx. tcx. hir( ) . parent_iter( block. hir_id) {
63
- if let Some ( body_id) = cx. tcx. hir( ) . maybe_body_owned_by( hir_id) {
64
- // if we're in a body, check if it's an fn or a closure
65
- if cx. tcx. hir( ) . body_owner_kind( hir_id) . is_fn_or_closure( ) {
66
- let item_body = cx. tcx. hir( ) . body( body_id) ;
67
- if let ExprKind :: Block ( fn_block, _) = item_body. value. kind {
68
- // check for an illegal statement in the list of statements...
69
- for stmt in fn_block. stmts {
70
- if let StmtKind :: Expr ( pot_ille_expr) = stmt. kind {
71
- check_expr_return!( pot_ille_expr. kind) ;
72
- }
73
- }
74
-
75
- //.. the potential last statement ..
76
- if let Some ( last_expr) = fn_block. expr {
77
- check_expr_return!( last_expr. kind) ;
78
- }
41
+ fn check_fn (
42
+ & mut self ,
43
+ cx : & LateContext < ' tcx > ,
44
+ fn_kind : FnKind < ' tcx > ,
45
+ _: & ' tcx FnDecl < ' _ > ,
46
+ body : & ' tcx Body < ' _ > ,
47
+ _: Span ,
48
+ _: HirId ,
49
+ ) {
50
+ if let ExprKind :: Block ( block) = body. value . kind {
51
+ // also check this block if we're inside a closure
52
+ if matches ! ( fn_kind, FnKind :: Closure ) {
53
+ check_block ( cx, block) ;
54
+ }
79
55
80
- // .. or if this is the body of an fn
81
- if fn_block. hir_id == block. hir_id &&
82
- !matches!( cx. tcx. hir( ) . body_owner_kind( hir_id) , BodyOwnerKind :: Closure ) {
83
- return
84
- }
85
- }
86
- }
87
- }
56
+ // iterate over the statements and check if we find a potential
57
+ // block to check
58
+ for stmt in block. stmts {
59
+ match stmt. kind {
60
+ StmtKind :: Expr ( Expr {
61
+ kind : ExprKind :: Block ( bl, ..) ,
62
+ ..
63
+ } )
64
+ | StmtKind :: Semi ( Expr {
65
+ kind : ExprKind :: Block ( bl, ..) ,
66
+ ..
67
+ } ) => check_block ( cx, bl) ,
68
+ _ => ( ) ,
88
69
}
70
+ }
89
71
90
- // filter out other blocks and the desugared for loop
91
- check_expr_return!( expr. kind) ;
92
-
93
- // make sure we're also having the semicolon at the end of the expression...
94
- let expr_w_sem = expand_span_to_semicolon( cx, expr. span) ;
95
- let expr_snip = snippet_with_macro_callsite( cx, expr_w_sem, ".." ) ;
96
- let mut expr_sugg = expr_snip. to_string( ) ;
97
- expr_sugg. pop( ) ;
72
+ // check if the potential trailing expr is a block and check it if necessary
73
+ if let Some ( Expr {
74
+ kind : ExprKind :: Block ( bl, ..) ,
75
+ ..
76
+ } ) = block. expr
77
+ {
78
+ check_block ( cx, bl) ;
79
+ }
80
+ }
81
+ }
82
+ }
98
83
99
- // and the block
100
- let block_w_sem = expand_span_to_semicolon( cx, block. span) ;
101
- let mut block_snip: String = snippet_with_macro_callsite( cx, block_w_sem, ".." ) . to_string( ) ;
102
- if block_snip. ends_with( '\n' ) {
103
- block_snip. pop( ) ;
104
- }
84
+ /// Checks for a block if it's a target of this lint and spans a suggestion
85
+ /// if applicable. This method also recurses through all other statements that
86
+ /// are blocks.
87
+ fn check_block ( cx : & LateContext < ' _ > , block : & Block < ' _ > ) {
88
+ // check all subblocks
89
+ for stmt in block. stmts {
90
+ match stmt. kind {
91
+ StmtKind :: Expr ( Expr {
92
+ kind : ExprKind :: Block ( bl, ..) ,
93
+ ..
94
+ } )
95
+ | StmtKind :: Semi ( Expr {
96
+ kind : ExprKind :: Block ( bl, ..) ,
97
+ ..
98
+ } ) => check_block ( cx, bl) ,
99
+ _ => ( ) ,
100
+ }
101
+ }
102
+ // check the potential trailing expression
103
+ if let Some ( Expr {
104
+ kind : ExprKind :: Block ( bl, ..) ,
105
+ ..
106
+ } ) = block. expr
107
+ {
108
+ check_block ( cx, bl) ;
109
+ }
105
110
106
- // retrieve the suggestion
107
- let suggestion = if block_snip. ends_with( ';' ) {
108
- block_snip. replace( expr_snip. as_ref( ) , & format!( "{}" , expr_sugg. as_str( ) ) )
109
- } else {
110
- format!( "{};" , block_snip. replace( expr_snip. as_ref( ) , & format!( "{}" , expr_sugg. as_str( ) ) ) )
111
- } ;
111
+ if_chain ! {
112
+ if !in_macro( block. span) ;
113
+ if block. expr. is_none( ) ;
114
+ if let Some ( last) = block. stmts. last( ) ;
115
+ if let StmtKind :: Semi ( expr) = last. kind;
116
+ let t_expr = cx. typeck_results( ) . expr_ty( expr) ;
117
+ if t_expr. is_unit( ) ;
118
+ then {
119
+ // make sure we're also having the semicolon at the end of the expression...
120
+ let expr_w_sem = expand_span_to_semicolon( cx, expr. span) ;
121
+ let expr_snip = snippet_with_macro_callsite( cx, expr_w_sem, ".." ) ;
122
+ let mut expr_sugg = expr_snip. to_string( ) ;
123
+ expr_sugg. pop( ) ;
112
124
113
- span_lint_and_sugg(
114
- cx,
115
- SEMICOLON_OUTSIDE_BLOCK ,
116
- if block_snip. ends_with( ';' ) {
117
- block_w_sem
118
- } else {
119
- block. span
120
- } ,
121
- "consider moving the `;` outside the block for consistent formatting" ,
122
- "put the `;` outside the block" ,
123
- suggestion,
124
- Applicability :: MaybeIncorrect ,
125
- ) ;
125
+ // and the block
126
+ let block_w_sem = expand_span_to_semicolon( cx, block. span) ;
127
+ let mut block_snip: String = snippet_with_macro_callsite( cx, block_w_sem, ".." ) . to_string( ) ;
128
+ if block_snip. ends_with( '\n' ) {
129
+ block_snip. pop( ) ;
126
130
}
131
+
132
+ // retrieve the suggestion
133
+ let suggestion = if block_snip. ends_with( ';' ) {
134
+ block_snip. replace( expr_snip. as_ref( ) , & format!( "{}" , expr_sugg. as_str( ) ) )
135
+ } else {
136
+ format!( "{};" , block_snip. replace( expr_snip. as_ref( ) , & format!( "{}" , expr_sugg. as_str( ) ) ) )
137
+ } ;
138
+
139
+ span_lint_and_sugg(
140
+ cx,
141
+ SEMICOLON_OUTSIDE_BLOCK ,
142
+ if block_snip. ends_with( ';' ) {
143
+ block_w_sem
144
+ } else {
145
+ block. span
146
+ } ,
147
+ "consider moving the `;` outside the block for consistent formatting" ,
148
+ "put the `;` outside the block" ,
149
+ suggestion,
150
+ Applicability :: MaybeIncorrect ,
151
+ ) ;
127
152
}
128
153
}
129
154
}
0 commit comments