unnecessary_semicolon: do not lint if it may cause borrow errors
Before edition 2024, some temporaries used in scrutinees in a `match` used as the last expression of a block may outlive some referenced local variables. Prevent those cases from happening by checking that alive temporaries with significant drop do have a static lifetime. The check is performed only for edition 2021 and earlier, and for the last statement if it would become the last expression of the block.
This commit is contained in:
parent
71ba2cf1e5
commit
9dca770aec
7 changed files with 243 additions and 6 deletions
|
|
@ -973,6 +973,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
|
||||
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_semicolon::UnnecessarySemicolon));
|
||||
store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default());
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::leaks_droppable_temporary_with_limited_lifetime;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{ExprKind, MatchSource, Stmt, StmtKind};
|
||||
use rustc_hir::{Block, ExprKind, HirId, MatchSource, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::edition::Edition::Edition2021;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -33,10 +35,50 @@ declare_clippy_lint! {
|
|||
"unnecessary semicolon after expression returning `()`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]);
|
||||
#[derive(Default)]
|
||||
pub struct UnnecessarySemicolon {
|
||||
last_statements: Vec<HirId>,
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for UnnecessarySemicolon {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
|
||||
impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]);
|
||||
|
||||
impl UnnecessarySemicolon {
|
||||
/// Enter or leave a block, remembering the last statement of the block.
|
||||
fn handle_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>, enter: bool) {
|
||||
// Up to edition 2021, removing the semicolon of the last statement of a block
|
||||
// may result in the scrutinee temporary values to live longer than the block
|
||||
// variables. To avoid this problem, we do not lint the last statement of an
|
||||
// expressionless block.
|
||||
if cx.tcx.sess.edition() <= Edition2021
|
||||
&& block.expr.is_none()
|
||||
&& let Some(last_stmt) = block.stmts.last()
|
||||
{
|
||||
if enter {
|
||||
self.last_statements.push(last_stmt.hir_id);
|
||||
} else {
|
||||
self.last_statements.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `stmt` is the last statement in an expressionless block for edition ≤ 2021.
|
||||
fn is_last_in_block(&self, stmt: &Stmt<'_>) -> bool {
|
||||
self.last_statements
|
||||
.last()
|
||||
.is_some_and(|last_stmt_id| last_stmt_id == &stmt.hir_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon {
|
||||
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
self.handle_block(cx, block, true);
|
||||
}
|
||||
|
||||
fn check_block_post(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
|
||||
self.handle_block(cx, block, false);
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
|
||||
// rustfmt already takes care of removing semicolons at the end
|
||||
// of loops.
|
||||
if let StmtKind::Semi(expr) = stmt.kind
|
||||
|
|
@ -48,6 +90,10 @@ impl LateLintPass<'_> for UnnecessarySemicolon {
|
|||
)
|
||||
&& cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit
|
||||
{
|
||||
if self.is_last_in_block(stmt) && leaks_droppable_temporary_with_limited_lifetime(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi());
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue