@@ -4,8 +4,9 @@ use clippy_utils::in_macro;
44use clippy_utils:: source:: snippet_with_macro_callsite;
55use if_chain:: if_chain;
66use rustc_errors:: Applicability ;
7+ use rustc_hir:: intravisit:: FnKind ;
78use rustc_hir:: ExprKind ;
8- use rustc_hir:: { Block , BodyOwnerKind , StmtKind } ;
9+ use rustc_hir:: { Block , Body , Expr , FnDecl , HirId , StmtKind } ;
910use rustc_lint:: { LateContext , LateLintPass } ;
1011use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1112use rustc_span:: BytePos ;
@@ -36,94 +37,118 @@ declare_clippy_lint! {
3637
3738declare_lint_pass ! ( SemicolonOutsideBlock => [ SEMICOLON_OUTSIDE_BLOCK ] ) ;
3839
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-
5140impl 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+ }
7955
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+ _ => ( ) ,
8869 }
70+ }
8971
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+ }
9883
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+ }
105110
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( ) ;
112124
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( ) ;
126130 }
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+ ) ;
127152 }
128153 }
129154}
0 commit comments