mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Unify initialization and conversion logic (#3255)
Combine the initialization, implicit conversion, and value category conversion functions into a single function. This substantially reduces the duplication between these steps, and ensures that we support the same set of conversions in all these contexts. This also fixes some issues where we would not use the proper value representation for tuples and structs after performing implicit conversions.
This commit is contained in:
+1
-638
@@ -4,11 +4,11 @@
|
||||
|
||||
#include "toolchain/check/context.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "common/vlog.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/Sequence.h"
|
||||
#include "toolchain/check/declaration_name_stack.h"
|
||||
#include "toolchain/check/node_block_stack.h"
|
||||
@@ -251,643 +251,6 @@ auto Context::is_current_position_reachable() -> bool {
|
||||
SemIR::TerminatorKind::Terminator;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// A handle to a new block that may be modified, with copy-on-write semantics.
|
||||
//
|
||||
// The constructor is given the ID of an existing block that provides the
|
||||
// initial contents of the new block. The new block is lazily allocated; if no
|
||||
// modifications have been made, the `id()` function will return the original
|
||||
// block ID.
|
||||
//
|
||||
// This is intended to avoid an unnecessary block allocation in the case where
|
||||
// the new block ends up being exactly the same as the original block.
|
||||
class CopyOnWriteBlock {
|
||||
public:
|
||||
CopyOnWriteBlock(SemIR::File& file, SemIR::NodeBlockId source_id)
|
||||
: file_(file), source_id_(source_id) {}
|
||||
|
||||
auto id() -> SemIR::NodeBlockId const { return id_; }
|
||||
|
||||
auto Set(int i, SemIR::NodeId value) -> void {
|
||||
if (file_.GetNodeBlock(id_)[i] == value) {
|
||||
return;
|
||||
}
|
||||
if (id_ == source_id_) {
|
||||
id_ = file_.AddNodeBlock(file_.GetNodeBlock(source_id_));
|
||||
}
|
||||
file_.GetNodeBlock(id_)[i] = value;
|
||||
}
|
||||
|
||||
private:
|
||||
SemIR::File& file_;
|
||||
SemIR::NodeBlockId source_id_;
|
||||
SemIR::NodeBlockId id_ = source_id_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// A block of code that contains pending instructions that might be needed but
|
||||
// that haven't been inserted yet.
|
||||
class Context::PendingBlock {
|
||||
public:
|
||||
PendingBlock(Context& context) : context_(context) {}
|
||||
|
||||
PendingBlock(const PendingBlock&) = delete;
|
||||
PendingBlock& operator=(const PendingBlock&) = delete;
|
||||
|
||||
// A scope in which we will tentatively add nodes to a pending block. If we
|
||||
// leave the scope without inserting or merging the block, nodes added after
|
||||
// this point will be removed again.
|
||||
class DiscardUnusedNodesScope {
|
||||
public:
|
||||
DiscardUnusedNodesScope(PendingBlock& block)
|
||||
: block_(block), size_(block.nodes_.size()) {}
|
||||
~DiscardUnusedNodesScope() {
|
||||
if (block_.nodes_.size() > size_) {
|
||||
block_.nodes_.truncate(size_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PendingBlock& block_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
auto AddNode(SemIR::Node node) -> SemIR::NodeId {
|
||||
auto node_id = context_.semantics_ir().AddNodeInNoBlock(node);
|
||||
nodes_.push_back(node_id);
|
||||
return node_id;
|
||||
}
|
||||
|
||||
// Insert the pending block of code at the current position.
|
||||
auto InsertHere() -> void {
|
||||
for (auto id : nodes_) {
|
||||
context_.node_block_stack().AddNodeId(id);
|
||||
}
|
||||
nodes_.clear();
|
||||
}
|
||||
|
||||
// Replace the node at target_id with the nodes in this block. The new value
|
||||
// for target_id should be value_id.
|
||||
auto MergeReplacing(SemIR::NodeId target_id, SemIR::NodeId value_id) -> void {
|
||||
auto value = context_.semantics_ir().GetNode(value_id);
|
||||
|
||||
// There are three cases here:
|
||||
|
||||
if (nodes_.empty()) {
|
||||
// 1) The block is empty. Replace `target_id` with an empty splice
|
||||
// pointing at `value_id`.
|
||||
context_.semantics_ir().ReplaceNode(
|
||||
target_id,
|
||||
SemIR::Node::SpliceBlock::Make(value.parse_node(), value.type_id(),
|
||||
SemIR::NodeBlockId::Empty, value_id));
|
||||
} else if (nodes_.size() == 1 && nodes_[0] == value_id) {
|
||||
// 2) The block is {value_id}. Replace `target_id` with the node referred
|
||||
// to by `value_id`. This is intended to be the common case.
|
||||
context_.semantics_ir().ReplaceNode(target_id, value);
|
||||
} else {
|
||||
// 3) Anything else: splice it into the IR, replacing `target_id`.
|
||||
context_.semantics_ir().ReplaceNode(
|
||||
target_id,
|
||||
SemIR::Node::SpliceBlock::Make(
|
||||
value.parse_node(), value.type_id(),
|
||||
context_.semantics_ir().AddNodeBlock(nodes_), value_id));
|
||||
}
|
||||
|
||||
// Prepare to stash more pending instructions.
|
||||
nodes_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
Context& context_;
|
||||
llvm::SmallVector<SemIR::NodeId> nodes_;
|
||||
};
|
||||
|
||||
auto Context::Initialize(Parse::Node parse_node, SemIR::NodeId target_id,
|
||||
SemIR::NodeId value_id) -> SemIR::NodeId {
|
||||
PendingBlock target_block(*this);
|
||||
return InitializeImpl(parse_node, target_id, target_block, value_id);
|
||||
}
|
||||
|
||||
auto Context::InitializeImpl(Parse::Node parse_node, SemIR::NodeId target_id,
|
||||
PendingBlock& target_block, SemIR::NodeId value_id)
|
||||
-> SemIR::NodeId {
|
||||
// Implicitly convert the value to the type of the target.
|
||||
auto type_id = semantics_ir().GetNode(target_id).type_id();
|
||||
auto expr_id = ImplicitAs(parse_node, value_id, type_id);
|
||||
SemIR::Node expr = semantics_ir().GetNode(expr_id);
|
||||
|
||||
// Perform initialization now that we have an expression of the right type.
|
||||
switch (SemIR::GetExpressionCategory(semantics_ir(), expr_id)) {
|
||||
case SemIR::ExpressionCategory::NotExpression:
|
||||
CARBON_FATAL() << "Converting non-expression node " << expr
|
||||
<< " to initializing expression";
|
||||
|
||||
case SemIR::ExpressionCategory::DurableReference:
|
||||
case SemIR::ExpressionCategory::EphemeralReference:
|
||||
// The design uses a custom "copy initialization" process here. We model
|
||||
// that as value binding followed by direct initialization.
|
||||
//
|
||||
// TODO: Determine whether this is observably different from the design,
|
||||
// and change either the toolchain or the design so they match.
|
||||
return AddNode(SemIR::Node::BindValue::Make(expr.parse_node(),
|
||||
expr.type_id(), expr_id));
|
||||
|
||||
case SemIR::ExpressionCategory::Value:
|
||||
// TODO: For class types, use an interface to determine how to perform
|
||||
// this operation.
|
||||
return expr_id;
|
||||
|
||||
case SemIR::ExpressionCategory::Initializing:
|
||||
MarkInitializerFor(expr_id, target_id, target_block);
|
||||
return expr_id;
|
||||
|
||||
case SemIR::ExpressionCategory::Mixed:
|
||||
expr = semantics_ir().GetNode(expr_id);
|
||||
|
||||
// TODO: Make non-recursive.
|
||||
// TODO: This should be done as part of the `ImplicitAs` processing so
|
||||
// that we can still initialize directly from one tuple element if
|
||||
// another one needs to be converted.
|
||||
switch (expr.kind()) {
|
||||
case SemIR::NodeKind::TupleLiteral:
|
||||
case SemIR::NodeKind::StructLiteral: {
|
||||
bool is_tuple = expr.kind() == SemIR::NodeKind::TupleLiteral;
|
||||
auto elements_id =
|
||||
is_tuple ? expr.GetAsTupleLiteral() : expr.GetAsStructLiteral();
|
||||
auto elements = semantics_ir().GetNodeBlock(elements_id);
|
||||
CopyOnWriteBlock new_block(semantics_ir(), elements_id);
|
||||
bool is_in_place =
|
||||
SemIR::GetInitializingRepresentation(semantics_ir(), type_id)
|
||||
.kind == SemIR::InitializingRepresentation::InPlace;
|
||||
for (auto [i, elem_id] : llvm::enumerate(elements)) {
|
||||
// TODO: We know the type already matches because we already invoked
|
||||
// `ImplicitAsRequired`, but this will need to change once we stop
|
||||
// doing that.
|
||||
auto inner_target_type = semantics_ir().GetNode(elem_id).type_id();
|
||||
PendingBlock::DiscardUnusedNodesScope scope(target_block);
|
||||
auto inner_target_id = target_block.AddNode(
|
||||
is_tuple ? SemIR::Node::TupleAccess::Make(
|
||||
parse_node, inner_target_type, target_id,
|
||||
SemIR::MemberIndex(i))
|
||||
: SemIR::Node::StructAccess::Make(
|
||||
parse_node, inner_target_type, target_id,
|
||||
SemIR::MemberIndex(i)));
|
||||
auto new_id =
|
||||
is_in_place ? InitializeAndFinalize(parse_node, inner_target_id,
|
||||
target_block, elem_id)
|
||||
: InitializeImpl(parse_node, inner_target_id,
|
||||
target_block, elem_id);
|
||||
new_block.Set(i, new_id);
|
||||
}
|
||||
return AddNode(
|
||||
is_tuple ? SemIR::Node::TupleInit::Make(parse_node, type_id,
|
||||
expr_id, new_block.id())
|
||||
: SemIR::Node::StructInit::Make(
|
||||
parse_node, type_id, expr_id, new_block.id()));
|
||||
}
|
||||
|
||||
default:
|
||||
CARBON_FATAL() << "Unexpected kind for mixed-category expression "
|
||||
<< expr.kind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::InitializeAndFinalize(Parse::Node parse_node,
|
||||
SemIR::NodeId target_id,
|
||||
PendingBlock& target_block,
|
||||
SemIR::NodeId value_id) -> SemIR::NodeId {
|
||||
auto init_id = InitializeImpl(parse_node, target_id, target_block, value_id);
|
||||
if (init_id == SemIR::NodeId::BuiltinError) {
|
||||
return init_id;
|
||||
}
|
||||
auto target_type_id = semantics_ir().GetNode(target_id).type_id();
|
||||
if (auto init_rep =
|
||||
SemIR::GetInitializingRepresentation(semantics_ir(), target_type_id);
|
||||
init_rep.kind == SemIR::InitializingRepresentation::ByCopy) {
|
||||
target_block.InsertHere();
|
||||
init_id = AddNode(SemIR::Node::InitializeFrom::Make(
|
||||
parse_node, target_type_id, init_id, target_id));
|
||||
}
|
||||
return init_id;
|
||||
}
|
||||
|
||||
auto Context::ConvertToValueExpression(SemIR::NodeId expr_id) -> SemIR::NodeId {
|
||||
if (expr_id == SemIR::NodeId::BuiltinError) {
|
||||
return expr_id;
|
||||
}
|
||||
|
||||
switch (SemIR::GetExpressionCategory(semantics_ir(), expr_id)) {
|
||||
case SemIR::ExpressionCategory::NotExpression: {
|
||||
// TODO: We currently encounter this for use of namespaces and functions.
|
||||
// We should provide a better diagnostic for inappropriate use of
|
||||
// namespace names, and allow use of functions as values.
|
||||
CARBON_DIAGNOSTIC(UseOfNonExpressionAsValue, Error,
|
||||
"Expression cannot be used as a value.");
|
||||
emitter().Emit(semantics_ir().GetNode(expr_id).parse_node(),
|
||||
UseOfNonExpressionAsValue);
|
||||
return SemIR::NodeId::BuiltinError;
|
||||
}
|
||||
|
||||
case SemIR::ExpressionCategory::Initializing:
|
||||
// Commit to using a temporary for this initializing expression.
|
||||
// TODO: Don't create a temporary if the initializing representation is
|
||||
// already a value representation.
|
||||
expr_id = FinalizeTemporary(expr_id, /*discarded=*/false);
|
||||
[[fallthrough]];
|
||||
|
||||
case SemIR::ExpressionCategory::DurableReference:
|
||||
case SemIR::ExpressionCategory::EphemeralReference: {
|
||||
// TODO: Support types with custom value representations.
|
||||
SemIR::Node expr = semantics_ir().GetNode(expr_id);
|
||||
return AddNode(SemIR::Node::BindValue::Make(expr.parse_node(),
|
||||
expr.type_id(), expr_id));
|
||||
}
|
||||
|
||||
case SemIR::ExpressionCategory::Value:
|
||||
return expr_id;
|
||||
|
||||
case SemIR::ExpressionCategory::Mixed: {
|
||||
SemIR::Node expr = semantics_ir().GetNode(expr_id);
|
||||
|
||||
// TODO: Make non-recursive.
|
||||
switch (expr.kind()) {
|
||||
case SemIR::NodeKind::TupleLiteral:
|
||||
case SemIR::NodeKind::StructLiteral: {
|
||||
bool is_tuple = expr.kind() == SemIR::NodeKind::TupleLiteral;
|
||||
auto elements_id =
|
||||
is_tuple ? expr.GetAsTupleLiteral() : expr.GetAsStructLiteral();
|
||||
auto elements = semantics_ir().GetNodeBlock(elements_id);
|
||||
CopyOnWriteBlock new_block(semantics_ir(), elements_id);
|
||||
for (auto [i, elem_id] : llvm::enumerate(elements)) {
|
||||
new_block.Set(i, ConvertToValueExpression(elem_id));
|
||||
}
|
||||
return AddNode(is_tuple ? SemIR::Node::TupleValue::Make(
|
||||
expr.parse_node(), expr.type_id(),
|
||||
expr_id, new_block.id())
|
||||
: SemIR::Node::StructValue::Make(
|
||||
expr.parse_node(), expr.type_id(),
|
||||
expr_id, new_block.id()));
|
||||
}
|
||||
|
||||
default:
|
||||
CARBON_FATAL() << "Unexpected kind for mixed-category expression "
|
||||
<< expr.kind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the given expression to a value or reference expression of the same
|
||||
// type.
|
||||
auto Context::ConvertToValueOrReferenceExpression(SemIR::NodeId expr_id,
|
||||
bool discarded)
|
||||
-> SemIR::NodeId {
|
||||
switch (GetExpressionCategory(semantics_ir(), expr_id)) {
|
||||
case SemIR::ExpressionCategory::Value:
|
||||
case SemIR::ExpressionCategory::DurableReference:
|
||||
case SemIR::ExpressionCategory::EphemeralReference:
|
||||
return expr_id;
|
||||
|
||||
case SemIR::ExpressionCategory::Initializing:
|
||||
return FinalizeTemporary(expr_id, discarded);
|
||||
|
||||
case SemIR::ExpressionCategory::Mixed:
|
||||
case SemIR::ExpressionCategory::NotExpression:
|
||||
return ConvertToValueExpression(expr_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Given an initializing expression, find its return slot. Returns `Invalid` if
|
||||
// there is no return slot, because the initialization is not performed in
|
||||
// place.
|
||||
static auto FindReturnSlotForInitializer(SemIR::File& semantics_ir,
|
||||
SemIR::NodeId init_id)
|
||||
-> SemIR::NodeId {
|
||||
SemIR::Node init = semantics_ir.GetNode(init_id);
|
||||
switch (init.kind()) {
|
||||
default:
|
||||
CARBON_FATAL() << "Initialization from unexpected node " << init;
|
||||
|
||||
case SemIR::NodeKind::StructInit:
|
||||
case SemIR::NodeKind::TupleInit:
|
||||
// TODO: Track a return slot for these initializers.
|
||||
CARBON_FATAL() << init
|
||||
<< " should be created with its return slot already "
|
||||
"filled in properly";
|
||||
|
||||
case SemIR::NodeKind::InitializeFrom: {
|
||||
auto [src_id, dest_id] = init.GetAsInitializeFrom();
|
||||
return dest_id;
|
||||
}
|
||||
|
||||
case SemIR::NodeKind::Call: {
|
||||
auto [refs_id, callee_id] = init.GetAsCall();
|
||||
if (!semantics_ir.GetFunction(callee_id).return_slot_id.is_valid()) {
|
||||
return SemIR::NodeId::Invalid;
|
||||
}
|
||||
return semantics_ir.GetNodeBlock(refs_id).back();
|
||||
}
|
||||
|
||||
case SemIR::NodeKind::ArrayInit: {
|
||||
auto [src_id, refs_id] = init.GetAsArrayInit();
|
||||
return semantics_ir.GetNodeBlock(refs_id).back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id,
|
||||
PendingBlock& target_block) -> void {
|
||||
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
|
||||
if (return_slot_id.is_valid()) {
|
||||
// Replace the temporary in the return slot with a reference to our target.
|
||||
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
|
||||
SemIR::NodeKind::TemporaryStorage)
|
||||
<< "Return slot for initializer does not contain a temporary; "
|
||||
<< "initialized multiple times? Have "
|
||||
<< semantics_ir().GetNode(return_slot_id);
|
||||
target_block.MergeReplacing(return_slot_id, target_id);
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::FinalizeTemporary(SemIR::NodeId init_id, bool discarded)
|
||||
-> SemIR::NodeId {
|
||||
auto return_slot_id = FindReturnSlotForInitializer(semantics_ir(), init_id);
|
||||
if (return_slot_id.is_valid()) {
|
||||
// The return slot should already have a materialized temporary in it.
|
||||
CARBON_CHECK(semantics_ir().GetNode(return_slot_id).kind() ==
|
||||
SemIR::NodeKind::TemporaryStorage)
|
||||
<< "Return slot for initializer does not contain a temporary; "
|
||||
<< "initialized multiple times? Have "
|
||||
<< semantics_ir().GetNode(return_slot_id);
|
||||
auto init = semantics_ir().GetNode(init_id);
|
||||
return AddNode(SemIR::Node::Temporary::Make(
|
||||
init.parse_node(), init.type_id(), return_slot_id, init_id));
|
||||
}
|
||||
|
||||
if (discarded) {
|
||||
// Don't invent a temporary that we're going to discard.
|
||||
return SemIR::NodeId::Invalid;
|
||||
}
|
||||
|
||||
// The initializer has no return slot, but we want to produce a temporary
|
||||
// object. Materialize one now.
|
||||
// TODO: Consider using an invalid ID to mean that we immediately
|
||||
// materialize and initialize a temporary, rather than two separate
|
||||
// nodes.
|
||||
auto init = semantics_ir().GetNode(init_id);
|
||||
auto temporary_id = AddNode(
|
||||
SemIR::Node::TemporaryStorage::Make(init.parse_node(), init.type_id()));
|
||||
return AddNode(SemIR::Node::Temporary::Make(init.parse_node(), init.type_id(),
|
||||
temporary_id, init_id));
|
||||
}
|
||||
|
||||
auto Context::HandleDiscardedExpression(SemIR::NodeId expr_id) -> void {
|
||||
// If we discard an initializing expression, convert it to a value or
|
||||
// reference so that it has something to initialize.
|
||||
ConvertToValueOrReferenceExpression(expr_id, /*discarded=*/true);
|
||||
|
||||
// TODO: This will eventually need to do some "do not discard" analysis.
|
||||
}
|
||||
|
||||
auto Context::ImplicitAsForArgs(Parse::Node call_parse_node,
|
||||
SemIR::NodeBlockId arg_refs_id,
|
||||
Parse::Node param_parse_node,
|
||||
SemIR::NodeBlockId param_refs_id,
|
||||
bool has_return_slot) -> bool {
|
||||
// If both arguments and parameters are empty, return quickly. Otherwise,
|
||||
// we'll fetch both so that errors are consistent.
|
||||
if (arg_refs_id == SemIR::NodeBlockId::Empty &&
|
||||
param_refs_id == SemIR::NodeBlockId::Empty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto arg_refs = semantics_ir_->GetNodeBlock(arg_refs_id);
|
||||
auto param_refs = semantics_ir_->GetNodeBlock(param_refs_id);
|
||||
|
||||
if (has_return_slot) {
|
||||
// There's no entry in the parameter block for the return slot, so ignore
|
||||
// the corresponding entry in the argument block.
|
||||
// TODO: Consider adding the return slot to the parameter list.
|
||||
CARBON_CHECK(!arg_refs.empty()) << "missing return slot";
|
||||
arg_refs = arg_refs.drop_back();
|
||||
}
|
||||
|
||||
// If sizes mismatch, fail early.
|
||||
if (arg_refs.size() != param_refs.size()) {
|
||||
CARBON_DIAGNOSTIC(CallArgCountMismatch, Error,
|
||||
"{0} argument(s) passed to function expecting "
|
||||
"{1} argument(s).",
|
||||
int, int);
|
||||
CARBON_DIAGNOSTIC(InCallToFunction, Note,
|
||||
"Calling function declared here.");
|
||||
emitter_
|
||||
->Build(call_parse_node, CallArgCountMismatch, arg_refs.size(),
|
||||
param_refs.size())
|
||||
.Note(param_parse_node, InCallToFunction)
|
||||
.Emit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (param_refs.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int diag_param_index;
|
||||
DiagnosticAnnotationScope annotate_diagnostics(emitter_, [&](auto& builder) {
|
||||
CARBON_DIAGNOSTIC(InCallToFunctionParam, Note,
|
||||
"Initializing parameter {0} of function declared here.",
|
||||
int);
|
||||
builder.Note(param_parse_node, InCallToFunctionParam, diag_param_index + 1);
|
||||
});
|
||||
|
||||
// Check type conversions per-element.
|
||||
for (auto [i, value_id, param_ref] : llvm::enumerate(arg_refs, param_refs)) {
|
||||
diag_param_index = i;
|
||||
|
||||
auto as_type_id = semantics_ir_->GetNode(param_ref).type_id();
|
||||
// TODO: Convert to the proper expression category. For now, we assume
|
||||
// parameters are all `let` bindings.
|
||||
value_id = ConvertToValueOfType(call_parse_node, value_id, as_type_id);
|
||||
if (value_id == SemIR::NodeId::BuiltinError) {
|
||||
return false;
|
||||
}
|
||||
arg_refs[i] = value_id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Performs a conversion from a tuple to an array type.
|
||||
static auto ConvertTupleToArray(Context& context, SemIR::Node tuple_type,
|
||||
SemIR::Node array_type,
|
||||
SemIR::TypeId array_type_id,
|
||||
SemIR::NodeId value_id) -> SemIR::NodeId {
|
||||
auto [array_bound_id, element_type_id] = array_type.GetAsArrayType();
|
||||
auto tuple_elem_types_id = tuple_type.GetAsTupleType();
|
||||
const auto& tuple_elem_types =
|
||||
context.semantics_ir().GetTypeBlock(tuple_elem_types_id);
|
||||
|
||||
auto value = context.semantics_ir().GetNode(value_id);
|
||||
|
||||
llvm::ArrayRef<SemIR::NodeId> literal_elems;
|
||||
if (value.kind() == SemIR::NodeKind::TupleLiteral) {
|
||||
literal_elems =
|
||||
context.semantics_ir().GetNodeBlock(value.GetAsTupleLiteral());
|
||||
}
|
||||
|
||||
// Check that the tuple is the right size.
|
||||
uint64_t array_bound =
|
||||
context.semantics_ir().GetArrayBoundValue(array_bound_id);
|
||||
if (tuple_elem_types.size() != array_bound) {
|
||||
CARBON_DIAGNOSTIC(
|
||||
ArrayInitFromLiteralArgCountMismatch, Error,
|
||||
"Cannot initialize array of {0} element(s) from {1} initializer(s).",
|
||||
uint64_t, size_t);
|
||||
CARBON_DIAGNOSTIC(ArrayInitFromExpressionArgCountMismatch, Error,
|
||||
"Cannot initialize array of {0} element(s) from tuple "
|
||||
"with {1} element(s).",
|
||||
uint64_t, size_t);
|
||||
context.emitter().Emit(
|
||||
context.semantics_ir().GetNode(value_id).parse_node(),
|
||||
literal_elems.empty() ? ArrayInitFromExpressionArgCountMismatch
|
||||
: ArrayInitFromLiteralArgCountMismatch,
|
||||
array_bound, tuple_elem_types.size());
|
||||
return SemIR::NodeId::BuiltinError;
|
||||
}
|
||||
|
||||
// If we're initializing from a tuple literal, we will use its elements
|
||||
// directly. Otherwise, materialize a temporary if needed and index into the
|
||||
// result.
|
||||
if (literal_elems.empty()) {
|
||||
value_id = context.ConvertToValueOrReferenceExpression(value_id);
|
||||
}
|
||||
|
||||
Context::PendingBlock target_block(context);
|
||||
|
||||
// Arrays are always initialized in-place. Tentatively allocate a temporary
|
||||
// as the destination for the array initialization.
|
||||
auto return_slot_id = target_block.AddNode(
|
||||
SemIR::Node::TemporaryStorage::Make(value.parse_node(), array_type_id));
|
||||
|
||||
// Initialize each element of the array from the corresponding element of the
|
||||
// tuple.
|
||||
llvm::SmallVector<SemIR::NodeId> inits;
|
||||
inits.reserve(array_bound + 1);
|
||||
for (auto [i, src_type_id] : llvm::enumerate(tuple_elem_types)) {
|
||||
Context::PendingBlock::DiscardUnusedNodesScope scope(target_block);
|
||||
// TODO: Add a new node kind for indexing an array at a constant index
|
||||
// so that we don't need an integer literal node here.
|
||||
auto index_id = target_block.AddNode(SemIR::Node::IntegerLiteral::Make(
|
||||
value.parse_node(),
|
||||
context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType),
|
||||
context.semantics_ir().AddIntegerLiteral(llvm::APInt(32, i))));
|
||||
auto target_id = target_block.AddNode(SemIR::Node::ArrayIndex::Make(
|
||||
value.parse_node(), element_type_id, return_slot_id, index_id));
|
||||
// Note, this is computing the source location not the destination, so it
|
||||
// goes into the current code block, not into the target block.
|
||||
// TODO: Ideally we would also discard this node if it's unused.
|
||||
auto src_id = !literal_elems.empty()
|
||||
? literal_elems[i]
|
||||
: context.AddNode(SemIR::Node::TupleAccess::Make(
|
||||
value.parse_node(), src_type_id, value_id,
|
||||
SemIR::MemberIndex(i)));
|
||||
auto init_id = context.InitializeAndFinalize(value.parse_node(), target_id,
|
||||
target_block, src_id);
|
||||
if (init_id == SemIR::NodeId::BuiltinError) {
|
||||
return SemIR::NodeId::BuiltinError;
|
||||
}
|
||||
inits.push_back(init_id);
|
||||
}
|
||||
|
||||
// The last element of the refs block contains the return slot for the array
|
||||
// initialization. Flush the temporary here if we didn't insert it earlier.
|
||||
target_block.InsertHere();
|
||||
inits.push_back(return_slot_id);
|
||||
|
||||
return context.AddNode(
|
||||
SemIR::Node::ArrayInit::Make(value.parse_node(), array_type_id, value_id,
|
||||
context.semantics_ir().AddNodeBlock(inits)));
|
||||
}
|
||||
|
||||
auto Context::ImplicitAs(Parse::Node parse_node, SemIR::NodeId value_id,
|
||||
SemIR::TypeId as_type_id) -> SemIR::NodeId {
|
||||
// Start by making sure both sides are valid. If any part is invalid, the
|
||||
// result is invalid and we shouldn't error.
|
||||
if (value_id == SemIR::NodeId::BuiltinError) {
|
||||
// If the value is invalid, we can't do much, but do "succeed".
|
||||
return value_id;
|
||||
}
|
||||
auto value = semantics_ir_->GetNode(value_id);
|
||||
auto value_type_id = value.type_id();
|
||||
if (value_type_id == SemIR::TypeId::Error ||
|
||||
as_type_id == SemIR::TypeId::Error) {
|
||||
return SemIR::NodeId::BuiltinError;
|
||||
}
|
||||
|
||||
if (value_type_id == as_type_id) {
|
||||
return value_id;
|
||||
}
|
||||
|
||||
auto as_type = semantics_ir_->GetTypeAllowBuiltinTypes(as_type_id);
|
||||
auto as_type_node = semantics_ir_->GetNode(as_type);
|
||||
|
||||
// A tuple (T1, T2, ..., Tn) converts to [T; n] if each Ti converts to T.
|
||||
if (as_type_node.kind() == SemIR::NodeKind::ArrayType) {
|
||||
auto value_type_node = semantics_ir_->GetNode(
|
||||
semantics_ir_->GetTypeAllowBuiltinTypes(value_type_id));
|
||||
if (value_type_node.kind() == SemIR::NodeKind::TupleType) {
|
||||
// The conversion from tuple to array is `final`, so we don't need a
|
||||
// fallback path here.
|
||||
return ConvertTupleToArray(*this, value_type_node, as_type_node,
|
||||
as_type_id, value_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (as_type_id == SemIR::TypeId::TypeType) {
|
||||
// A tuple of types converts to type `type`.
|
||||
// TODO: This should apply even for non-literal tuples.
|
||||
if (value.kind() == SemIR::NodeKind::TupleLiteral) {
|
||||
// The conversion from tuple to `type` is `final`.
|
||||
auto tuple_block_id = value.GetAsTupleLiteral();
|
||||
llvm::SmallVector<SemIR::TypeId> type_ids;
|
||||
// If it is empty tuple type, we don't fetch anything.
|
||||
if (tuple_block_id != SemIR::NodeBlockId::Empty) {
|
||||
const auto& tuple_block = semantics_ir_->GetNodeBlock(tuple_block_id);
|
||||
for (auto tuple_node_id : tuple_block) {
|
||||
// TODO: This call recurses back to this function. Switch to an
|
||||
// iterative approach.
|
||||
type_ids.push_back(
|
||||
ExpressionAsType(value.parse_node(), tuple_node_id));
|
||||
}
|
||||
}
|
||||
auto tuple_type_id =
|
||||
CanonicalizeTupleType(value.parse_node(), std::move(type_ids));
|
||||
return semantics_ir_->GetTypeAllowBuiltinTypes(tuple_type_id);
|
||||
}
|
||||
// When converting `{}` to a type, the result is `{} as type`.
|
||||
// TODO: This conversion should also be performed for a non-literal value of
|
||||
// type `{}`.
|
||||
if (value.kind() == SemIR::NodeKind::StructLiteral &&
|
||||
value.GetAsStructLiteral() == SemIR::NodeBlockId::Empty) {
|
||||
return semantics_ir_->GetType(value_type_id);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle ImplicitAs for compatible structs and tuples.
|
||||
|
||||
CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error,
|
||||
"Cannot implicitly convert from `{0}` to `{1}`.",
|
||||
std::string, std::string);
|
||||
emitter_
|
||||
->Build(parse_node, ImplicitAsConversionFailure,
|
||||
semantics_ir_->StringifyType(
|
||||
semantics_ir_->GetNode(value_id).type_id()),
|
||||
semantics_ir_->StringifyType(as_type_id))
|
||||
.Emit();
|
||||
return SemIR::NodeId::BuiltinError;
|
||||
}
|
||||
|
||||
auto Context::ParamOrArgStart() -> void { params_or_args_stack_.Push(); }
|
||||
|
||||
auto Context::ParamOrArgComma() -> void {
|
||||
|
||||
Reference in New Issue
Block a user