@@ -37,35 +37,33 @@ declare_clippy_lint! {
3737
3838#[ derive( Default ) ]
3939pub struct UnnecessarySemicolon {
40- last_statements : Vec < HirId > ,
40+ last_statements : Vec < ( HirId , bool ) > ,
4141}
4242
4343impl_lint_pass ! ( UnnecessarySemicolon => [ UNNECESSARY_SEMICOLON ] ) ;
4444
4545impl UnnecessarySemicolon {
4646 /// Enter or leave a block, remembering the last statement of the block.
4747 fn handle_block ( & mut self , cx : & LateContext < ' _ > , block : & Block < ' _ > , enter : bool ) {
48- // Up to edition 2021, removing the semicolon of the last statement of a block
49- // may result in the scrutinee temporary values to live longer than the block
50- // variables. To avoid this problem, we do not lint the last statement of an
51- // expressionless block.
52- if cx. tcx . sess . edition ( ) <= Edition2021
53- && block. expr . is_none ( )
48+ // The last statement of an expressionless block deserves a special treatment.
49+ if block. expr . is_none ( )
5450 && let Some ( last_stmt) = block. stmts . last ( )
5551 {
5652 if enter {
57- self . last_statements . push ( last_stmt. hir_id ) ;
53+ let block_ty = cx. typeck_results ( ) . node_type ( block. hir_id ) ;
54+ self . last_statements . push ( ( last_stmt. hir_id , block_ty. is_unit ( ) ) ) ;
5855 } else {
5956 self . last_statements . pop ( ) ;
6057 }
6158 }
6259 }
6360
64- /// Checks if `stmt` is the last statement in an expressionless block for edition ≤ 2021.
65- fn is_last_in_block ( & self , stmt : & Stmt < ' _ > ) -> bool {
61+ /// Checks if `stmt` is the last statement in an expressionless block. In this case,
62+ /// return `Some` with a boolean which is `true` if the block type is `()`.
63+ fn is_last_in_block ( & self , stmt : & Stmt < ' _ > ) -> Option < bool > {
6664 self . last_statements
6765 . last ( )
68- . is_some_and ( |last_stmt_id| last_stmt_id == & stmt. hir_id )
66+ . and_then ( | & ( stmt_id , is_unit ) | ( stmt_id == stmt. hir_id ) . then_some ( is_unit ) )
6967 }
7068}
7169
@@ -90,8 +88,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon {
9088 )
9189 && cx. typeck_results ( ) . expr_ty ( expr) == cx. tcx . types . unit
9290 {
93- if self . is_last_in_block ( stmt) && leaks_droppable_temporary_with_limited_lifetime ( cx, expr) {
94- return ;
91+ if let Some ( block_is_unit) = self . is_last_in_block ( stmt) {
92+ if cx. tcx . sess . edition ( ) <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime ( cx, expr) {
93+ // The expression contains temporaries with limited lifetimes in edition lower than 2024. Those may
94+ // survive until after the end of the current scope instead of until the end of the statement, so do
95+ // not lint this situation.
96+ return ;
97+ }
98+
99+ if !block_is_unit {
100+ // Although the expression returns `()`, the block doesn't. This may happen if the expression
101+ // returns early in all code paths, such as a `return value` in the condition of an `if` statement,
102+ // in which case the block type would be `!`. Do not lint in this case, as the statement would
103+ // become the block expression; the block type would become `()` and this may not type correctly
104+ // if the expected type for the block is not `()`.
105+ return ;
106+ }
95107 }
96108
97109 let semi_span = expr. span . shrink_to_hi ( ) . to ( stmt. span . shrink_to_hi ( ) ) ;
0 commit comments