Implement super let.

This commit is contained in:
Mara Bos
2025-03-27 18:29:58 +01:00
parent 9e14530c7c
commit 3123df8ef0
13 changed files with 126 additions and 48 deletions

View File

@@ -8,6 +8,7 @@
use std::mem;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, Visitor};
@@ -44,6 +45,8 @@ struct ScopeResolutionVisitor<'tcx> {
scope_tree: ScopeTree,
cx: Context,
extended_super_lets: FxHashMap<hir::ItemLocalId, Option<Scope>>,
}
/// Records the lifetime of a local variable as `cx.var_parent`
@@ -214,18 +217,29 @@ fn resolve_stmt<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, stmt: &'tcx hi
let stmt_id = stmt.hir_id.local_id;
debug!("resolve_stmt(stmt.id={:?})", stmt_id);
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
if let hir::StmtKind::Let(LetStmt { super_: Some(_), .. }) = stmt.kind {
// `super let` statement does not start a new scope, such that
//
// { super let x = identity(&temp()); &x }.method();
//
// behaves exactly as
//
// (&identity(&temp()).method();
intravisit::walk_stmt(visitor, stmt);
} else {
// Every statement will clean up the temporaries created during
// execution of that statement. Therefore each statement has an
// associated destruction scope that represents the scope of the
// statement plus its destructors, and thus the scope for which
// regions referenced by the destructors need to survive.
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
let prev_parent = visitor.cx.parent;
visitor.enter_node_scope_with_dtor(stmt_id, true);
intravisit::walk_stmt(visitor, stmt);
intravisit::walk_stmt(visitor, stmt);
visitor.cx.parent = prev_parent;
visitor.cx.parent = prev_parent;
}
}
fn resolve_expr<'tcx>(
@@ -485,10 +499,9 @@ fn resolve_local<'tcx>(
visitor: &mut ScopeResolutionVisitor<'tcx>,
pat: Option<&'tcx hir::Pat<'tcx>>,
init: Option<&'tcx hir::Expr<'tcx>>,
super_let: bool,
) {
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
let blk_scope = visitor.cx.var_parent;
debug!("resolve_local(pat={:?}, init={:?}, super_let={:?})", pat, init, super_let);
// As an exception to the normal rules governing temporary
// lifetimes, initializers in a let have a temporary lifetime
@@ -546,14 +559,50 @@ fn resolve_local<'tcx>(
// A, but the inner rvalues `a()` and `b()` have an extended lifetime
// due to rule C.
if super_let {
if let Some(scope) = visitor.extended_super_lets.remove(&pat.unwrap().hir_id.local_id) {
// This expression was lifetime-extended by a parent let binding. E.g.
//
// let a = {
// super let b = temp();
// &b
// };
//
// (Which needs to behave exactly as: let a = &temp();)
//
// Processing of `let a` will have already decided to extend the lifetime of this
// `super let` to its own var_scope. We use that scope.
visitor.cx.var_parent = scope;
} else {
// This `super let` is not subject to lifetime extension from a parent let binding. E.g.
//
// identity({ super let x = temp(); &x }).method();
//
// (Which needs to behave exactly as: identity(&temp()).method();)
//
// Iterate up to the enclosing destruction scope to find the same scope that will also
// be used for the result of the block itself.
while let Some(s) = visitor.cx.var_parent {
let parent = visitor.scope_tree.parent_map.get(&s).cloned();
if let Some(Scope { data: ScopeData::Destruction, .. }) = parent {
break;
}
visitor.cx.var_parent = parent;
}
}
}
if let Some(expr) = init {
record_rvalue_scope_if_borrow_expr(visitor, expr, blk_scope);
record_rvalue_scope_if_borrow_expr(visitor, expr, visitor.cx.var_parent);
if let Some(pat) = pat {
if is_binding_pat(pat) {
visitor.scope_tree.record_rvalue_candidate(
expr.hir_id,
RvalueCandidate { target: expr.hir_id.local_id, lifetime: blk_scope },
RvalueCandidate {
target: expr.hir_id.local_id,
lifetime: visitor.cx.var_parent,
},
);
}
}
@@ -565,6 +614,7 @@ fn resolve_local<'tcx>(
if let Some(expr) = init {
visitor.visit_expr(expr);
}
if let Some(pat) = pat {
visitor.visit_pat(pat);
}
@@ -642,6 +692,7 @@ fn resolve_local<'tcx>(
/// | [ ..., E&, ... ]
/// | ( ..., E&, ... )
/// | {...; E&}
/// | { super let ... = E&; ... }
/// | if _ { ...; E& } else { ...; E& }
/// | match _ { ..., _ => E&, ... }
/// | box E&
@@ -678,6 +729,13 @@ fn resolve_local<'tcx>(
if let Some(subexpr) = block.expr {
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
}
for stmt in block.stmts {
if let hir::StmtKind::Let(local) = stmt.kind
&& let Some(_) = local.super_
{
visitor.extended_super_lets.insert(local.pat.hir_id.local_id, blk_id);
}
}
}
hir::ExprKind::If(_, then_block, else_block) => {
record_rvalue_scope_if_borrow_expr(visitor, then_block, blk_id);
@@ -803,7 +861,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
local_id: body.value.hir_id.local_id,
data: ScopeData::Destruction,
});
resolve_local(this, None, Some(body.value));
resolve_local(this, None, Some(body.value), false);
}
})
}
@@ -821,7 +879,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
resolve_expr(self, ex, false);
}
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) {
resolve_local(self, Some(l.pat), l.init)
resolve_local(self, Some(l.pat), l.init, l.super_.is_some());
}
fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
let body = self.tcx.hir_body(c.body);
@@ -850,6 +908,7 @@ pub(crate) fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree {
cx: Context { parent: None, var_parent: None },
pessimistic_yield: false,
fixup_scopes: vec![],
extended_super_lets: Default::default(),
};
visitor.scope_tree.root_body = Some(body.value.hir_id);