Files
carbon-lang/toolchain/check/control_flow.cpp
T
Richard Smith bf5d2a2ace Add support for -> auto deduced return types. (#7931)
The return type is deduced from the function's `return` statements
(currently required to all have the same type), or from its first
`returned var`. Calls to generic functions with deduced return types are
not yet supported.

When a function has a deduced return type, we defer the return type
portion of the callee pattern match until deduction, and during
deduction we extend the decl block and the pattern block with the
return-related instructions.

Conversions for `return expr;` statements are deferred until the return
type has been deduced. The design requires that we take the common type
of the returned types as the deduced return type, so we can't in general
perform any conversions for returned expressions until we reach the end
of the function. This is accomplished by inserting a `splice_block`
where the conversions will go, and later updating that instruction to
contain the conversion.

Assisted-by: Claude Opus 5.5 via Antigravity
2026-10-15 00:30:09 +00:00

214 lines
8.1 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/control_flow.h"
#include <initializer_list>
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/call.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/scope_stack.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
template <typename BranchNode, typename... Args>
static auto AddDominatedBlockAndBranchImpl(Context& context,
Parse::NodeId node_id, Args... args)
-> SemIR::InstBlockId {
if (!context.inst_block_stack().is_current_block_reachable()) {
return SemIR::InstBlockId::Unreachable;
}
auto block_id = context.inst_blocks().AddPlaceholder();
AddInst<BranchNode>(context, node_id, {block_id, args...});
return block_id;
}
auto AddDominatedBlockAndBranch(Context& context, Parse::NodeId node_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::Branch>(context, node_id);
}
auto AddDominatedBlockAndBranchWithArg(Context& context, Parse::NodeId node_id,
SemIR::InstId arg_id)
-> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchWithArg>(context, node_id,
arg_id);
}
auto AddDominatedBlockAndBranchIf(Context& context, Parse::NodeId node_id,
SemIR::InstId cond_id) -> SemIR::InstBlockId {
return AddDominatedBlockAndBranchImpl<SemIR::BranchIf>(context, node_id,
cond_id);
}
auto AddConvergenceBlockAndPush(Context& context, Parse::NodeId node_id,
int num_blocks) -> void {
CARBON_CHECK(num_blocks >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for ([[maybe_unused]] auto _ : llvm::seq(num_blocks)) {
if (context.inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = context.inst_blocks().AddPlaceholder();
}
CARBON_CHECK(node_id.has_value());
AddInst<SemIR::Branch>(context, node_id, {.target_id = new_block_id});
}
context.inst_block_stack().Pop();
}
context.inst_block_stack().Push(new_block_id);
context.region_stack().AddToRegion(new_block_id, node_id);
}
auto AddConvergenceBlockWithArgAndPush(
Context& context, Parse::NodeId node_id,
std::initializer_list<SemIR::InstId> block_args) -> SemIR::InstId {
CARBON_CHECK(block_args.size() >= 2, "no convergence");
SemIR::InstBlockId new_block_id = SemIR::InstBlockId::Unreachable;
for (auto arg_id : block_args) {
if (context.inst_block_stack().is_current_block_reachable()) {
if (new_block_id == SemIR::InstBlockId::Unreachable) {
new_block_id = context.inst_blocks().AddPlaceholder();
}
AddInst<SemIR::BranchWithArg>(
context, node_id, {.target_id = new_block_id, .arg_id = arg_id});
}
context.inst_block_stack().Pop();
}
context.inst_block_stack().Push(new_block_id);
context.region_stack().AddToRegion(new_block_id, node_id);
// Acquire the result value.
SemIR::TypeId result_type_id =
context.insts().Get(*block_args.begin()).type_id();
return AddInst<SemIR::BlockArg>(
context, node_id, {.type_id = result_type_id, .block_id = new_block_id});
}
auto SetBlockArgResultBeforeConstantUse(Context& context,
SemIR::InstId select_id,
SemIR::InstId cond_id,
SemIR::InstId if_true,
SemIR::InstId if_false) -> void {
CARBON_CHECK(context.insts().Is<SemIR::BlockArg>(select_id));
// Determine the constant result based on the condition value.
SemIR::ConstantId const_id = SemIR::ConstantId::NotConstant;
auto cond_const_id = context.constant_values().Get(cond_id);
if (!cond_const_id.is_concrete()) {
// Symbolic or non-constant condition means a non-constant result.
} else if (auto literal =
context.constant_values().TryGetInstAs<SemIR::BoolLiteral>(
cond_const_id)) {
const_id = context.constant_values().Get(
literal.value().value.ToBool() ? if_true : if_false);
} else {
CARBON_CHECK(cond_const_id == SemIR::ErrorInst::ConstantId,
"Unexpected constant branch condition.");
const_id = SemIR::ErrorInst::ConstantId;
}
if (const_id.is_constant()) {
CARBON_VLOG_TO(context.vlog_stream(), "Constant: {0} -> {1}\n",
context.insts().Get(select_id),
context.constant_values().GetInstId(const_id));
context.constant_values().Set(select_id, const_id);
}
}
auto IsCurrentPositionReachable(Context& context) -> bool {
if (!context.inst_block_stack().is_current_block_reachable()) {
return false;
}
// Our current position is at the end of a reachable block. That position is
// reachable unless the previous instruction is a terminator instruction.
auto block_contents = context.inst_block_stack().PeekCurrentBlockContents();
if (block_contents.empty()) {
return true;
}
const auto& last_inst = context.insts().Get(block_contents.back());
return last_inst.kind().terminator_kind() !=
SemIR::TerminatorKind::Terminator;
}
auto MaybeAddCleanupForInst(Context& context, SemIR::InstId inst_id) -> void {
if (!context.scope_stack().IsCleanupScope()) {
return;
}
context.scope_stack().PushCleanupFor(inst_id);
}
// Adds cleanups for variables added after `depth`.
static auto AddCleanups(Context& context, ScopeStack::CleanupScopeDepth depth)
-> void {
for (auto destroy_id :
llvm::reverse(context.scope_stack().GetCleanupsSince(depth))) {
// TODO: This does the `Destroy` lookup and call at every cleanup block.
// Control flow can lead to the same variable being destroyed by multiple
// cleanup blocks, so we'll want to avoid this in the future.
BuildSelfDestructCall(context, destroy_id);
}
}
auto AddAndDiscardTemporaryCleanups(Context& context) -> void {
auto depth = context.scope_stack().ambient_cleanup_scope_depth();
AddCleanups(context, depth);
context.scope_stack().DiscardCleanupsSince(depth);
}
auto AddAndDiscardScopeCleanups(Context& context) -> void {
auto depth = context.scope_stack().enclosing_cleanup_scope_depth();
AddCleanups(context, depth);
context.scope_stack().DiscardCleanupsSince(depth);
}
// TODO: When we have multiple branches or returns in the same function, share
// cleanup instructions rather than duplicating them for each branch or return.
// We would do this by creating cleanup blocks that hold the relevant cleanups,
// and chaining between cleanup blocks to allow cleanup instruction reuse. We
// could either share cleanup blocks only if they end in the same instruction,
// or generate branches out of the cleanups to the chosen destination.
//
// For example:
//
// fn F() {
// var a: C;
// if (...) {
// // Cleanup block 1: destroy a, return
// return;
// }
//
// var b: C;
// if (...) {
// // Cleanup block 2: destroy b, reuse cleanup block 1.
// return;
// }
//
// DoSomethingMore();
// // Cleanup block 3: reuse cleanup block 2.
// }
auto AddBranchWithCleanups(Context& context, SemIR::LocId loc_id,
SemIR::InstBlockId target_id,
ScopeStack::CleanupScopeDepth depth) -> void {
AddCleanups(context, depth);
AddInst<SemIR::Branch>(context, loc_id, {.target_id = target_id});
}
auto AddReturnInstWithCleanups(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
AddCleanups(context, context.scope_stack().function_cleanup_scope_depth());
return AddInst(context, loc_id_and_inst);
}
} // namespace Carbon::Check