@@ -37,35 +37,33 @@ declare_clippy_lint! {
37
37
38
38
#[ derive( Default ) ]
39
39
pub struct UnnecessarySemicolon {
40
- last_statements : Vec < HirId > ,
40
+ last_statements : Vec < ( HirId , bool ) > ,
41
41
}
42
42
43
43
impl_lint_pass ! ( UnnecessarySemicolon => [ UNNECESSARY_SEMICOLON ] ) ;
44
44
45
45
impl UnnecessarySemicolon {
46
46
/// Enter or leave a block, remembering the last statement of the block.
47
47
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 ( )
54
50
&& let Some ( last_stmt) = block. stmts . last ( )
55
51
{
56
52
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 ( ) ) ) ;
58
55
} else {
59
56
self . last_statements . pop ( ) ;
60
57
}
61
58
}
62
59
}
63
60
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 > {
66
64
self . last_statements
67
65
. 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 ) )
69
67
}
70
68
}
71
69
@@ -90,8 +88,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon {
90
88
)
91
89
&& cx. typeck_results ( ) . expr_ty ( expr) == cx. tcx . types . unit
92
90
{
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
+ }
95
107
}
96
108
97
109
let semi_span = expr. span . shrink_to_hi ( ) . to ( stmt. span . shrink_to_hi ( ) ) ;
0 commit comments