mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 16:38:09 +00:00
We allow using `InstId` as a `LocId` for diagnostics, which works great in `handle_*` functions, where we are working with non-canonical insts. And convert and pattern matching often are given non-canonical insts, which they can use, or pull apart to find some other inst, and use those as a location for diagnostics. However these operations can also be called with canonical insts, which have no location. In that case if we need to diagnose an error, it ends up not being attached to any code at all. Since we always have a `LocId` somewhere up the call stack, pass it along and in these places that handle canonical instructions too, use `GetInstLocationOrFallback()` to get a valid location for diagnostics. This is also similar to what eval does with its fallback `LocId`. The result is that many diagnostics now have a location where they had none before. It also means some insts don't get `<elided>` in formatted semir for not having a location at all and thus not being in range.
327 lines
13 KiB
C++
327 lines
13 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/call.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/control_flow.h"
|
|
#include "toolchain/check/convert.h"
|
|
#include "toolchain/check/core_identifier.h"
|
|
#include "toolchain/check/full_pattern_stack.h"
|
|
#include "toolchain/check/handle.h"
|
|
#include "toolchain/check/inst.h"
|
|
#include "toolchain/check/member_access.h"
|
|
#include "toolchain/check/operator.h"
|
|
#include "toolchain/check/pattern.h"
|
|
#include "toolchain/check/pattern_match.h"
|
|
#include "toolchain/check/type.h"
|
|
#include "toolchain/sem_ir/absolute_node_ref.h"
|
|
#include "toolchain/sem_ir/expr_info.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// Starts emitting the loop header for a `while`-like looping construct. Returns
|
|
// the loop header block ID.
|
|
static auto StartLoopHeader(Context& context, Parse::NodeId node_id)
|
|
-> SemIR::InstBlockId {
|
|
// Branch to the loop header block. Note that we create a new block here even
|
|
// if the current block is empty; this ensures that the loop always has a
|
|
// preheader block.
|
|
auto loop_header_id = AddDominatedBlockAndBranch(context, node_id);
|
|
context.inst_block_stack().Pop();
|
|
|
|
// Start emitting the loop header block.
|
|
context.inst_block_stack().Push(loop_header_id);
|
|
context.region_stack().AddToRegion(loop_header_id, node_id);
|
|
|
|
return loop_header_id;
|
|
}
|
|
|
|
// Starts emitting the loop body for a `while`-like looping construct. Converts
|
|
// `cond_value_id` to bool and branches to the loop body if it is `true` and to
|
|
// 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);
|
|
auto loop_exit_id = AddDominatedBlockAndBranch(context, node_id);
|
|
context.inst_block_stack().Pop();
|
|
|
|
// Start emitting the loop body.
|
|
context.inst_block_stack().Push(loop_body_id);
|
|
context.region_stack().AddToRegion(loop_body_id, node_id);
|
|
|
|
// Allow `break` and `continue` in this scope. `continue` will destroy
|
|
// temporaries in the loop header, `break` will not, as the loop exit block
|
|
// also destroys those temporaries.
|
|
context.break_continue_stack().push_back(
|
|
{.break_target = loop_exit_id,
|
|
.break_depth = context.scope_stack().cleanup_scope_depth(),
|
|
.continue_target = loop_header_id,
|
|
.continue_depth = continue_depth,
|
|
.return_scope_decl_id =
|
|
context.scope_stack().IsInFunctionScope()
|
|
? context.scope_stack().GetReturnScopeDeclId()
|
|
: SemIR::InstId::None});
|
|
}
|
|
|
|
// Finishes emitting the body for a `while`-like loop. Adds a back-edge to the
|
|
// loop header, and starts emitting in the loop exit block.
|
|
static auto FinishLoopBody(Context& context, Parse::NodeId node_id) -> void {
|
|
auto blocks = context.break_continue_stack().pop_back_val();
|
|
|
|
// Add the loop backedge.
|
|
AddBranchWithCleanups(context, node_id, blocks.continue_target,
|
|
blocks.continue_depth);
|
|
context.inst_block_stack().Pop();
|
|
|
|
// Start emitting the loop exit block.
|
|
context.scope_stack().DiscardCleanupsSince(blocks.break_depth);
|
|
context.inst_block_stack().Push(blocks.break_target);
|
|
context.region_stack().AddToRegion(blocks.break_target, node_id);
|
|
|
|
// Clean up anything created in the loop header and pop the loop scope.
|
|
AddAndDiscardScopeCleanups(context);
|
|
context.scope_stack().Pop(/*check_unused=*/true);
|
|
}
|
|
|
|
// `while`
|
|
// -------
|
|
|
|
auto HandleParseNode(Context& context, Parse::WhileConditionStartId node_id)
|
|
-> bool {
|
|
context.scope_stack().PushForSameRegion(ScopeStack::CleanupScopeKind::Owned);
|
|
context.node_stack().Push(node_id, StartLoopHeader(context, node_id));
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::WhileConditionId node_id)
|
|
-> bool {
|
|
auto cond_value_id = context.node_stack().PopExpr();
|
|
auto loop_header_id =
|
|
context.node_stack().Pop<Parse::NodeKind::WhileConditionStart>();
|
|
|
|
// Branch to either the loop body or the loop exit block, and start emitting
|
|
// the loop body.
|
|
BranchAndStartLoopBody(context, node_id, loop_header_id,
|
|
context.scope_stack().enclosing_cleanup_scope_depth(),
|
|
cond_value_id);
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::WhileStatementId node_id)
|
|
-> bool {
|
|
FinishLoopBody(context, node_id);
|
|
return true;
|
|
}
|
|
|
|
// `for`
|
|
// -----
|
|
|
|
auto HandleParseNode(Context& context, Parse::ForHeaderStartId node_id)
|
|
-> bool {
|
|
// Create a scope that will eventually hold the range and cursor of the for
|
|
// loop.
|
|
context.scope_stack().PushForSameRegion(ScopeStack::CleanupScopeKind::Owned);
|
|
|
|
// Create a scope for any variables introduced in the pattern.
|
|
context.scope_stack().PushForSameRegion(ScopeStack::CleanupScopeKind::Owned);
|
|
|
|
// Begin an implicit let declaration context for the pattern.
|
|
context.decl_introducer_state_stack().Push<Lex::TokenKind::Let>();
|
|
context.pattern_block_stack().Push();
|
|
context.full_pattern_stack().PushNameBindingDecl();
|
|
BeginExprRegionForPattern(context);
|
|
|
|
context.node_stack().Push(node_id);
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::ForInId node_id) -> bool {
|
|
EndExprRegionForPattern(context, context.node_stack());
|
|
auto pattern_block_id = context.pattern_block_stack().Pop();
|
|
AddInst<SemIR::NameBindingDecl>(context, node_id,
|
|
{.pattern_block_id = pattern_block_id});
|
|
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Let>();
|
|
context.full_pattern_stack().StartPatternInitializer();
|
|
|
|
// Create a temporary scope to hold the range expression and the cursor. This
|
|
// comes before the pattern in control flow order, but we'll reorder temporary
|
|
// destruction later.
|
|
context.scope_stack().PushForSameRegion(ScopeStack::CleanupScopeKind::Owned);
|
|
return true;
|
|
}
|
|
|
|
// For a value or reference of type `Optional(T)`, call the given accessor.
|
|
static auto CallOptionalAccessor(Context& context, Parse::NodeId node_id,
|
|
SemIR::InstId optional_id,
|
|
CoreIdentifier accessor_name)
|
|
-> SemIR::InstId {
|
|
auto accessor_name_id = context.core_identifiers().AddNameId(accessor_name);
|
|
auto accessor_id =
|
|
PerformMemberAccess(context, node_id, optional_id, accessor_name_id);
|
|
return PerformCall(context, node_id, accessor_id, {});
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::ForHeaderId node_id) -> bool {
|
|
auto range_id = context.node_stack().PopExpr();
|
|
auto pattern_id = context.node_stack().PopPattern();
|
|
auto start_node_id =
|
|
context.node_stack().PopForSoloNodeId<Parse::NodeKind::ForHeaderStart>();
|
|
|
|
// Convert the range expression to a value or reference so that we can use it
|
|
// multiple times.
|
|
// TODO: If this produces a temporary, its lifetime should presumably be
|
|
// extended to cover the loop body.
|
|
range_id = ConvertToValueOrRefExpr(context, range_id);
|
|
|
|
// Create the cursor variable.
|
|
// TODO: Produce a custom diagnostic if the range operand can't be used as a
|
|
// range.
|
|
// TODO: We need to allocate the `VarStorage` before building the operator.
|
|
// The current order risks violating the preconditions on `Initialize` and
|
|
// risks violating the topological ordering of insts.
|
|
auto cursor_id =
|
|
BuildUnaryOperator(context, node_id,
|
|
{.interface_name = CoreIdentifier::Iterate,
|
|
.op_name = CoreIdentifier::NewCursor},
|
|
range_id);
|
|
auto cursor_type_id = context.insts().Get(cursor_id).type_id();
|
|
PendingBlock cursor_var_block(&context);
|
|
auto cursor_var_id = cursor_var_block.AddInstWithCleanup<SemIR::VarStorage>(
|
|
node_id,
|
|
{.type_id = cursor_type_id, .pattern_id = SemIR::AbsoluteInstId::None});
|
|
auto init_result = Initialize(
|
|
context, node_id,
|
|
// Disable broken lint that suggests a "fix" that doesn't compile.
|
|
// NOLINTNEXTLINE(performance-move-const-arg)
|
|
std::move(cursor_var_id), std::move(cursor_var_block), cursor_id);
|
|
AddInst<SemIR::Assign>(
|
|
context, node_id,
|
|
{.lhs_id = init_result.storage_id, .rhs_id = init_result.init_id});
|
|
cursor_var_id = init_result.storage_id;
|
|
|
|
// Now we're finished with the loop initialization, merge the scope containing
|
|
// the range expression into its grandparent scope. The parent scope currently
|
|
// contains the loop variables, whereas the current scope contains the range,
|
|
// and that's backwards from a control flow and destruction order perspective.
|
|
// We created the grandparent scope for this purpose when handling the
|
|
// ForHeaderStart node.
|
|
context.scope_stack().MergeTopScopeIntoGrandparentAndPop();
|
|
|
|
// 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)(ref cursor)`.
|
|
// Don't diagnose if the call to `Iterate.NewCursor` failed, since this call
|
|
// will likely also fail for the same reason.
|
|
// TODO: We should only perform the impl lookup once.
|
|
auto element_id =
|
|
BuildBinaryOperator(context, node_id,
|
|
{.interface_name = CoreIdentifier::Iterate,
|
|
.op_name = CoreIdentifier::Next},
|
|
range_id, cursor_var_id,
|
|
/*diagnose=*/cursor_id != SemIR::ErrorInst::InstId);
|
|
// We need to convert away from an initializing expression in order to call
|
|
// `HasValue` and then separately pattern-match against the element.
|
|
// TODO: Instead, form a `.Some(pattern_id)` pattern and pattern-match against
|
|
// 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, continue_depth,
|
|
cond_value_id);
|
|
|
|
// The loop pattern's initializer is now complete, and any bindings in it
|
|
// should be in scope.
|
|
context.full_pattern_stack().EndPatternInitializer();
|
|
|
|
// Initialize the pattern from `<element>.Get()`.
|
|
auto element_value_id =
|
|
CallOptionalAccessor(context, node_id, element_id, CoreIdentifier::Get);
|
|
LocalPatternMatch(context, node_id, pattern_id, element_value_id);
|
|
context.full_pattern_stack().PopFullPattern();
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::ForStatementId node_id) -> bool {
|
|
FinishLoopBody(context, node_id);
|
|
|
|
// Pop the scope that the range and cursor live in.
|
|
AddAndDiscardScopeCleanups(context);
|
|
context.scope_stack().Pop(/*check_unused=*/true);
|
|
return true;
|
|
}
|
|
|
|
// `break`
|
|
// -------
|
|
|
|
auto HandleParseNode(Context& context, Parse::BreakStatementStartId node_id)
|
|
-> bool {
|
|
auto& stack = context.break_continue_stack();
|
|
if (stack.empty() || !context.scope_stack().IsInFunctionScope() ||
|
|
stack.back().return_scope_decl_id !=
|
|
context.scope_stack().GetReturnScopeDeclId()) {
|
|
CARBON_DIAGNOSTIC(BreakOutsideLoop, Error,
|
|
"`break` can only be used in a loop");
|
|
context.emitter().Emit(node_id, BreakOutsideLoop);
|
|
} else {
|
|
AddBranchWithCleanups(context, node_id, stack.back().break_target,
|
|
stack.back().break_depth);
|
|
}
|
|
|
|
context.inst_block_stack().Pop();
|
|
context.inst_block_stack().PushUnreachable();
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& /*context*/, Parse::BreakStatementId /*node_id*/)
|
|
-> bool {
|
|
return true;
|
|
}
|
|
|
|
// `continue`
|
|
// ----------
|
|
|
|
auto HandleParseNode(Context& context, Parse::ContinueStatementStartId node_id)
|
|
-> bool {
|
|
auto& stack = context.break_continue_stack();
|
|
if (stack.empty() || !context.scope_stack().IsInFunctionScope() ||
|
|
stack.back().return_scope_decl_id !=
|
|
context.scope_stack().GetReturnScopeDeclId()) {
|
|
CARBON_DIAGNOSTIC(ContinueOutsideLoop, Error,
|
|
"`continue` can only be used in a loop");
|
|
context.emitter().Emit(node_id, ContinueOutsideLoop);
|
|
} else {
|
|
AddBranchWithCleanups(context, node_id, stack.back().continue_target,
|
|
stack.back().continue_depth);
|
|
}
|
|
|
|
context.inst_block_stack().Pop();
|
|
context.inst_block_stack().PushUnreachable();
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& /*context*/,
|
|
Parse::ContinueStatementId /*node_id*/) -> bool {
|
|
return true;
|
|
}
|
|
|
|
} // namespace Carbon::Check
|