Destroy temporaries at the end of expression statements. (#7513)

Instead of tracking the cleanup scope depth on entry to each scope,
track an "ambient" cleanup scope depth that's *after* the destructors of
local variables in that scope. This gets increased to include the
destructors of local variables when we create a name-binding
declaration. Then, when we reach a point where temporaries should be
destroyed, run cleanups that are after the ambient cleanup scope depth
on the stack. This happens:

* At the `;` of a statement expression.
* At the `)` of an `if` or `while` statement.
* After performing the implied `HasValue()` call in a `for` statement.

Per informal agreement with leads, this means we lifetime-extend all
temporaries created in the initializer of a name-binding declaration to
the full scope of that declaration, but that temporaries created in an
expression statement are destroyed at the `;`.
This commit is contained in:
Richard Smith
2026-07-16 15:12:39 +00:00
committed by GitHub
parent 81e495ee53
commit 703529fc55
37 changed files with 844 additions and 384 deletions
+17 -6
View File
@@ -43,9 +43,13 @@ static auto StartLoopHeader(Context& context, Parse::NodeId node_id)
// the loop exit if it is `false`.
static auto BranchAndStartLoopBody(Context& context, Parse::NodeId node_id,
SemIR::InstBlockId loop_header_id,
ScopeStack::CleanupScopeDepth continue_depth,
SemIR::InstId cond_value_id) -> void {
cond_value_id = ConvertToBoolValue(context, node_id, cond_value_id);
// Destroy any temporaries created computing the loop condition.
AddAndDiscardTemporaryCleanups(context);
// Branch to either the loop body or the loop exit block.
auto loop_body_id =
AddDominatedBlockAndBranchIf(context, node_id, cond_value_id);
@@ -63,8 +67,7 @@ static auto BranchAndStartLoopBody(Context& context, Parse::NodeId node_id,
{.break_target = loop_exit_id,
.break_depth = context.scope_stack().cleanup_scope_depth(),
.continue_target = loop_header_id,
.continue_depth =
context.scope_stack().enclosing_cleanup_scope_depth()});
.continue_depth = continue_depth});
}
// Finishes emitting the body for a `while`-like loop. Adds a back-edge to the
@@ -83,7 +86,7 @@ static auto FinishLoopBody(Context& context, Parse::NodeId node_id) -> void {
context.region_stack().AddToRegion(blocks.break_target, node_id);
// Clean up anything created in the loop header and pop the loop scope.
AddAndDiscardCleanups(context);
AddAndDiscardScopeCleanups(context);
context.scope_stack().Pop(/*check_unused=*/true);
}
@@ -105,7 +108,9 @@ auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
// Branch to either the loop body or the loop exit block, and start emitting
// the loop body.
BranchAndStartLoopBody(context, node_id, loop_header_id, cond_value_id);
BranchAndStartLoopBody(context, node_id, loop_header_id,
context.scope_stack().enclosing_cleanup_scope_depth(),
cond_value_id);
return true;
}
@@ -211,6 +216,7 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
// Start emitting the loop header block.
auto loop_header_id = StartLoopHeader(context, start_node_id);
auto continue_depth = context.scope_stack().ambient_cleanup_scope_depth();
// Call `<range>.(Iterate.Next)(&cursor)`.
auto cursor_type_inst_id = context.types().GetTypeInstId(cursor_type_id);
@@ -229,10 +235,15 @@ auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
// that.
element_id = ConvertToValueOrRefExpr(context, element_id);
// Temporaries in the optional and loop variables live for the duration of the
// loop body.
context.scope_stack().DeferCleanups();
// Branch to the loop body if the optional element has a value.
auto cond_value_id = CallOptionalAccessor(context, node_id, element_id,
CoreIdentifier::HasValue);
BranchAndStartLoopBody(context, node_id, loop_header_id, cond_value_id);
BranchAndStartLoopBody(context, node_id, loop_header_id, continue_depth,
cond_value_id);
// The loop pattern's initializer is now complete, and any bindings in it
// should be in scope.
@@ -250,7 +261,7 @@ auto HandleParseNode(Context& context, Parse::ForStatementId node_id) -> bool {
FinishLoopBody(context, node_id);
// Pop the scope that the range and cursor live in.
AddAndDiscardCleanups(context);
AddAndDiscardScopeCleanups(context);
context.scope_stack().Pop(/*check_unused=*/true);
return true;
}