diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 6978598db04b..3515cf5a2cf5 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -33,6 +33,7 @@ cc_library( srcs = [ "check.cpp", "context.cpp", + "convert.cpp", "declaration_name_stack.cpp", "node_block_stack.cpp", ] + @@ -43,8 +44,10 @@ cc_library( hdrs = [ "check.h", "context.h", + "convert.h", "declaration_name_stack.h", "node_block_stack.h", + "pending_block.h", ], deps = [ ":node_stack", diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 21c05d114edb..08b5d057e67c 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -4,11 +4,11 @@ #include "toolchain/check/context.h" +#include #include #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 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 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 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 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 { diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 97f4f70b10fb..acff030b079e 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -21,9 +21,6 @@ namespace Carbon::Check { // Context and shared functionality for semantics handlers. class Context { public: - // A block of code that has not yet been inserted into SemIR. - class PendingBlock; - // Stores references for work. explicit Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter, @@ -108,54 +105,6 @@ class Context { // Returns whether the current position in the current block is reachable. auto is_current_position_reachable() -> bool; - // Convert the given expression to a value expression of the same type. - auto ConvertToValueExpression(SemIR::NodeId expr_id) -> SemIR::NodeId; - - // Convert the given expression to a value or reference expression of the same - // type. - auto ConvertToValueOrReferenceExpression(SemIR::NodeId expr_id, - bool discarded = false) - -> SemIR::NodeId; - - // Performs initialization of `target_id` from `value_id`. Returns the - // possibly-converted initialization expression, which should be assigned to - // the target using a suitable node for the kind of initialization. - auto Initialize(Parse::Node parse_node, SemIR::NodeId target_id, - SemIR::NodeId value_id) -> SemIR::NodeId; - - // Performs and finalizes initialization of `target_id` from `value_id`. This - // is the same as `Initialize`, except that it also performs the final store - // from the initializer to the target if the initialization is not in-place. - // That final store is often undesirable as it is performed by the consumer - // of the initializer, such as an `Assign` or `ReturnExpression` node. The - // resulting node describes the initialization operation that was performed. - auto InitializeAndFinalize(Parse::Node parse_node, SemIR::NodeId target_id, - PendingBlock& target_block, SemIR::NodeId value_id) - -> SemIR::NodeId; - - // Converts `value_id` to a value expression of type `type_id`. - auto ConvertToValueOfType(Parse::Node parse_node, SemIR::NodeId value_id, - SemIR::TypeId type_id) -> SemIR::NodeId { - return ConvertToValueExpression(ImplicitAs(parse_node, value_id, type_id)); - } - - // Converts `value_id` to a value expression of type `bool`. - auto ConvertToBoolValue(Parse::Node parse_node, SemIR::NodeId value_id) - -> SemIR::NodeId { - return ConvertToValueOfType( - parse_node, value_id, CanonicalizeType(SemIR::NodeId::BuiltinBoolType)); - } - - // Handles an expression whose result is discarded. - auto HandleDiscardedExpression(SemIR::NodeId id) -> void; - - // Runs ImplicitAs for a set of arguments and parameters in a function call. - auto 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; - // Canonicalizes a type which is tracked as a single node. // TODO: This should eventually return a type ID. auto CanonicalizeType(SemIR::NodeId node_id) -> SemIR::TypeId; @@ -180,13 +129,6 @@ class Context { auto GetPointerType(Parse::Node parse_node, SemIR::TypeId pointee_type_id) -> SemIR::TypeId; - // Converts an expression for use as a type. - auto ExpressionAsType(Parse::Node parse_node, SemIR::NodeId value_id) - -> SemIR::TypeId { - return CanonicalizeType( - ConvertToValueOfType(parse_node, value_id, SemIR::TypeId::TypeType)); - } - // Removes any top-level `const` qualifiers from a type. auto GetUnqualifiedType(SemIR::TypeId type_id) -> SemIR::TypeId; @@ -268,31 +210,6 @@ class Context { // TODO: This likely needs to track things which need to be destructed. }; - // Implementation of `Initialize`. Takes a `target_block` which contains - // pending instructions that are needed to form the value of `target_id`. - // These can be discarded if no initialization is needed. - auto InitializeImpl(Parse::Node parse_node, SemIR::NodeId target_id, - PendingBlock& target_block, SemIR::NodeId value_id) - -> SemIR::NodeId; - - // Commits to using a temporary to store the result of the initializing - // expression described by `init_id`, and returns the location of the - // temporary. If `discarded` is `true`, the result is discarded, and no - // temporary will be created if possible; if no temporary is created, the - // return value will be `SemIR::NodeId::Invalid`. - auto FinalizeTemporary(SemIR::NodeId init_id, bool discarded) - -> SemIR::NodeId; - - // Marks the initializer `init_id` as initializing `target_id`. - auto MarkInitializerFor(SemIR::NodeId init_id, SemIR::NodeId target_id, - PendingBlock& target_block) -> void; - - // Runs ImplicitAs behavior to convert `value` to `as_type`, returning the - // converted result. Prints a diagnostic and returns an Error if the - // conversion cannot be performed. - auto ImplicitAs(Parse::Node parse_node, SemIR::NodeId value_id, - SemIR::TypeId as_type_id) -> SemIR::NodeId; - // Forms a canonical type ID for a type. This function is given two // callbacks: // diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp new file mode 100644 index 000000000000..c22fd0af1517 --- /dev/null +++ b/toolchain/check/convert.cpp @@ -0,0 +1,838 @@ +// 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/convert.h" + +#include +#include + +#include "common/check.h" +#include "llvm/ADT/STLExtras.h" +#include "toolchain/check/context.h" +#include "toolchain/diagnostics/diagnostic_kind.h" +#include "toolchain/parse/node_kind.h" +#include "toolchain/sem_ir/file.h" +#include "toolchain/sem_ir/node.h" +#include "toolchain/sem_ir/node_kind.h" + +namespace Carbon::Check { + +// 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(); + } + } +} + +// Marks the initializer `init_id` as initializing `target_id`. +static auto MarkInitializerFor(SemIR::File& semantics_ir, 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); + } +} + +// Commits to using a temporary to store the result of the initializing +// expression described by `init_id`, and returns the location of the +// temporary. If `discarded` is `true`, the result is discarded, and no +// temporary will be created if possible; if no temporary is created, the +// return value will be `SemIR::NodeId::Invalid`. +static auto FinalizeTemporary(Context& context, SemIR::NodeId init_id, + bool discarded) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + 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 context.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 = context.AddNode( + SemIR::Node::TemporaryStorage::Make(init.parse_node(), init.type_id())); + return context.AddNode(SemIR::Node::Temporary::Make( + init.parse_node(), init.type_id(), temporary_id, init_id)); +} + +// Materialize a temporary to hold the result of the given expression if it is +// an initializing expression. +static auto MaterializeIfInitializing(Context& context, SemIR::NodeId expr_id) + -> SemIR::NodeId { + if (GetExpressionCategory(context.semantics_ir(), expr_id) == + SemIR::ExpressionCategory::Initializing) { + return FinalizeTemporary(context, expr_id, /*discarded=*/false); + } + return expr_id; +} + +// Creates and adds a node to perform element access into an aggregate. +template +static auto MakeElemAccessNode(Context& context, Parse::Node parse_node, + SemIR::NodeId aggregate_id, + SemIR::TypeId elem_type_id, NodeBlockT& block, + std::size_t i) { + if constexpr (std::is_same_v) { + // 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, and remove this + // special case. + auto index_id = block.AddNode(SemIR::Node::IntegerLiteral::Make( + parse_node, context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType), + context.semantics_ir().AddIntegerLiteral(llvm::APInt(32, i)))); + return block.AddNode( + AccessNodeT::Make(parse_node, elem_type_id, aggregate_id, index_id)); + } else { + return block.AddNode(AccessNodeT::Make( + parse_node, elem_type_id, aggregate_id, SemIR::MemberIndex(i))); + } +} + +// Converts an element of one aggregate so that it can be used as an element of +// another aggregate. +// +// For the source: `src_id` is the source aggregate, `src_elem_type` is the +// element type, `i` is the index, and `SourceAccessNodeT` is the kind of node +// used to access the source element. +// +// For the target: `kind` is the kind of conversion or initialization, +// `target_elem_type` is the element type. For initialization, `target_id` is +// the destination, `target_block` is a pending block for target location +// calculations that will be spliced as the return slot of the initializer if +// necessary, `i` is the index, and `TargetAccessNodeT` is the kind of node +// used to access the destination element. +template +static auto ConvertAggregateElement( + Context& context, Parse::Node parse_node, SemIR::NodeId src_id, + SemIR::TypeId src_elem_type, + llvm::ArrayRef src_literal_elems, + ConversionTarget::Kind kind, SemIR::NodeId target_id, + SemIR::TypeId target_elem_type, PendingBlock* target_block, std::size_t i) { + // Compute the location of the source element. This goes into the current code + // block, not into the target block. + // TODO: Ideally we would discard this node if it's unused. + auto src_elem_id = + !src_literal_elems.empty() + ? src_literal_elems[i] + : MakeElemAccessNode(context, parse_node, src_id, + src_elem_type, context, i); + + // If we're performing a conversion rather than an initialization, we won't + // have or need a target. + ConversionTarget target = {.kind = kind, .type_id = target_elem_type}; + if (!target.is_initializer()) { + return Convert(context, parse_node, src_elem_id, target); + } + + // Compute the location of the target element and initialize it. + PendingBlock::DiscardUnusedNodesScope scope(target_block); + target.init_block = target_block; + target.init_id = MakeElemAccessNode( + context, parse_node, target_id, target_elem_type, *target_block, i); + return Convert(context, parse_node, src_elem_id, target); +} + +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: + // Constructs the block. If `source_id` is valid, it is used as the initial + // value of the block. Otherwise, uninitialized storage for `size` elements + // is allocated. + CopyOnWriteBlock(SemIR::File& file, SemIR::NodeBlockId source_id, size_t size) + : file_(file), source_id_(source_id) { + if (!source_id_.is_valid()) { + id_ = file_.AddUninitializedNodeBlock(size); + } + } + + auto id() -> SemIR::NodeBlockId const { return id_; } + + auto Set(int i, SemIR::NodeId value) -> void { + if (source_id_.is_valid() && 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 + +// Performs a conversion from a tuple to an array type. Does not perform a +// final conversion to the requested expression category. +static auto ConvertTupleToArray(Context& context, SemIR::Node tuple_type, + SemIR::Node array_type, SemIR::NodeId value_id, + ConversionTarget target) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + auto [array_bound_id, element_type_id] = array_type.GetAsArrayType(); + auto tuple_elem_types_id = tuple_type.GetAsTupleType(); + const auto& tuple_elem_types = semantics_ir.GetTypeBlock(tuple_elem_types_id); + + auto value = semantics_ir.GetNode(value_id); + + // 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. + llvm::ArrayRef literal_elems; + if (value.kind() == SemIR::NodeKind::TupleLiteral) { + literal_elems = semantics_ir.GetNodeBlock(value.GetAsTupleLiteral()); + } else { + value_id = MaterializeIfInitializing(context, value_id); + } + + // Check that the tuple is the right size. + uint64_t array_bound = 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(value.parse_node(), + literal_elems.empty() + ? ArrayInitFromExpressionArgCountMismatch + : ArrayInitFromLiteralArgCountMismatch, + array_bound, tuple_elem_types.size()); + return SemIR::NodeId::BuiltinError; + } + + PendingBlock target_block_storage(context); + PendingBlock* target_block = + target.init_block ? target.init_block : &target_block_storage; + + // Arrays are always initialized in-place. Allocate a temporary as the + // destination for the array initialization if we weren't given one. + SemIR::NodeId return_slot_id = target.init_id; + if (!target.init_id.is_valid()) { + return_slot_id = target_block->AddNode(SemIR::Node::TemporaryStorage::Make( + value.parse_node(), target.type_id)); + } + + // Initialize each element of the array from the corresponding element of the + // tuple. + // TODO: Annotate diagnostics coming from here with the array element index, + // if initializing from a tuple literal. + llvm::SmallVector inits; + inits.reserve(array_bound + 1); + for (auto [i, src_type_id] : llvm::enumerate(tuple_elem_types)) { + // TODO: This call recurses back into conversion. Switch to an iterative + // approach. + auto init_id = ConvertAggregateElement( + context, value.parse_node(), value_id, src_type_id, literal_elems, + ConversionTarget::FullInitializer, return_slot_id, element_type_id, + target_block, i); + 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(), target.type_id, value_id, + semantics_ir.AddNodeBlock(inits))); +} + +// Performs a conversion from a tuple to a tuple type. Does not perform a +// final conversion to the requested expression category. +static auto ConvertTupleToTuple(Context& context, SemIR::Node src_type, + SemIR::Node dest_type, SemIR::NodeId value_id, + ConversionTarget target) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + auto src_elem_types = semantics_ir.GetTypeBlock(src_type.GetAsTupleType()); + auto dest_elem_types = semantics_ir.GetTypeBlock(dest_type.GetAsTupleType()); + + auto value = semantics_ir.GetNode(value_id); + + // 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. + llvm::ArrayRef literal_elems; + if (value.kind() == SemIR::NodeKind::TupleLiteral) { + literal_elems = semantics_ir.GetNodeBlock(value.GetAsTupleLiteral()); + } else { + value_id = MaterializeIfInitializing(context, value_id); + } + + // Check that the tuples are the same size. + if (src_elem_types.size() != dest_elem_types.size()) { + CARBON_DIAGNOSTIC(TupleInitElementCountMismatch, Error, + "Cannot initialize tuple of {0} element(s) from tuple " + "with {1} element(s).", + size_t, size_t); + context.emitter().Emit(value.parse_node(), TupleInitElementCountMismatch, + dest_elem_types.size(), src_elem_types.size()); + return SemIR::NodeId::BuiltinError; + } + + // If we're forming an initializer, then we want an initializer for each + // element. Otherwise, we want a value representation for each element. + // Perform a final destination store if we're performing an in-place + // initialization. + bool is_init = target.is_initializer(); + ConversionTarget::Kind inner_kind = + !is_init ? ConversionTarget::Value + : SemIR::GetInitializingRepresentation(semantics_ir, target.type_id) + .kind == SemIR::InitializingRepresentation::InPlace + ? ConversionTarget::FullInitializer + : ConversionTarget::Initializer; + + // Initialize each element of the destination from the corresponding element + // of the source. + // TODO: Annotate diagnostics coming from here with the element index. + CopyOnWriteBlock new_block(semantics_ir, + value.kind() == SemIR::NodeKind::TupleLiteral + ? value.GetAsTupleLiteral() + : SemIR::NodeBlockId::Invalid, + src_elem_types.size()); + for (auto [i, src_type_id, dest_type_id] : + llvm::enumerate(src_elem_types, dest_elem_types)) { + // TODO: This call recurses back into conversion. Switch to an iterative + // approach. + auto init_id = ConvertAggregateElement( + context, value.parse_node(), value_id, src_type_id, literal_elems, + inner_kind, target.init_id, dest_type_id, target.init_block, i); + if (init_id == SemIR::NodeId::BuiltinError) { + return SemIR::NodeId::BuiltinError; + } + new_block.Set(i, init_id); + } + + return context.AddNode( + is_init + ? SemIR::Node::TupleInit::Make(value.parse_node(), target.type_id, + value_id, new_block.id()) + : SemIR::Node::TupleValue::Make(value.parse_node(), target.type_id, + value_id, new_block.id())); +} + +// Performs a conversion from a struct to a struct type. Does not perform a +// final conversion to the requested expression category. +static auto ConvertStructToStruct(Context& context, SemIR::Node src_type, + SemIR::Node dest_type, SemIR::NodeId value_id, + ConversionTarget target) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + auto src_elem_fields = semantics_ir.GetNodeBlock(src_type.GetAsStructType()); + auto dest_elem_fields = + semantics_ir.GetNodeBlock(dest_type.GetAsStructType()); + + auto value = semantics_ir.GetNode(value_id); + + // If we're initializing from a struct literal, we will use its elements + // directly. Otherwise, materialize a temporary if needed and index into the + // result. + llvm::ArrayRef literal_elems; + if (value.kind() == SemIR::NodeKind::StructLiteral) { + literal_elems = semantics_ir.GetNodeBlock(value.GetAsStructLiteral()); + } else { + value_id = MaterializeIfInitializing(context, value_id); + } + + // Check that the structs are the same size. + // TODO: Check the field names are the same up to permutation, compute the + // permutation, and use it below. + if (src_elem_fields.size() != dest_elem_fields.size()) { + CARBON_DIAGNOSTIC(StructInitElementCountMismatch, Error, + "Cannot initialize struct of {0} element(s) from struct " + "with {1} element(s).", + size_t, size_t); + context.emitter().Emit(value.parse_node(), StructInitElementCountMismatch, + dest_elem_fields.size(), src_elem_fields.size()); + return SemIR::NodeId::BuiltinError; + } + + // If we're forming an initializer, then we want an initializer for each + // element. Otherwise, we want a value representation for each element. + // Perform a final destination store if we're performing an in-place + // initialization. + bool is_init = target.is_initializer(); + ConversionTarget::Kind inner_kind = + !is_init ? ConversionTarget::Value + : SemIR::GetInitializingRepresentation(semantics_ir, target.type_id) + .kind == SemIR::InitializingRepresentation::InPlace + ? ConversionTarget::FullInitializer + : ConversionTarget::Initializer; + + // Initialize each element of the destination from the corresponding element + // of the source. + // TODO: Annotate diagnostics coming from here with the element index. + CopyOnWriteBlock new_block(semantics_ir, + value.kind() == SemIR::NodeKind::StructLiteral + ? value.GetAsStructLiteral() + : SemIR::NodeBlockId::Invalid, + src_elem_fields.size()); + for (auto [i, src_field_id, dest_field_id] : + llvm::enumerate(src_elem_fields, dest_elem_fields)) { + auto [src_name_id, src_type_id] = + semantics_ir.GetNode(src_field_id).GetAsStructTypeField(); + auto [dest_name_id, dest_type_id] = + semantics_ir.GetNode(dest_field_id).GetAsStructTypeField(); + if (src_name_id != dest_name_id) { + CARBON_DIAGNOSTIC( + StructInitFieldNameMismatch, Error, + "Mismatched names for field {0} in struct initialization: " + "source has field name `{1}`, destination has field name `{2}`.", + size_t, llvm::StringRef, llvm::StringRef); + context.emitter().Emit(value.parse_node(), StructInitFieldNameMismatch, + i + 1, semantics_ir.GetString(src_name_id), + semantics_ir.GetString(dest_name_id)); + return SemIR::NodeId::BuiltinError; + } + + // TODO: This call recurses back into conversion. Switch to an iterative + // approach. + auto init_id = ConvertAggregateElement( + context, value.parse_node(), value_id, src_type_id, literal_elems, + inner_kind, target.init_id, dest_type_id, target.init_block, i); + if (init_id == SemIR::NodeId::BuiltinError) { + return SemIR::NodeId::BuiltinError; + } + new_block.Set(i, init_id); + } + + return context.AddNode( + is_init + ? SemIR::Node::StructInit::Make(value.parse_node(), target.type_id, + value_id, new_block.id()) + : SemIR::Node::StructValue::Make(value.parse_node(), target.type_id, + value_id, new_block.id())); +} + +// Returns whether `category` is a valid expression category to produce as a +// result of a conversion with kind `target_kind`, or at most needs a temporary +// to be materialized. +static bool IsValidExpressionCategoryForConversionTarget( + SemIR::ExpressionCategory category, ConversionTarget::Kind target_kind) { + switch (target_kind) { + case ConversionTarget::Value: + return category == SemIR::ExpressionCategory::Value; + case ConversionTarget::ValueOrReference: + case ConversionTarget::Discarded: + return category == SemIR::ExpressionCategory::Value || + category == SemIR::ExpressionCategory::DurableReference || + category == SemIR::ExpressionCategory::EphemeralReference || + category == SemIR::ExpressionCategory::Initializing; + case ConversionTarget::Initializer: + case ConversionTarget::FullInitializer: + return category == SemIR::ExpressionCategory::Initializing; + } +} + +static auto PerformBuiltinConversion(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id, + ConversionTarget target) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + auto value = semantics_ir.GetNode(value_id); + auto value_type_id = value.type_id(); + auto target_type_node = semantics_ir.GetNode( + semantics_ir.GetTypeAllowBuiltinTypes(target.type_id)); + + // Various forms of implicit conversion are supported as builtin conversions, + // either in addition to or instead of `impl`s of `ImplicitAs` in the Carbon + // prelude. There are a few reasons we need to perform some of these + // conversions as builtins: + // + // 1) Conversions from struct and tuple *literals* have special rules that + // cannot be implemented by invoking `ImplicitAs`. Specifically, we must + // recurse into the elements of the literal before performing + // initialization in order to avoid unnecessary conversions between + // expression categories that would be performed by `ImplicitAs.Convert`. + // 2) (Not implemented yet) Conversion of a facet to a facet type depends on + // the value of the facet, not only its type, and therefore cannot be + // modeled by `ImplicitAs`. + // 3) Some of these conversions are used while checking the library + // definition of `ImplicitAs` itself or implementations of it. + // + // We also expect to see better performance by avoiding an `impl` lookup for + // common conversions. + // + // TODO: We should provide a debugging flag to turn off as many of these + // builtin conversions as we can so that we can test that they do the same + // thing as the library implementations. + // + // The builtin conversions that correspond to `impl`s in the library all + // correspond to `final impl`s, so we don't need to worry about `ImplicitAs` + // being specialized in any of these cases. + + // If the value is already of the right kind and expression category, there's + // nothing to do. Performing a conversion would decompose and rebuild tuples + // and structs, so it's important that we bail out early in this case. + if (value_type_id == target.type_id && + IsValidExpressionCategoryForConversionTarget( + SemIR::GetExpressionCategory(semantics_ir, value_id), target.kind)) { + return value_id; + } + + // A tuple (T1, T2, ..., Tn) converts to (U1, U2, ..., Un) if each Ti + // converts to Ui. + if (target_type_node.kind() == SemIR::NodeKind::TupleType) { + auto value_type_node = semantics_ir.GetNode( + semantics_ir.GetTypeAllowBuiltinTypes(value_type_id)); + if (value_type_node.kind() == SemIR::NodeKind::TupleType) { + return ConvertTupleToTuple(context, value_type_node, target_type_node, + value_id, target); + } + } + + // A struct {.f_1: T_1, .f_2: T_2, ..., .f_n: T_n} converts to + // {.f_p(1): U_p(1), .f_p(2): U_p(2), ..., .f_p(n): U_p(n)} if + // (p(1), ..., p(n)) is a permutation of (1, ..., n) and each Ti converts + // to Ui. + if (target_type_node.kind() == SemIR::NodeKind::StructType) { + auto value_type_node = semantics_ir.GetNode( + semantics_ir.GetTypeAllowBuiltinTypes(value_type_id)); + if (value_type_node.kind() == SemIR::NodeKind::StructType) { + return ConvertStructToStruct(context, value_type_node, target_type_node, + value_id, target); + } + } + + // A tuple (T1, T2, ..., Tn) converts to [T; n] if each Ti converts to T. + if (target_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) { + return ConvertTupleToArray(context, value_type_node, target_type_node, + value_id, target); + } + } + + if (target.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) { + auto tuple_block_id = value.GetAsTupleLiteral(); + llvm::SmallVector 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 into conversion. Switch to an + // iterative approach. + type_ids.push_back( + ExpressionAsType(context, parse_node, tuple_node_id)); + } + } + auto tuple_type_id = + context.CanonicalizeTupleType(parse_node, std::move(type_ids)); + return semantics_ir.GetTypeAllowBuiltinTypes(tuple_type_id); + } + + // `{}` converts to `{} 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) { + value_id = semantics_ir.GetTypeAllowBuiltinTypes(value_type_id); + } + } + + // No builtin conversion applies. + return value_id; +} + +auto Convert(Context& context, Parse::Node parse_node, SemIR::NodeId expr_id, + ConversionTarget target) -> SemIR::NodeId { + auto& semantics_ir = context.semantics_ir(); + auto orig_expr_id = expr_id; + + // Start by making sure both sides are valid. If any part is invalid, the + // result is invalid and we shouldn't error. + if (expr_id == SemIR::NodeId::BuiltinError) { + return expr_id; + } + if (semantics_ir.GetNode(expr_id).type_id() == SemIR::TypeId::Error || + target.type_id == SemIR::TypeId::Error) { + return SemIR::NodeId::BuiltinError; + } + + if (SemIR::GetExpressionCategory(semantics_ir, expr_id) == + 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."); + context.emitter().Emit(semantics_ir.GetNode(expr_id).parse_node(), + UseOfNonExpressionAsValue); + return SemIR::NodeId::BuiltinError; + } + + // Check whether any builtin conversion applies. + expr_id = PerformBuiltinConversion(context, parse_node, expr_id, target); + if (expr_id == SemIR::NodeId::BuiltinError) { + return expr_id; + } + + // If the types don't match at this point, we can't perform the conversion. + // TODO: Look for an ImplicitAs impl. + SemIR::Node expr = semantics_ir.GetNode(expr_id); + if (expr.type_id() != target.type_id) { + CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error, + "Cannot implicitly convert from `{0}` to `{1}`.", + std::string, std::string); + context.emitter() + .Build(parse_node, ImplicitAsConversionFailure, + semantics_ir.StringifyType(expr.type_id()), + semantics_ir.StringifyType(target.type_id)) + .Emit(); + return SemIR::NodeId::BuiltinError; + } + + // Now perform any necessary value category conversions. + switch (SemIR::GetExpressionCategory(semantics_ir, expr_id)) { + case SemIR::ExpressionCategory::NotExpression: + case SemIR::ExpressionCategory::Mixed: + CARBON_FATAL() << "Unexpected expression " << expr + << " after builtin conversions"; + + case SemIR::ExpressionCategory::Initializing: + if (target.is_initializer()) { + if (orig_expr_id == expr_id) { + // Don't fill in the return slot if we created the expression through + // a conversion. In that case, we will have created it with the + // target already set. + // TODO: Find a better way to track whether we need to do this. + MarkInitializerFor(semantics_ir, expr_id, target.init_id, + *target.init_block); + } + break; + } + + // 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(context, expr_id, + target.kind == ConversionTarget::Discarded); + // We now have an ephemeral reference. + [[fallthrough]]; + + case SemIR::ExpressionCategory::DurableReference: + case SemIR::ExpressionCategory::EphemeralReference: { + // If we have a reference and don't want one, form a value binding. + if (target.kind != ConversionTarget::ValueOrReference && + target.kind != ConversionTarget::Discarded) { + // TODO: Support types with custom value representations. + expr_id = context.AddNode(SemIR::Node::BindValue::Make( + expr.parse_node(), expr.type_id(), expr_id)); + } + break; + } + + case SemIR::ExpressionCategory::Value: + break; + } + + // Perform a final destination store, if necessary. + if (target.kind == ConversionTarget::FullInitializer) { + if (auto init_rep = + SemIR::GetInitializingRepresentation(semantics_ir, target.type_id); + init_rep.kind == SemIR::InitializingRepresentation::ByCopy) { + target.init_block->InsertHere(); + expr_id = context.AddNode(SemIR::Node::InitializeFrom::Make( + parse_node, target.type_id, expr_id, target.init_id)); + } + } + + return expr_id; +} + +auto Initialize(Context& context, Parse::Node parse_node, + SemIR::NodeId target_id, SemIR::NodeId value_id) + -> SemIR::NodeId { + PendingBlock target_block(context); + return Convert( + context, parse_node, value_id, + {.kind = ConversionTarget::Initializer, + .type_id = context.semantics_ir().GetNode(target_id).type_id(), + .init_id = target_id, + .init_block = &target_block}); +} + +auto ConvertToValueExpression(Context& context, SemIR::NodeId expr_id) + -> SemIR::NodeId { + auto expr = context.semantics_ir().GetNode(expr_id); + return Convert(context, expr.parse_node(), expr_id, + {.kind = ConversionTarget::Value, .type_id = expr.type_id()}); +} + +auto ConvertToValueOrReferenceExpression(Context& context, + SemIR::NodeId expr_id) + -> SemIR::NodeId { + auto expr = context.semantics_ir().GetNode(expr_id); + return Convert( + context, expr.parse_node(), expr_id, + {.kind = ConversionTarget::ValueOrReference, .type_id = expr.type_id()}); +} + +auto ConvertToValueOfType(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id, SemIR::TypeId type_id) + -> SemIR::NodeId { + return Convert(context, parse_node, value_id, + {.kind = ConversionTarget::Value, .type_id = type_id}); +} + +auto ConvertToBoolValue(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id) -> SemIR::NodeId { + return ConvertToValueOfType( + context, parse_node, value_id, + context.CanonicalizeType(SemIR::NodeId::BuiltinBoolType)); +} + +auto ConvertCallArgs(Context& context, 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 = context.semantics_ir().GetNodeBlock(arg_refs_id); + auto param_refs = context.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."); + context.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( + &context.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 = context.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(context, call_parse_node, value_id, as_type_id); + if (value_id == SemIR::NodeId::BuiltinError) { + return false; + } + arg_refs[i] = value_id; + } + + return true; +} + +auto ExpressionAsType(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id) -> SemIR::TypeId { + return context.CanonicalizeType(ConvertToValueOfType( + context, parse_node, value_id, SemIR::TypeId::TypeType)); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h new file mode 100644 index 000000000000..7991ea3585c4 --- /dev/null +++ b/toolchain/check/convert.h @@ -0,0 +1,93 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_CONVERT_H_ +#define CARBON_TOOLCHAIN_CHECK_CONVERT_H_ + +#include "toolchain/check/context.h" +#include "toolchain/check/pending_block.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/node.h" + +namespace Carbon::Check { + +// Description of the target of a conversion. +struct ConversionTarget { + enum Kind { + // Convert to a value of type `type`. + Value, + // Convert to either a value or a reference of type `type`. + ValueOrReference, + // The result of the conversion is discarded. It can't be an initializing + // expression, but can be anything else. + Discarded, + // Convert to an initializer for the object denoted by `init_id`. + Initializer, + // Convert to an initializer for the object denoted by `init_id`, + // including a final destination store if needed. + FullInitializer, + Last = FullInitializer + }; + // The kind of the target for this conversion. + Kind kind; + // The target type for the conversion. + SemIR::TypeId type_id; + // For an initializer, the object being initialized. + SemIR::NodeId init_id = SemIR::NodeId::Invalid; + // For an initializer, a block of pending instructions that are needed to + // form the value of `target_id`, and that can be discarded if no + // initialization is needed. + PendingBlock* init_block = nullptr; + + // Are we converting this value into an initializer for an object? + bool is_initializer() const { + return kind == Initializer || kind == FullInitializer; + } +}; + +// Convert a value to another type and expression category. +auto Convert(Context& context, Parse::Node parse_node, SemIR::NodeId value_id, + ConversionTarget target) -> SemIR::NodeId; + +// Performs initialization of `target_id` from `value_id`. Returns the +// possibly-converted initializing expression, which should be assigned to the +// target using a suitable node for the kind of initialization. +auto Initialize(Context& context, Parse::Node parse_node, + SemIR::NodeId target_id, SemIR::NodeId value_id) + -> SemIR::NodeId; + +// Convert the given expression to a value expression of the same type. +auto ConvertToValueExpression(Context& context, SemIR::NodeId expr_id) + -> SemIR::NodeId; + +// Convert the given expression to a value or reference expression of the same +// type. +auto ConvertToValueOrReferenceExpression(Context& context, + SemIR::NodeId expr_id) + -> SemIR::NodeId; + +// Converts `value_id` to a value expression of type `type_id`. +auto ConvertToValueOfType(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id, SemIR::TypeId type_id) + -> SemIR::NodeId; + +// Converts `value_id` to a value expression of type `bool`. +auto ConvertToBoolValue(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id) -> SemIR::NodeId; + +// Implicitly converts a set of arguments to match the parameter types in a +// function call. +auto ConvertCallArgs(Context& context, 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; + +// Converts an expression for use as a type. +auto ExpressionAsType(Context& context, Parse::Node parse_node, + SemIR::NodeId value_id) -> SemIR::TypeId; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_CONVERT_H_ diff --git a/toolchain/check/handle_array.cpp b/toolchain/check/handle_array.cpp index 31560dd19bd5..925c174b8812 100644 --- a/toolchain/check/handle_array.cpp +++ b/toolchain/check/handle_array.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/parse/node_kind.h" #include "toolchain/sem_ir/node.h" #include "toolchain/sem_ir/node_kind.h" @@ -43,7 +44,7 @@ auto HandleArrayExpression(Context& context, Parse::Node parse_node) -> bool { parse_node, SemIR::Node::ArrayType::Make( parse_node, SemIR::TypeId::TypeType, bound_node_id, - context.ExpressionAsType(parse_node, element_type_node_id))); + ExpressionAsType(context, parse_node, element_type_node_id))); return true; } } diff --git a/toolchain/check/handle_call_expression.cpp b/toolchain/check/handle_call_expression.cpp index c00d7a329bc5..d509eac1612f 100644 --- a/toolchain/check/handle_call_expression.cpp +++ b/toolchain/check/handle_call_expression.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { @@ -46,9 +47,9 @@ auto HandleCallExpression(Context& context, Parse::Node parse_node) -> bool { // Convert the arguments to match the parameters. auto refs_id = context.ParamOrArgPop(); - if (!context.ImplicitAsForArgs(call_expr_parse_node, refs_id, - name_node.parse_node(), callable.param_refs_id, - callable.return_slot_id.is_valid())) { + if (!ConvertCallArgs(context, call_expr_parse_node, refs_id, + name_node.parse_node(), callable.param_refs_id, + callable.return_slot_id.is_valid())) { context.node_stack().Push(parse_node, SemIR::NodeId::BuiltinError); return true; } diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 63bddfeadfb3..061b24e47826 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { @@ -133,7 +134,7 @@ auto HandleReturnType(Context& context, Parse::Node parse_node) -> bool { // Propagate the type expression. auto [type_parse_node, type_node_id] = context.node_stack().PopExpressionWithParseNode(); - auto type_id = context.ExpressionAsType(type_parse_node, type_node_id); + auto type_id = ExpressionAsType(context, type_parse_node, type_node_id); // TODO: Use a dedicated node rather than VarStorage here. context.AddNodeAndPush( parse_node, diff --git a/toolchain/check/handle_if_expression.cpp b/toolchain/check/handle_if_expression.cpp index a9900f2f045e..bf786f4c4774 100644 --- a/toolchain/check/handle_if_expression.cpp +++ b/toolchain/check/handle_if_expression.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { @@ -13,7 +14,7 @@ auto HandleIfExpressionIf(Context& context, Parse::Node parse_node) -> bool { auto cond_value_id = context.node_stack().PopExpression(); // Convert the condition to `bool`, and branch on it. - cond_value_id = context.ConvertToBoolValue(if_node, cond_value_id); + cond_value_id = ConvertToBoolValue(context, if_node, cond_value_id); auto then_block_id = context.AddDominatedBlockAndBranchIf(if_node, cond_value_id); auto else_block_id = context.AddDominatedBlockAndBranch(if_node); @@ -33,7 +34,7 @@ auto HandleIfExpressionThen(Context& context, Parse::Node parse_node) -> bool { context.node_stack().Peek(); // Convert the first operand to a value. - then_value_id = context.ConvertToValueExpression(then_value_id); + then_value_id = ConvertToValueExpression(context, then_value_id); // Start emitting the `else` block. context.node_block_stack().Push(else_block_id); @@ -58,7 +59,7 @@ auto HandleIfExpressionElse(Context& context, Parse::Node parse_node) -> bool { // TODO: Find a common type, and convert both operands to it instead. auto result_type_id = context.semantics_ir().GetNode(then_value_id).type_id(); else_value_id = - context.ConvertToValueOfType(else_node, else_value_id, result_type_id); + ConvertToValueOfType(context, else_node, else_value_id, result_type_id); // Create a resumption block and branches to it. auto chosen_value_id = context.AddConvergenceBlockWithArgAndPush( diff --git a/toolchain/check/handle_if_statement.cpp b/toolchain/check/handle_if_statement.cpp index cdf11a53b50d..0e025e3e52bc 100644 --- a/toolchain/check/handle_if_statement.cpp +++ b/toolchain/check/handle_if_statement.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { @@ -15,7 +16,7 @@ auto HandleIfConditionStart(Context& /*context*/, Parse::Node /*parse_node*/) auto HandleIfCondition(Context& context, Parse::Node parse_node) -> bool { // Convert the condition to `bool`. auto cond_value_id = context.node_stack().PopExpression(); - cond_value_id = context.ConvertToBoolValue(parse_node, cond_value_id); + cond_value_id = ConvertToBoolValue(context, parse_node, cond_value_id); // Create the then block and the else block, and branch to the right one. If // there is no `else`, the then block will terminate with a branch to the diff --git a/toolchain/check/handle_index.cpp b/toolchain/check/handle_index.cpp index 9cbe4af8ce4a..3de689e5b743 100644 --- a/toolchain/check/handle_index.cpp +++ b/toolchain/check/handle_index.cpp @@ -4,6 +4,7 @@ #include "llvm/ADT/APSInt.h" #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" #include "toolchain/sem_ir/node_kind.h" @@ -42,7 +43,7 @@ auto HandleIndexExpression(Context& context, Parse::Node parse_node) -> bool { auto index_node = context.semantics_ir().GetNode(index_node_id); auto operand_node_id = context.node_stack().PopExpression(); operand_node_id = - context.ConvertToValueOrReferenceExpression(operand_node_id); + ConvertToValueOrReferenceExpression(context, operand_node_id); auto operand_node = context.semantics_ir().GetNode(operand_node_id); auto operand_type_id = operand_node.type_id(); auto operand_type_node = context.semantics_ir().GetNode( @@ -59,8 +60,8 @@ auto HandleIndexExpression(Context& context, Parse::Node parse_node) -> bool { context.semantics_ir().GetArrayBoundValue(bound_id))) { index_node_id = SemIR::NodeId::BuiltinError; } - auto cast_index_id = context.ConvertToValueOfType( - index_node.parse_node(), index_node_id, + auto cast_index_id = ConvertToValueOfType( + context, index_node.parse_node(), index_node_id, context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType)); context.AddNodeAndPush(parse_node, SemIR::Node::ArrayIndex::Make( parse_node, element_type_id, diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 9df65cd6b18e..66be7b4d1d58 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -4,6 +4,7 @@ #include "llvm/ADT/STLExtras.h" #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { @@ -25,7 +26,7 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) } // Materialize a temporary for the base expression if necessary. - base_id = context.ConvertToValueOrReferenceExpression(base_id); + base_id = ConvertToValueOrReferenceExpression(context, base_id); auto base_type = context.semantics_ir().GetNode( context.semantics_ir().GetTypeAllowBuiltinTypes(base.type_id())); diff --git a/toolchain/check/handle_operator.cpp b/toolchain/check/handle_operator.cpp index 879b6022fa74..382afe3c5a4f 100644 --- a/toolchain/check/handle_operator.cpp +++ b/toolchain/check/handle_operator.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { @@ -16,9 +17,10 @@ auto HandleInfixOperator(Context& context, Parse::Node parse_node) -> bool { case Lex::TokenKind::Plus: // TODO: This should search for a compatible interface. For now, it's a // very trivial check of validity on the operation. - lhs_id = context.ConvertToValueOfType( - parse_node, lhs_id, context.semantics_ir().GetNode(rhs_id).type_id()); - rhs_id = context.ConvertToValueExpression(rhs_id); + lhs_id = ConvertToValueOfType( + context, parse_node, lhs_id, + context.semantics_ir().GetNode(rhs_id).type_id()); + rhs_id = ConvertToValueExpression(context, rhs_id); context.AddNodeAndPush( parse_node, @@ -32,7 +34,7 @@ auto HandleInfixOperator(Context& context, Parse::Node parse_node) -> bool { // The first operand is wrapped in a ShortCircuitOperand, which we // already handled by creating a RHS block and a resumption block, which // are the current block and its enclosing block. - rhs_id = context.ConvertToBoolValue(parse_node, rhs_id); + rhs_id = ConvertToBoolValue(context, parse_node, rhs_id); // When the second operand is evaluated, the result of `and` and `or` is // its value. @@ -60,7 +62,7 @@ auto HandleInfixOperator(Context& context, Parse::Node parse_node) -> bool { } // TODO: Destroy the old value before reinitializing. This will require // building the destruction code before we build the RHS subexpression. - rhs_id = context.Initialize(parse_node, lhs_id, rhs_id); + rhs_id = Initialize(context, parse_node, lhs_id, rhs_id); context.AddNode(SemIR::Node::Assign::Make(parse_node, lhs_id, rhs_id)); // We model assignment as an expression, so we need to push a value for // it, even though it doesn't produce a value. @@ -81,7 +83,7 @@ auto HandlePostfixOperator(Context& context, Parse::Node parse_node) -> bool { auto token = context.parse_tree().node_token(parse_node); switch (auto token_kind = context.tokens().GetKind(token)) { case Lex::TokenKind::Star: { - auto inner_type_id = context.ExpressionAsType(parse_node, value_id); + auto inner_type_id = ExpressionAsType(context, parse_node, value_id); context.AddNodeAndPush( parse_node, SemIR::Node::PointerType::Make( parse_node, SemIR::TypeId::TypeType, inner_type_id)); @@ -138,7 +140,7 @@ auto HandlePrefixOperator(Context& context, Parse::Node parse_node) -> bool { "additional effect."); context.emitter().Emit(parse_node, RepeatedConst); } - auto inner_type_id = context.ExpressionAsType(parse_node, value_id); + auto inner_type_id = ExpressionAsType(context, parse_node, value_id); context.AddNodeAndPush( parse_node, SemIR::Node::ConstType::Make( parse_node, SemIR::TypeId::TypeType, inner_type_id)); @@ -146,7 +148,7 @@ auto HandlePrefixOperator(Context& context, Parse::Node parse_node) -> bool { } case Lex::TokenKind::Not: - value_id = context.ConvertToBoolValue(parse_node, value_id); + value_id = ConvertToBoolValue(context, parse_node, value_id); context.AddNodeAndPush( parse_node, SemIR::Node::UnaryOperatorNot::Make( @@ -179,7 +181,7 @@ auto HandlePrefixOperator(Context& context, Parse::Node parse_node) -> bool { } builder.Emit(); } - value_id = context.ConvertToValueExpression(value_id); + value_id = ConvertToValueExpression(context, value_id); context.AddNodeAndPush( parse_node, SemIR::Node::Dereference::Make(parse_node, result_type_id, value_id)); @@ -195,7 +197,7 @@ auto HandleShortCircuitOperand(Context& context, Parse::Node parse_node) -> bool { // Convert the condition to `bool`. auto cond_value_id = context.node_stack().PopExpression(); - cond_value_id = context.ConvertToBoolValue(parse_node, cond_value_id); + cond_value_id = ConvertToBoolValue(context, parse_node, cond_value_id); auto bool_type_id = context.semantics_ir().GetNode(cond_value_id).type_id(); // Compute the branch value: the condition for `and`, inverted for `or`. diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index d318fd98003e..90044c9baa8a 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/toolchain/check/handle_pattern_binding.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { @@ -19,7 +20,7 @@ auto HandleGenericPatternBinding(Context& context, Parse::Node parse_node) auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { auto [type_node, parsed_type_id] = context.node_stack().PopExpressionWithParseNode(); - auto cast_type_id = context.ExpressionAsType(type_node, parsed_type_id); + auto cast_type_id = ExpressionAsType(context, type_node, parsed_type_id); // Get the name. auto [name_node, name_id] = diff --git a/toolchain/check/handle_statement.cpp b/toolchain/check/handle_statement.cpp index 8d90514bbaee..8744ca9f0855 100644 --- a/toolchain/check/handle_statement.cpp +++ b/toolchain/check/handle_statement.cpp @@ -3,13 +3,27 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { +// TODO: Find a better home for this. We'll likely need it for more than just +// expression statements. +static auto HandleDiscardedExpression(Context& context, 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. + auto expr = context.semantics_ir().GetNode(expr_id); + Convert(context, expr.parse_node(), expr_id, + {.kind = ConversionTarget::Discarded, .type_id = expr.type_id()}); + + // TODO: This will eventually need to do some "do not discard" analysis. +} + auto HandleExpressionStatement(Context& context, Parse::Node /*parse_node*/) -> bool { - context.HandleDiscardedExpression(context.node_stack().PopExpression()); + HandleDiscardedExpression(context, context.node_stack().PopExpression()); return true; } @@ -52,10 +66,10 @@ auto HandleReturnStatement(Context& context, Parse::Node parse_node) -> bool { .Note(fn_node.parse_node(), ReturnStatementImplicitNote) .Emit(); } else if (callable.return_slot_id.is_valid()) { - arg = context.Initialize(parse_node, callable.return_slot_id, arg); + arg = Initialize(context, parse_node, callable.return_slot_id, arg); } else { - arg = context.ConvertToValueOfType(parse_node, arg, - callable.return_type_id); + arg = ConvertToValueOfType(context, parse_node, arg, + callable.return_type_id); } context.AddNode(SemIR::Node::ReturnExpression::Make(parse_node, arg)); diff --git a/toolchain/check/handle_struct.cpp b/toolchain/check/handle_struct.cpp index 96630821dc36..e29a0c534a8d 100644 --- a/toolchain/check/handle_struct.cpp +++ b/toolchain/check/handle_struct.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { @@ -22,7 +23,7 @@ auto HandleStructFieldDesignator(Context& context, Parse::Node /*parse_node*/) auto HandleStructFieldType(Context& context, Parse::Node parse_node) -> bool { auto [type_node, type_id] = context.node_stack().PopExpressionWithParseNode(); - SemIR::TypeId cast_type_id = context.ExpressionAsType(type_node, type_id); + SemIR::TypeId cast_type_id = ExpressionAsType(context, type_node, type_id); auto [name_node, name_id] = context.node_stack().PopWithParseNode(); diff --git a/toolchain/check/handle_variable.cpp b/toolchain/check/handle_variable.cpp index 82c87c4bfc3b..5280d5c075c8 100644 --- a/toolchain/check/handle_variable.cpp +++ b/toolchain/check/handle_variable.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" #include "toolchain/sem_ir/node.h" namespace Carbon::Check { @@ -28,7 +29,7 @@ auto HandleVariableDeclaration(Context& context, Parse::Node parse_node) context.AddNameToLookup(var.parse_node(), name_id, var_id); // If there was an initializer, assign it to storage. if (has_init) { - init_id = context.Initialize(parse_node, var_id, init_id); + init_id = Initialize(context, parse_node, var_id, init_id); // TODO: Consider using different node kinds for assignment versus // initialization. context.AddNode(SemIR::Node::Assign::Make(parse_node, var_id, init_id)); diff --git a/toolchain/check/pending_block.h b/toolchain/check/pending_block.h new file mode 100644 index 000000000000..5aa638539648 --- /dev/null +++ b/toolchain/check/pending_block.h @@ -0,0 +1,94 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_PENDING_BLOCK_H_ +#define CARBON_TOOLCHAIN_CHECK_PENDING_BLOCK_H_ + +#include "llvm/ADT/SmallVector.h" +#include "toolchain/check/context.h" + +namespace Carbon::Check { + +// A block of code that contains pending instructions that might be needed but +// that haven't been inserted yet. +class 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: + // If `block` is not null, enters the scope. If `block` is null, this object + // has no effect. + DiscardUnusedNodesScope(PendingBlock* block) + : block_(block), size_(block ? block->nodes_.size() : 0) {} + ~DiscardUnusedNodesScope() { + if (block_ && 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 nodes_; +}; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_PENDING_BLOCK_H_ diff --git a/toolchain/check/testdata/array/array_in_place.carbon b/toolchain/check/testdata/array/array_in_place.carbon index f2dd99074c69..3d21dfcc3bcb 100644 --- a/toolchain/check/testdata/array/array_in_place.carbon +++ b/toolchain/check/testdata/array/array_in_place.carbon @@ -24,19 +24,18 @@ fn G() { // CHECK:STDOUT: %.loc10_29: type = array_type %.loc10_28, (i32, i32, i32) // CHECK:STDOUT: %v: ref [(i32, i32, i32); 2] = var "v" // CHECK:STDOUT: %.loc10_42.3: ref (i32, i32, i32) = splice_block %.loc10_42.2 { -// CHECK:STDOUT: %.loc10_7: ref [(i32, i32, i32); 2] = splice_block %v {} // CHECK:STDOUT: %.loc10_42.1: i32 = int_literal 0 -// CHECK:STDOUT: %.loc10_42.2: ref (i32, i32, i32) = array_index %.loc10_7, %.loc10_42.1 +// CHECK:STDOUT: %.loc10_42.2: ref (i32, i32, i32) = array_index %v, %.loc10_42.1 // CHECK:STDOUT: } // CHECK:STDOUT: %.loc10_35: init (i32, i32, i32) = call @F() to %.loc10_42.3 // CHECK:STDOUT: %.loc10_42.6: ref (i32, i32, i32) = splice_block %.loc10_42.5 { // CHECK:STDOUT: %.loc10_42.4: i32 = int_literal 1 -// CHECK:STDOUT: %.loc10_42.5: ref (i32, i32, i32) = array_index %.loc10_7, %.loc10_42.4 +// CHECK:STDOUT: %.loc10_42.5: ref (i32, i32, i32) = array_index %v, %.loc10_42.4 // CHECK:STDOUT: } // CHECK:STDOUT: %.loc10_40: init (i32, i32, i32) = call @F() to %.loc10_42.6 // CHECK:STDOUT: %.loc10_42.7: type = tuple_type ((i32, i32, i32), (i32, i32, i32)) // CHECK:STDOUT: %.loc10_42.8: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_35, %.loc10_40) -// CHECK:STDOUT: %.loc10_42.9: init [(i32, i32, i32); 2] = array_init %.loc10_42.8, (%.loc10_35, %.loc10_40) to %.loc10_7 +// CHECK:STDOUT: %.loc10_42.9: init [(i32, i32, i32); 2] = array_init %.loc10_42.8, (%.loc10_35, %.loc10_40) to %v // CHECK:STDOUT: assign %v, %.loc10_42.9 // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/array/assign_return_value.carbon b/toolchain/check/testdata/array/assign_return_value.carbon index 760a6c2042d8..04c3953053d3 100644 --- a/toolchain/check/testdata/array/assign_return_value.carbon +++ b/toolchain/check/testdata/array/assign_return_value.carbon @@ -33,11 +33,10 @@ fn Run() { // CHECK:STDOUT: %.loc10_22.3: ref (i32,) = temporary %.loc10_22.2, %.loc10_22.1 // CHECK:STDOUT: %.loc10_22.4: ref i32 = tuple_access %.loc10_22.3, member0 // CHECK:STDOUT: %.loc10_22.5: i32 = bind_value %.loc10_22.4 -// CHECK:STDOUT: %.loc10_7: ref [i32; 1] = splice_block %t {} // CHECK:STDOUT: %.loc10_22.6: i32 = int_literal 0 -// CHECK:STDOUT: %.loc10_22.7: ref i32 = array_index %.loc10_7, %.loc10_22.6 +// CHECK:STDOUT: %.loc10_22.7: ref i32 = array_index %t, %.loc10_22.6 // CHECK:STDOUT: %.loc10_22.8: init i32 = initialize_from %.loc10_22.5 to %.loc10_22.7 -// CHECK:STDOUT: %.loc10_22.9: init [i32; 1] = array_init %.loc10_22.3, (%.loc10_22.8) to %.loc10_7 +// CHECK:STDOUT: %.loc10_22.9: init [i32; 1] = array_init %.loc10_22.3, (%.loc10_22.8) to %t // CHECK:STDOUT: assign %t, %.loc10_22.9 // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/array/assign_var.carbon b/toolchain/check/testdata/array/assign_var.carbon index 05e7dcae3eb2..90191833f3be 100644 --- a/toolchain/check/testdata/array/assign_var.carbon +++ b/toolchain/check/testdata/array/assign_var.carbon @@ -15,34 +15,33 @@ var b: [i32; 3] = a; // CHECK:STDOUT: %.loc7_27: i32 = int_literal 1 // CHECK:STDOUT: %.loc7_30: i32 = int_literal 2 // CHECK:STDOUT: %.loc7_33: i32 = int_literal 3 -// CHECK:STDOUT: %.loc7_34: (i32, i32, i32) = tuple_literal (%.loc7_27, %.loc7_30, %.loc7_33) -// CHECK:STDOUT: %.loc7_35.1: ref i32 = tuple_access %a, member0 -// CHECK:STDOUT: %.loc7_35.2: init i32 = initialize_from %.loc7_27 to %.loc7_35.1 -// CHECK:STDOUT: %.loc7_35.3: ref i32 = tuple_access %a, member1 -// CHECK:STDOUT: %.loc7_35.4: init i32 = initialize_from %.loc7_30 to %.loc7_35.3 -// CHECK:STDOUT: %.loc7_35.5: ref i32 = tuple_access %a, member2 -// CHECK:STDOUT: %.loc7_35.6: init i32 = initialize_from %.loc7_33 to %.loc7_35.5 -// CHECK:STDOUT: %.loc7_35.7: init (i32, i32, i32) = tuple_init %.loc7_34, (%.loc7_35.2, %.loc7_35.4, %.loc7_35.6) -// CHECK:STDOUT: assign %a, %.loc7_35.7 +// CHECK:STDOUT: %.loc7_34.1: (i32, i32, i32) = tuple_literal (%.loc7_27, %.loc7_30, %.loc7_33) +// CHECK:STDOUT: %.loc7_34.2: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_34.3: init i32 = initialize_from %.loc7_27 to %.loc7_34.2 +// CHECK:STDOUT: %.loc7_34.4: ref i32 = tuple_access %a, member1 +// CHECK:STDOUT: %.loc7_34.5: init i32 = initialize_from %.loc7_30 to %.loc7_34.4 +// CHECK:STDOUT: %.loc7_34.6: ref i32 = tuple_access %a, member2 +// CHECK:STDOUT: %.loc7_34.7: init i32 = initialize_from %.loc7_33 to %.loc7_34.6 +// CHECK:STDOUT: %.loc7_34.8: init (i32, i32, i32) = tuple_init %.loc7_34.1, (%.loc7_34.3, %.loc7_34.5, %.loc7_34.7) +// CHECK:STDOUT: assign %a, %.loc7_34.8 // CHECK:STDOUT: %.loc8_14: i32 = int_literal 3 // CHECK:STDOUT: %.loc8_15: type = array_type %.loc8_14, i32 // CHECK:STDOUT: %b: ref [i32; 3] = var "b" // CHECK:STDOUT: %.loc7_5.1: ref i32 = tuple_access %a, member0 // CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 -// CHECK:STDOUT: %.loc8_5: ref [i32; 3] = splice_block %b {} // CHECK:STDOUT: %.loc7_5.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_5.4: ref i32 = array_index %.loc8_5, %.loc7_5.3 +// CHECK:STDOUT: %.loc7_5.4: ref i32 = array_index %b, %.loc7_5.3 // CHECK:STDOUT: %.loc7_5.5: init i32 = initialize_from %.loc7_5.2 to %.loc7_5.4 // CHECK:STDOUT: %.loc7_5.6: ref i32 = tuple_access %a, member1 // CHECK:STDOUT: %.loc7_5.7: i32 = bind_value %.loc7_5.6 // CHECK:STDOUT: %.loc7_5.8: i32 = int_literal 1 -// CHECK:STDOUT: %.loc7_5.9: ref i32 = array_index %.loc8_5, %.loc7_5.8 +// CHECK:STDOUT: %.loc7_5.9: ref i32 = array_index %b, %.loc7_5.8 // CHECK:STDOUT: %.loc7_5.10: init i32 = initialize_from %.loc7_5.7 to %.loc7_5.9 // CHECK:STDOUT: %.loc7_5.11: ref i32 = tuple_access %a, member2 // CHECK:STDOUT: %.loc7_5.12: i32 = bind_value %.loc7_5.11 // CHECK:STDOUT: %.loc7_5.13: i32 = int_literal 2 -// CHECK:STDOUT: %.loc7_5.14: ref i32 = array_index %.loc8_5, %.loc7_5.13 +// CHECK:STDOUT: %.loc7_5.14: ref i32 = array_index %b, %.loc7_5.13 // CHECK:STDOUT: %.loc7_5.15: init i32 = initialize_from %.loc7_5.12 to %.loc7_5.14 -// CHECK:STDOUT: %.loc7_5.16: init [i32; 3] = array_init %a, (%.loc7_5.5, %.loc7_5.10, %.loc7_5.15) to %.loc8_5 +// CHECK:STDOUT: %.loc7_5.16: init [i32; 3] = array_init %a, (%.loc7_5.5, %.loc7_5.10, %.loc7_5.15) to %b // CHECK:STDOUT: assign %b, %.loc7_5.16 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/array/base.carbon b/toolchain/check/testdata/array/base.carbon index 4ad1db1b1bf6..70cdd5f25261 100644 --- a/toolchain/check/testdata/array/base.carbon +++ b/toolchain/check/testdata/array/base.carbon @@ -15,11 +15,10 @@ var c: [(); 5] = ((), (), (), (), (),); // CHECK:STDOUT: %.loc7_20: i32 = int_literal 1 // CHECK:STDOUT: %.loc7_22.1: type = tuple_type (i32) // CHECK:STDOUT: %.loc7_22.2: (i32,) = tuple_literal (%.loc7_20) -// CHECK:STDOUT: %.loc7_5: ref [i32; 1] = splice_block %a {} // CHECK:STDOUT: %.loc7_22.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_22.4: ref i32 = array_index %.loc7_5, %.loc7_22.3 +// CHECK:STDOUT: %.loc7_22.4: ref i32 = array_index %a, %.loc7_22.3 // CHECK:STDOUT: %.loc7_22.5: init i32 = initialize_from %.loc7_20 to %.loc7_22.4 -// CHECK:STDOUT: %.loc7_22.6: init [i32; 1] = array_init %.loc7_22.2, (%.loc7_22.5) to %.loc7_5 +// CHECK:STDOUT: %.loc7_22.6: init [i32; 1] = array_init %.loc7_22.2, (%.loc7_22.5) to %a // CHECK:STDOUT: assign %a, %.loc7_22.6 // CHECK:STDOUT: %.loc8_14: i32 = int_literal 2 // CHECK:STDOUT: %.loc8_15: type = array_type %.loc8_14, f64 @@ -28,33 +27,31 @@ var c: [(); 5] = ((), (), (), (), (),); // CHECK:STDOUT: %.loc8_26: f64 = real_literal 22e-1 // CHECK:STDOUT: %.loc8_30.1: type = tuple_type (f64, f64) // CHECK:STDOUT: %.loc8_30.2: (f64, f64) = tuple_literal (%.loc8_20, %.loc8_26) -// CHECK:STDOUT: %.loc8_5: ref [f64; 2] = splice_block %b {} // CHECK:STDOUT: %.loc8_30.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc8_30.4: ref f64 = array_index %.loc8_5, %.loc8_30.3 +// CHECK:STDOUT: %.loc8_30.4: ref f64 = array_index %b, %.loc8_30.3 // CHECK:STDOUT: %.loc8_30.5: init f64 = initialize_from %.loc8_20 to %.loc8_30.4 // CHECK:STDOUT: %.loc8_30.6: i32 = int_literal 1 -// CHECK:STDOUT: %.loc8_30.7: ref f64 = array_index %.loc8_5, %.loc8_30.6 +// CHECK:STDOUT: %.loc8_30.7: ref f64 = array_index %b, %.loc8_30.6 // CHECK:STDOUT: %.loc8_30.8: init f64 = initialize_from %.loc8_26 to %.loc8_30.7 -// CHECK:STDOUT: %.loc8_30.9: init [f64; 2] = array_init %.loc8_30.2, (%.loc8_30.5, %.loc8_30.8) to %.loc8_5 +// CHECK:STDOUT: %.loc8_30.9: init [f64; 2] = array_init %.loc8_30.2, (%.loc8_30.5, %.loc8_30.8) to %b // CHECK:STDOUT: assign %b, %.loc8_30.9 // CHECK:STDOUT: %.loc9_10.1: type = tuple_type () // CHECK:STDOUT: %.loc9_10.2: () = tuple_literal () // CHECK:STDOUT: %.loc9_13: i32 = int_literal 5 // CHECK:STDOUT: %.loc9_14: type = array_type %.loc9_13, () // CHECK:STDOUT: %c: ref [(); 5] = var "c" -// CHECK:STDOUT: %.loc9_20: () = tuple_literal () -// CHECK:STDOUT: %.loc9_24: () = tuple_literal () -// CHECK:STDOUT: %.loc9_28: () = tuple_literal () -// CHECK:STDOUT: %.loc9_32: () = tuple_literal () -// CHECK:STDOUT: %.loc9_36: () = tuple_literal () +// CHECK:STDOUT: %.loc9_20.1: () = tuple_literal () +// CHECK:STDOUT: %.loc9_24.1: () = tuple_literal () +// CHECK:STDOUT: %.loc9_28.1: () = tuple_literal () +// CHECK:STDOUT: %.loc9_32.1: () = tuple_literal () +// CHECK:STDOUT: %.loc9_36.1: () = tuple_literal () // CHECK:STDOUT: %.loc9_38.1: type = tuple_type ((), (), (), (), ()) -// CHECK:STDOUT: %.loc9_38.2: ((), (), (), (), ()) = tuple_literal (%.loc9_20, %.loc9_24, %.loc9_28, %.loc9_32, %.loc9_36) -// CHECK:STDOUT: %.loc9_38.3: init () = tuple_init %.loc9_20, () -// CHECK:STDOUT: %.loc9_38.4: init () = tuple_init %.loc9_24, () -// CHECK:STDOUT: %.loc9_38.5: init () = tuple_init %.loc9_28, () -// CHECK:STDOUT: %.loc9_38.6: init () = tuple_init %.loc9_32, () -// CHECK:STDOUT: %.loc9_38.7: init () = tuple_init %.loc9_36, () -// CHECK:STDOUT: %.loc9_5: ref [(); 5] = splice_block %c {} -// CHECK:STDOUT: %.loc9_38.8: init [(); 5] = array_init %.loc9_38.2, (%.loc9_38.3, %.loc9_38.4, %.loc9_38.5, %.loc9_38.6, %.loc9_38.7) to %.loc9_5 -// CHECK:STDOUT: assign %c, %.loc9_38.8 +// CHECK:STDOUT: %.loc9_38.2: ((), (), (), (), ()) = tuple_literal (%.loc9_20.1, %.loc9_24.1, %.loc9_28.1, %.loc9_32.1, %.loc9_36.1) +// CHECK:STDOUT: %.loc9_20.2: init () = tuple_init %.loc9_20.1, () +// CHECK:STDOUT: %.loc9_24.2: init () = tuple_init %.loc9_24.1, () +// CHECK:STDOUT: %.loc9_28.2: init () = tuple_init %.loc9_28.1, () +// CHECK:STDOUT: %.loc9_32.2: init () = tuple_init %.loc9_32.1, () +// CHECK:STDOUT: %.loc9_36.2: init () = tuple_init %.loc9_36.1, () +// CHECK:STDOUT: %.loc9_38.3: init [(); 5] = array_init %.loc9_38.2, (%.loc9_20.2, %.loc9_24.2, %.loc9_28.2, %.loc9_32.2, %.loc9_36.2) to %c +// CHECK:STDOUT: assign %c, %.loc9_38.3 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/array/fail_type_mismatch.carbon b/toolchain/check/testdata/array/fail_type_mismatch.carbon index 921c135c3d63..4cdf82c38f08 100644 --- a/toolchain/check/testdata/array/fail_type_mismatch.carbon +++ b/toolchain/check/testdata/array/fail_type_mismatch.carbon @@ -35,10 +35,9 @@ var d: [i32; 3] = t2; // CHECK:STDOUT: %.loc10_32: String = string_literal "World" // CHECK:STDOUT: %.loc10_39.1: type = tuple_type (i32, String, String) // CHECK:STDOUT: %.loc10_39.2: (i32, String, String) = tuple_literal (%.loc10_20, %.loc10_23, %.loc10_32) -// CHECK:STDOUT: %.loc10_39.3: ref [i32; 3] = temporary_storage -// CHECK:STDOUT: %.loc10_39.4: i32 = int_literal 0 -// CHECK:STDOUT: %.loc10_39.5: ref i32 = array_index %.loc10_39.3, %.loc10_39.4 -// CHECK:STDOUT: %.loc10_39.6: init i32 = initialize_from %.loc10_20 to %.loc10_39.5 +// CHECK:STDOUT: %.loc10_39.3: i32 = int_literal 0 +// CHECK:STDOUT: %.loc10_39.4: ref i32 = array_index %a, %.loc10_39.3 +// CHECK:STDOUT: %.loc10_39.5: init i32 = initialize_from %.loc10_20 to %.loc10_39.4 // CHECK:STDOUT: assign %a, // CHECK:STDOUT: %.loc15_29.1: type = tuple_type (type, type, type) // CHECK:STDOUT: %.loc15_29.2: (type, type, type) = tuple_literal (i32, String, String) @@ -48,11 +47,10 @@ var d: [i32; 3] = t2; // CHECK:STDOUT: %b: ref [i32; 3] = var "b" // CHECK:STDOUT: %.loc15_5.1: ref i32 = tuple_access %t1, member0 // CHECK:STDOUT: %.loc15_5.2: i32 = bind_value %.loc15_5.1 -// CHECK:STDOUT: %.loc15_5.3: ref [i32; 3] = temporary_storage -// CHECK:STDOUT: %.loc15_5.4: i32 = int_literal 0 -// CHECK:STDOUT: %.loc15_5.5: ref i32 = array_index %.loc15_5.3, %.loc15_5.4 -// CHECK:STDOUT: %.loc15_5.6: init i32 = initialize_from %.loc15_5.2 to %.loc15_5.5 -// CHECK:STDOUT: %.loc15_5.7: ref String = tuple_access %t1, member1 +// CHECK:STDOUT: %.loc15_5.3: i32 = int_literal 0 +// CHECK:STDOUT: %.loc15_5.4: ref i32 = array_index %b, %.loc15_5.3 +// CHECK:STDOUT: %.loc15_5.5: init i32 = initialize_from %.loc15_5.2 to %.loc15_5.4 +// CHECK:STDOUT: %.loc15_5.6: ref String = tuple_access %t1, member1 // CHECK:STDOUT: assign %b, // CHECK:STDOUT: %.loc21_14: i32 = int_literal 3 // CHECK:STDOUT: %.loc21_15: type = array_type %.loc21_14, i32 diff --git a/toolchain/check/testdata/array/nine_elements.carbon b/toolchain/check/testdata/array/nine_elements.carbon index b87b63206bbe..020f9ee0b236 100644 --- a/toolchain/check/testdata/array/nine_elements.carbon +++ b/toolchain/check/testdata/array/nine_elements.carbon @@ -21,34 +21,33 @@ var a: [i32; 9] = (1, 2, 3, 4, 5, 6, 7, 8, 9); // CHECK:STDOUT: %.loc7_44: i32 = int_literal 9 // CHECK:STDOUT: %.loc7_45.1: type = tuple_type (i32, i32, i32, i32, i32, i32, i32, i32, i32) // CHECK:STDOUT: %.loc7_45.2: (i32, i32, i32, i32, i32, i32, i32, i32, i32) = tuple_literal (%.loc7_20, %.loc7_23, %.loc7_26, %.loc7_29, %.loc7_32, %.loc7_35, %.loc7_38, %.loc7_41, %.loc7_44) -// CHECK:STDOUT: %.loc7_5: ref [i32; 9] = splice_block %a {} // CHECK:STDOUT: %.loc7_45.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_45.4: ref i32 = array_index %.loc7_5, %.loc7_45.3 +// CHECK:STDOUT: %.loc7_45.4: ref i32 = array_index %a, %.loc7_45.3 // CHECK:STDOUT: %.loc7_45.5: init i32 = initialize_from %.loc7_20 to %.loc7_45.4 // CHECK:STDOUT: %.loc7_45.6: i32 = int_literal 1 -// CHECK:STDOUT: %.loc7_45.7: ref i32 = array_index %.loc7_5, %.loc7_45.6 +// CHECK:STDOUT: %.loc7_45.7: ref i32 = array_index %a, %.loc7_45.6 // CHECK:STDOUT: %.loc7_45.8: init i32 = initialize_from %.loc7_23 to %.loc7_45.7 // CHECK:STDOUT: %.loc7_45.9: i32 = int_literal 2 -// CHECK:STDOUT: %.loc7_45.10: ref i32 = array_index %.loc7_5, %.loc7_45.9 +// CHECK:STDOUT: %.loc7_45.10: ref i32 = array_index %a, %.loc7_45.9 // CHECK:STDOUT: %.loc7_45.11: init i32 = initialize_from %.loc7_26 to %.loc7_45.10 // CHECK:STDOUT: %.loc7_45.12: i32 = int_literal 3 -// CHECK:STDOUT: %.loc7_45.13: ref i32 = array_index %.loc7_5, %.loc7_45.12 +// CHECK:STDOUT: %.loc7_45.13: ref i32 = array_index %a, %.loc7_45.12 // CHECK:STDOUT: %.loc7_45.14: init i32 = initialize_from %.loc7_29 to %.loc7_45.13 // CHECK:STDOUT: %.loc7_45.15: i32 = int_literal 4 -// CHECK:STDOUT: %.loc7_45.16: ref i32 = array_index %.loc7_5, %.loc7_45.15 +// CHECK:STDOUT: %.loc7_45.16: ref i32 = array_index %a, %.loc7_45.15 // CHECK:STDOUT: %.loc7_45.17: init i32 = initialize_from %.loc7_32 to %.loc7_45.16 // CHECK:STDOUT: %.loc7_45.18: i32 = int_literal 5 -// CHECK:STDOUT: %.loc7_45.19: ref i32 = array_index %.loc7_5, %.loc7_45.18 +// CHECK:STDOUT: %.loc7_45.19: ref i32 = array_index %a, %.loc7_45.18 // CHECK:STDOUT: %.loc7_45.20: init i32 = initialize_from %.loc7_35 to %.loc7_45.19 // CHECK:STDOUT: %.loc7_45.21: i32 = int_literal 6 -// CHECK:STDOUT: %.loc7_45.22: ref i32 = array_index %.loc7_5, %.loc7_45.21 +// CHECK:STDOUT: %.loc7_45.22: ref i32 = array_index %a, %.loc7_45.21 // CHECK:STDOUT: %.loc7_45.23: init i32 = initialize_from %.loc7_38 to %.loc7_45.22 // CHECK:STDOUT: %.loc7_45.24: i32 = int_literal 7 -// CHECK:STDOUT: %.loc7_45.25: ref i32 = array_index %.loc7_5, %.loc7_45.24 +// CHECK:STDOUT: %.loc7_45.25: ref i32 = array_index %a, %.loc7_45.24 // CHECK:STDOUT: %.loc7_45.26: init i32 = initialize_from %.loc7_41 to %.loc7_45.25 // CHECK:STDOUT: %.loc7_45.27: i32 = int_literal 8 -// CHECK:STDOUT: %.loc7_45.28: ref i32 = array_index %.loc7_5, %.loc7_45.27 +// CHECK:STDOUT: %.loc7_45.28: ref i32 = array_index %a, %.loc7_45.27 // CHECK:STDOUT: %.loc7_45.29: init i32 = initialize_from %.loc7_44 to %.loc7_45.28 -// CHECK:STDOUT: %.loc7_45.30: init [i32; 9] = array_init %.loc7_45.2, (%.loc7_45.5, %.loc7_45.8, %.loc7_45.11, %.loc7_45.14, %.loc7_45.17, %.loc7_45.20, %.loc7_45.23, %.loc7_45.26, %.loc7_45.29) to %.loc7_5 +// CHECK:STDOUT: %.loc7_45.30: init [i32; 9] = array_init %.loc7_45.2, (%.loc7_45.5, %.loc7_45.8, %.loc7_45.11, %.loc7_45.14, %.loc7_45.17, %.loc7_45.20, %.loc7_45.23, %.loc7_45.26, %.loc7_45.29) to %a // CHECK:STDOUT: assign %a, %.loc7_45.30 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/basics/numeric_literals.carbon b/toolchain/check/testdata/basics/numeric_literals.carbon index bd45e8337910..50c1d29f6187 100644 --- a/toolchain/check/testdata/basics/numeric_literals.carbon +++ b/toolchain/check/testdata/basics/numeric_literals.carbon @@ -41,23 +41,22 @@ fn F() { // CHECK:STDOUT: %.loc15: i32 = int_literal 39999999999999999993 // CHECK:STDOUT: %.loc16_3.1: type = tuple_type (i32, i32, i32, i32, i32) // CHECK:STDOUT: %.loc16_3.2: (i32, i32, i32, i32, i32) = tuple_literal (%.loc11, %.loc12, %.loc13, %.loc14, %.loc15) -// CHECK:STDOUT: %.loc10_7: ref [i32; 5] = splice_block %ints {} // CHECK:STDOUT: %.loc16_3.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc16_3.4: ref i32 = array_index %.loc10_7, %.loc16_3.3 +// CHECK:STDOUT: %.loc16_3.4: ref i32 = array_index %ints, %.loc16_3.3 // CHECK:STDOUT: %.loc16_3.5: init i32 = initialize_from %.loc11 to %.loc16_3.4 // CHECK:STDOUT: %.loc16_3.6: i32 = int_literal 1 -// CHECK:STDOUT: %.loc16_3.7: ref i32 = array_index %.loc10_7, %.loc16_3.6 +// CHECK:STDOUT: %.loc16_3.7: ref i32 = array_index %ints, %.loc16_3.6 // CHECK:STDOUT: %.loc16_3.8: init i32 = initialize_from %.loc12 to %.loc16_3.7 // CHECK:STDOUT: %.loc16_3.9: i32 = int_literal 2 -// CHECK:STDOUT: %.loc16_3.10: ref i32 = array_index %.loc10_7, %.loc16_3.9 +// CHECK:STDOUT: %.loc16_3.10: ref i32 = array_index %ints, %.loc16_3.9 // CHECK:STDOUT: %.loc16_3.11: init i32 = initialize_from %.loc13 to %.loc16_3.10 // CHECK:STDOUT: %.loc16_3.12: i32 = int_literal 3 -// CHECK:STDOUT: %.loc16_3.13: ref i32 = array_index %.loc10_7, %.loc16_3.12 +// CHECK:STDOUT: %.loc16_3.13: ref i32 = array_index %ints, %.loc16_3.12 // CHECK:STDOUT: %.loc16_3.14: init i32 = initialize_from %.loc14 to %.loc16_3.13 // CHECK:STDOUT: %.loc16_3.15: i32 = int_literal 4 -// CHECK:STDOUT: %.loc16_3.16: ref i32 = array_index %.loc10_7, %.loc16_3.15 +// CHECK:STDOUT: %.loc16_3.16: ref i32 = array_index %ints, %.loc16_3.15 // CHECK:STDOUT: %.loc16_3.17: init i32 = initialize_from %.loc15 to %.loc16_3.16 -// CHECK:STDOUT: %.loc16_3.18: init [i32; 5] = array_init %.loc16_3.2, (%.loc16_3.5, %.loc16_3.8, %.loc16_3.11, %.loc16_3.14, %.loc16_3.17) to %.loc10_7 +// CHECK:STDOUT: %.loc16_3.18: init [i32; 5] = array_init %.loc16_3.2, (%.loc16_3.5, %.loc16_3.8, %.loc16_3.11, %.loc16_3.14, %.loc16_3.17) to %ints // CHECK:STDOUT: assign %ints, %.loc16_3.18 // CHECK:STDOUT: %.loc17_21: i32 = int_literal 7 // CHECK:STDOUT: %.loc17_22: type = array_type %.loc17_21, f64 @@ -71,29 +70,28 @@ fn F() { // CHECK:STDOUT: %.loc24: f64 = real_literal 399999999999999999930e39999999999999999992 // CHECK:STDOUT: %.loc25_3.1: type = tuple_type (f64, f64, f64, f64, f64, f64, f64) // CHECK:STDOUT: %.loc25_3.2: (f64, f64, f64, f64, f64, f64, f64) = tuple_literal (%.loc18, %.loc19, %.loc20, %.loc21, %.loc22, %.loc23, %.loc24) -// CHECK:STDOUT: %.loc17_7: ref [f64; 7] = splice_block %floats {} // CHECK:STDOUT: %.loc25_3.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc25_3.4: ref f64 = array_index %.loc17_7, %.loc25_3.3 +// CHECK:STDOUT: %.loc25_3.4: ref f64 = array_index %floats, %.loc25_3.3 // CHECK:STDOUT: %.loc25_3.5: init f64 = initialize_from %.loc18 to %.loc25_3.4 // CHECK:STDOUT: %.loc25_3.6: i32 = int_literal 1 -// CHECK:STDOUT: %.loc25_3.7: ref f64 = array_index %.loc17_7, %.loc25_3.6 +// CHECK:STDOUT: %.loc25_3.7: ref f64 = array_index %floats, %.loc25_3.6 // CHECK:STDOUT: %.loc25_3.8: init f64 = initialize_from %.loc19 to %.loc25_3.7 // CHECK:STDOUT: %.loc25_3.9: i32 = int_literal 2 -// CHECK:STDOUT: %.loc25_3.10: ref f64 = array_index %.loc17_7, %.loc25_3.9 +// CHECK:STDOUT: %.loc25_3.10: ref f64 = array_index %floats, %.loc25_3.9 // CHECK:STDOUT: %.loc25_3.11: init f64 = initialize_from %.loc20 to %.loc25_3.10 // CHECK:STDOUT: %.loc25_3.12: i32 = int_literal 3 -// CHECK:STDOUT: %.loc25_3.13: ref f64 = array_index %.loc17_7, %.loc25_3.12 +// CHECK:STDOUT: %.loc25_3.13: ref f64 = array_index %floats, %.loc25_3.12 // CHECK:STDOUT: %.loc25_3.14: init f64 = initialize_from %.loc21 to %.loc25_3.13 // CHECK:STDOUT: %.loc25_3.15: i32 = int_literal 4 -// CHECK:STDOUT: %.loc25_3.16: ref f64 = array_index %.loc17_7, %.loc25_3.15 +// CHECK:STDOUT: %.loc25_3.16: ref f64 = array_index %floats, %.loc25_3.15 // CHECK:STDOUT: %.loc25_3.17: init f64 = initialize_from %.loc22 to %.loc25_3.16 // CHECK:STDOUT: %.loc25_3.18: i32 = int_literal 5 -// CHECK:STDOUT: %.loc25_3.19: ref f64 = array_index %.loc17_7, %.loc25_3.18 +// CHECK:STDOUT: %.loc25_3.19: ref f64 = array_index %floats, %.loc25_3.18 // CHECK:STDOUT: %.loc25_3.20: init f64 = initialize_from %.loc23 to %.loc25_3.19 // CHECK:STDOUT: %.loc25_3.21: i32 = int_literal 6 -// CHECK:STDOUT: %.loc25_3.22: ref f64 = array_index %.loc17_7, %.loc25_3.21 +// CHECK:STDOUT: %.loc25_3.22: ref f64 = array_index %floats, %.loc25_3.21 // CHECK:STDOUT: %.loc25_3.23: init f64 = initialize_from %.loc24 to %.loc25_3.22 -// CHECK:STDOUT: %.loc25_3.24: init [f64; 7] = array_init %.loc25_3.2, (%.loc25_3.5, %.loc25_3.8, %.loc25_3.11, %.loc25_3.14, %.loc25_3.17, %.loc25_3.20, %.loc25_3.23) to %.loc17_7 +// CHECK:STDOUT: %.loc25_3.24: init [f64; 7] = array_init %.loc25_3.2, (%.loc25_3.5, %.loc25_3.8, %.loc25_3.11, %.loc25_3.14, %.loc25_3.17, %.loc25_3.20, %.loc25_3.23) to %floats // CHECK:STDOUT: assign %floats, %.loc25_3.24 // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/basics/raw_and_textual_ir.carbon b/toolchain/check/testdata/basics/raw_and_textual_ir.carbon index d5168eae6bef..bb299739451e 100644 --- a/toolchain/check/testdata/basics/raw_and_textual_ir.carbon +++ b/toolchain/check/testdata/basics/raw_and_textual_ir.carbon @@ -114,11 +114,11 @@ fn Foo(n: i32) -> (i32, f64) { // CHECK:STDOUT: %.loc12_15: i32 = int_literal 2 // CHECK:STDOUT: %.loc12_13: i32 = add %n, %.loc12_15 // CHECK:STDOUT: %.loc12_18: f64 = real_literal 34e-1 -// CHECK:STDOUT: %.loc12_21: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18) -// CHECK:STDOUT: %.loc12_22.1: ref i32 = tuple_access %return, member0 -// CHECK:STDOUT: %.loc12_22.2: init i32 = initialize_from %.loc12_13 to %.loc12_22.1 -// CHECK:STDOUT: %.loc12_22.3: ref f64 = tuple_access %return, member1 -// CHECK:STDOUT: %.loc12_22.4: init f64 = initialize_from %.loc12_18 to %.loc12_22.3 -// CHECK:STDOUT: %.loc12_22.5: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.2, %.loc12_22.4) -// CHECK:STDOUT: return %.loc12_22.5 +// CHECK:STDOUT: %.loc12_21.1: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18) +// CHECK:STDOUT: %.loc12_21.2: ref i32 = tuple_access %return, member0 +// CHECK:STDOUT: %.loc12_21.3: init i32 = initialize_from %.loc12_13 to %.loc12_21.2 +// CHECK:STDOUT: %.loc12_21.4: ref f64 = tuple_access %return, member1 +// CHECK:STDOUT: %.loc12_21.5: init f64 = initialize_from %.loc12_18 to %.loc12_21.4 +// CHECK:STDOUT: %.loc12_21.6: init (i32, f64) = tuple_init %.loc12_21.1, (%.loc12_21.3, %.loc12_21.5) +// CHECK:STDOUT: return %.loc12_21.6 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/basics/textual_ir.carbon b/toolchain/check/testdata/basics/textual_ir.carbon index 0e57d3f3e6d9..bca57cb9342d 100644 --- a/toolchain/check/testdata/basics/textual_ir.carbon +++ b/toolchain/check/testdata/basics/textual_ir.carbon @@ -21,11 +21,11 @@ fn Foo(n: i32) -> (i32, f64) { // CHECK:STDOUT: %.loc12_15: i32 = int_literal 2 // CHECK:STDOUT: %.loc12_13: i32 = add %n, %.loc12_15 // CHECK:STDOUT: %.loc12_18: f64 = real_literal 34e-1 -// CHECK:STDOUT: %.loc12_21: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18) -// CHECK:STDOUT: %.loc12_22.1: ref i32 = tuple_access %return, member0 -// CHECK:STDOUT: %.loc12_22.2: init i32 = initialize_from %.loc12_13 to %.loc12_22.1 -// CHECK:STDOUT: %.loc12_22.3: ref f64 = tuple_access %return, member1 -// CHECK:STDOUT: %.loc12_22.4: init f64 = initialize_from %.loc12_18 to %.loc12_22.3 -// CHECK:STDOUT: %.loc12_22.5: init (i32, f64) = tuple_init %.loc12_21, (%.loc12_22.2, %.loc12_22.4) -// CHECK:STDOUT: return %.loc12_22.5 +// CHECK:STDOUT: %.loc12_21.1: (i32, f64) = tuple_literal (%.loc12_13, %.loc12_18) +// CHECK:STDOUT: %.loc12_21.2: ref i32 = tuple_access %return, member0 +// CHECK:STDOUT: %.loc12_21.3: init i32 = initialize_from %.loc12_13 to %.loc12_21.2 +// CHECK:STDOUT: %.loc12_21.4: ref f64 = tuple_access %return, member1 +// CHECK:STDOUT: %.loc12_21.5: init f64 = initialize_from %.loc12_18 to %.loc12_21.4 +// CHECK:STDOUT: %.loc12_21.6: init (i32, f64) = tuple_init %.loc12_21.1, (%.loc12_21.3, %.loc12_21.5) +// CHECK:STDOUT: return %.loc12_21.6 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/if_expression/struct.carbon b/toolchain/check/testdata/if_expression/struct.carbon new file mode 100644 index 000000000000..8c2efb4a303f --- /dev/null +++ b/toolchain/check/testdata/if_expression/struct.carbon @@ -0,0 +1,57 @@ +// 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 +// +// AUTOUPDATE + +fn G(s: {.a: i32, .b: i32}); + +fn F(cond: bool) { + var a: {.a: i32, .b: i32} = {.a = 1, .b = 2}; + G(if cond then a else a); +} + +// CHECK:STDOUT: file "struct.carbon" { +// CHECK:STDOUT: %.loc7 = fn_decl @G +// CHECK:STDOUT: %.loc9 = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%s: {.a: i32, .b: i32}); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%cond: bool) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc10_27: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %a: ref {.a: i32, .b: i32} = var "a" +// CHECK:STDOUT: %.loc10_37: i32 = int_literal 1 +// CHECK:STDOUT: %.loc10_45: i32 = int_literal 2 +// CHECK:STDOUT: %.loc10_46.1: {.a: i32, .b: i32} = struct_literal (%.loc10_37, %.loc10_45) +// CHECK:STDOUT: %.loc10_46.2: ref i32 = struct_access %a, member0 +// CHECK:STDOUT: %.loc10_46.3: init i32 = initialize_from %.loc10_37 to %.loc10_46.2 +// CHECK:STDOUT: %.loc10_46.4: ref i32 = struct_access %a, member1 +// CHECK:STDOUT: %.loc10_46.5: init i32 = initialize_from %.loc10_45 to %.loc10_46.4 +// CHECK:STDOUT: %.loc10_46.6: init {.a: i32, .b: i32} = struct_init %.loc10_46.1, (%.loc10_46.3, %.loc10_46.5) +// CHECK:STDOUT: assign %a, %.loc10_46.6 +// CHECK:STDOUT: if %cond br !if.expr.then else br !if.expr.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.then: +// CHECK:STDOUT: %.loc10_7.1: ref i32 = struct_access %a, member0 +// CHECK:STDOUT: %.loc10_7.2: i32 = bind_value %.loc10_7.1 +// CHECK:STDOUT: %.loc10_7.3: ref i32 = struct_access %a, member1 +// CHECK:STDOUT: %.loc10_7.4: i32 = bind_value %.loc10_7.3 +// CHECK:STDOUT: %.loc10_7.5: {.a: i32, .b: i32} = struct_value %a, (%.loc10_7.2, %.loc10_7.4) +// CHECK:STDOUT: br !if.expr.result(%.loc10_7.5) +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.else: +// CHECK:STDOUT: %.loc10_7.6: ref i32 = struct_access %a, member0 +// CHECK:STDOUT: %.loc10_7.7: i32 = bind_value %.loc10_7.6 +// CHECK:STDOUT: %.loc10_7.8: ref i32 = struct_access %a, member1 +// CHECK:STDOUT: %.loc10_7.9: i32 = bind_value %.loc10_7.8 +// CHECK:STDOUT: %.loc10_7.10: {.a: i32, .b: i32} = struct_value %a, (%.loc10_7.7, %.loc10_7.9) +// CHECK:STDOUT: br !if.expr.result(%.loc10_7.10) +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.result: +// CHECK:STDOUT: %.loc11_5: {.a: i32, .b: i32} = block_arg !if.expr.result +// CHECK:STDOUT: %.loc11_4.1: type = tuple_type () +// CHECK:STDOUT: %.loc11_4.2: init () = call @G(%.loc11_5) +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/index/array_element_access.carbon b/toolchain/check/testdata/index/array_element_access.carbon index 1e637db389de..d330c81f7886 100644 --- a/toolchain/check/testdata/index/array_element_access.carbon +++ b/toolchain/check/testdata/index/array_element_access.carbon @@ -17,14 +17,13 @@ var d: i32 = a[b]; // CHECK:STDOUT: %.loc7_24: i32 = int_literal 24 // CHECK:STDOUT: %.loc7_26.1: type = tuple_type (i32, i32) // CHECK:STDOUT: %.loc7_26.2: (i32, i32) = tuple_literal (%.loc7_20, %.loc7_24) -// CHECK:STDOUT: %.loc7_5: ref [i32; 2] = splice_block %a {} // CHECK:STDOUT: %.loc7_26.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_26.4: ref i32 = array_index %.loc7_5, %.loc7_26.3 +// CHECK:STDOUT: %.loc7_26.4: ref i32 = array_index %a, %.loc7_26.3 // CHECK:STDOUT: %.loc7_26.5: init i32 = initialize_from %.loc7_20 to %.loc7_26.4 // CHECK:STDOUT: %.loc7_26.6: i32 = int_literal 1 -// CHECK:STDOUT: %.loc7_26.7: ref i32 = array_index %.loc7_5, %.loc7_26.6 +// CHECK:STDOUT: %.loc7_26.7: ref i32 = array_index %a, %.loc7_26.6 // CHECK:STDOUT: %.loc7_26.8: init i32 = initialize_from %.loc7_24 to %.loc7_26.7 -// CHECK:STDOUT: %.loc7_26.9: init [i32; 2] = array_init %.loc7_26.2, (%.loc7_26.5, %.loc7_26.8) to %.loc7_5 +// CHECK:STDOUT: %.loc7_26.9: init [i32; 2] = array_init %.loc7_26.2, (%.loc7_26.5, %.loc7_26.8) to %a // CHECK:STDOUT: assign %a, %.loc7_26.9 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc8_14: i32 = int_literal 1 diff --git a/toolchain/check/testdata/index/fail_array_large_index.carbon b/toolchain/check/testdata/index/fail_array_large_index.carbon index 6d435afb44a8..1242f98d7a93 100644 --- a/toolchain/check/testdata/index/fail_array_large_index.carbon +++ b/toolchain/check/testdata/index/fail_array_large_index.carbon @@ -17,11 +17,10 @@ var b: i32 = a[0xFFFFFFFFFFFFFFFFF]; // CHECK:STDOUT: %.loc7_20: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_23.1: type = tuple_type (i32) // CHECK:STDOUT: %.loc7_23.2: (i32,) = tuple_literal (%.loc7_20) -// CHECK:STDOUT: %.loc7_5: ref [i32; 1] = splice_block %a {} // CHECK:STDOUT: %.loc7_23.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %.loc7_5, %.loc7_23.3 +// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %a, %.loc7_23.3 // CHECK:STDOUT: %.loc7_23.5: init i32 = initialize_from %.loc7_20 to %.loc7_23.4 -// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %.loc7_5 +// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %a // CHECK:STDOUT: assign %a, %.loc7_23.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc11_16: i32 = int_literal 295147905179352825855 diff --git a/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon b/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon index 23124d73adeb..64bbc1534aec 100644 --- a/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon +++ b/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon @@ -17,11 +17,10 @@ var b: i32 = a[2.6]; // CHECK:STDOUT: %.loc7_20: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_23.1: type = tuple_type (i32) // CHECK:STDOUT: %.loc7_23.2: (i32,) = tuple_literal (%.loc7_20) -// CHECK:STDOUT: %.loc7_5: ref [i32; 1] = splice_block %a {} // CHECK:STDOUT: %.loc7_23.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %.loc7_5, %.loc7_23.3 +// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %a, %.loc7_23.3 // CHECK:STDOUT: %.loc7_23.5: init i32 = initialize_from %.loc7_20 to %.loc7_23.4 -// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %.loc7_5 +// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %a // CHECK:STDOUT: assign %a, %.loc7_23.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc11_16: f64 = real_literal 26e-1 diff --git a/toolchain/check/testdata/index/fail_array_out_of_bound_access.carbon b/toolchain/check/testdata/index/fail_array_out_of_bound_access.carbon index 48cfdd0266e8..ecfa55f56476 100644 --- a/toolchain/check/testdata/index/fail_array_out_of_bound_access.carbon +++ b/toolchain/check/testdata/index/fail_array_out_of_bound_access.carbon @@ -17,11 +17,10 @@ var b: i32 = a[2]; // CHECK:STDOUT: %.loc7_20: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_23.1: type = tuple_type (i32) // CHECK:STDOUT: %.loc7_23.2: (i32,) = tuple_literal (%.loc7_20) -// CHECK:STDOUT: %.loc7_5: ref [i32; 1] = splice_block %a {} // CHECK:STDOUT: %.loc7_23.3: i32 = int_literal 0 -// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %.loc7_5, %.loc7_23.3 +// CHECK:STDOUT: %.loc7_23.4: ref i32 = array_index %a, %.loc7_23.3 // CHECK:STDOUT: %.loc7_23.5: init i32 = initialize_from %.loc7_20 to %.loc7_23.4 -// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %.loc7_5 +// CHECK:STDOUT: %.loc7_23.6: init [i32; 1] = array_init %.loc7_23.2, (%.loc7_23.5) to %a // CHECK:STDOUT: assign %a, %.loc7_23.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc11_16: i32 = int_literal 2 diff --git a/toolchain/check/testdata/index/fail_non_deterministic_type.carbon b/toolchain/check/testdata/index/fail_non_deterministic_type.carbon index c459f2c211c5..e336f97ba748 100644 --- a/toolchain/check/testdata/index/fail_non_deterministic_type.carbon +++ b/toolchain/check/testdata/index/fail_non_deterministic_type.carbon @@ -18,13 +18,13 @@ var c: i32 = a[b]; // CHECK:STDOUT: %a: ref (i32, i32) = var "a" // CHECK:STDOUT: %.loc7_22: i32 = int_literal 2 // CHECK:STDOUT: %.loc7_25: i32 = int_literal 3 -// CHECK:STDOUT: %.loc7_26: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25) -// CHECK:STDOUT: %.loc7_27.1: ref i32 = tuple_access %a, member0 -// CHECK:STDOUT: %.loc7_27.2: init i32 = initialize_from %.loc7_22 to %.loc7_27.1 -// CHECK:STDOUT: %.loc7_27.3: ref i32 = tuple_access %a, member1 -// CHECK:STDOUT: %.loc7_27.4: init i32 = initialize_from %.loc7_25 to %.loc7_27.3 -// CHECK:STDOUT: %.loc7_27.5: init (i32, i32) = tuple_init %.loc7_26, (%.loc7_27.2, %.loc7_27.4) -// CHECK:STDOUT: assign %a, %.loc7_27.5 +// CHECK:STDOUT: %.loc7_26.1: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25) +// CHECK:STDOUT: %.loc7_26.2: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_26.3: init i32 = initialize_from %.loc7_22 to %.loc7_26.2 +// CHECK:STDOUT: %.loc7_26.4: ref i32 = tuple_access %a, member1 +// CHECK:STDOUT: %.loc7_26.5: init i32 = initialize_from %.loc7_25 to %.loc7_26.4 +// CHECK:STDOUT: %.loc7_26.6: init (i32, i32) = tuple_init %.loc7_26.1, (%.loc7_26.3, %.loc7_26.5) +// CHECK:STDOUT: assign %a, %.loc7_26.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc8: i32 = int_literal 0 // CHECK:STDOUT: assign %b, %.loc8 diff --git a/toolchain/check/testdata/index/fail_tuple_large_index.carbon b/toolchain/check/testdata/index/fail_tuple_large_index.carbon index b8ba3e955b17..5a18ae06c9ed 100644 --- a/toolchain/check/testdata/index/fail_tuple_large_index.carbon +++ b/toolchain/check/testdata/index/fail_tuple_large_index.carbon @@ -17,13 +17,15 @@ var c: i32 = b[0xFFFFFFFFFFFFFFFFF]; // CHECK:STDOUT: %.loc7_13.3: type = tuple_type (i32) // CHECK:STDOUT: %a: ref (i32,) = var "a" // CHECK:STDOUT: %.loc7_18: i32 = int_literal 12 -// CHECK:STDOUT: %.loc7_21: (i32,) = tuple_literal (%.loc7_18) -// CHECK:STDOUT: %.loc7_22: init (i32,) = tuple_init %.loc7_21, (%.loc7_18) -// CHECK:STDOUT: assign %a, %.loc7_22 +// CHECK:STDOUT: %.loc7_21.1: (i32,) = tuple_literal (%.loc7_18) +// CHECK:STDOUT: %.loc7_21.2: init (i32,) = tuple_init %.loc7_21.1, (%.loc7_18) +// CHECK:STDOUT: assign %a, %.loc7_21.2 // CHECK:STDOUT: %.loc8: (type,) = tuple_literal (i32) // CHECK:STDOUT: %b: ref (i32,) = var "b" -// CHECK:STDOUT: %.loc7_5: (i32,) = bind_value %a -// CHECK:STDOUT: assign %b, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: init (i32,) = tuple_init %a, (%.loc7_5.2) +// CHECK:STDOUT: assign %b, %.loc7_5.3 // CHECK:STDOUT: %c: ref i32 = var "c" // CHECK:STDOUT: %.loc12_16: i32 = int_literal 295147905179352825855 // CHECK:STDOUT: %.loc12_35: ref = tuple_index %b, diff --git a/toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon b/toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon index 9d98bdea87fc..8ef4feff1d47 100644 --- a/toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon +++ b/toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon @@ -17,13 +17,13 @@ var b: i32 = a[2.6]; // CHECK:STDOUT: %a: ref (i32, i32) = var "a" // CHECK:STDOUT: %.loc7_22: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_26: i32 = int_literal 6 -// CHECK:STDOUT: %.loc7_27: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26) -// CHECK:STDOUT: %.loc7_28.1: ref i32 = tuple_access %a, member0 -// CHECK:STDOUT: %.loc7_28.2: init i32 = initialize_from %.loc7_22 to %.loc7_28.1 -// CHECK:STDOUT: %.loc7_28.3: ref i32 = tuple_access %a, member1 -// CHECK:STDOUT: %.loc7_28.4: init i32 = initialize_from %.loc7_26 to %.loc7_28.3 -// CHECK:STDOUT: %.loc7_28.5: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.2, %.loc7_28.4) -// CHECK:STDOUT: assign %a, %.loc7_28.5 +// CHECK:STDOUT: %.loc7_27.1: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26) +// CHECK:STDOUT: %.loc7_27.2: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_27.3: init i32 = initialize_from %.loc7_22 to %.loc7_27.2 +// CHECK:STDOUT: %.loc7_27.4: ref i32 = tuple_access %a, member1 +// CHECK:STDOUT: %.loc7_27.5: init i32 = initialize_from %.loc7_26 to %.loc7_27.4 +// CHECK:STDOUT: %.loc7_27.6: init (i32, i32) = tuple_init %.loc7_27.1, (%.loc7_27.3, %.loc7_27.5) +// CHECK:STDOUT: assign %a, %.loc7_27.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc11_16: f64 = real_literal 26e-1 // CHECK:STDOUT: %.loc11_19: ref = tuple_index %a, diff --git a/toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon b/toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon index d10b8c6bc32c..961b22cc192f 100644 --- a/toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon +++ b/toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon @@ -17,13 +17,13 @@ var b: i32 = a[2]; // CHECK:STDOUT: %a: ref (i32, i32) = var "a" // CHECK:STDOUT: %.loc7_22: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_26: i32 = int_literal 6 -// CHECK:STDOUT: %.loc7_27: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26) -// CHECK:STDOUT: %.loc7_28.1: ref i32 = tuple_access %a, member0 -// CHECK:STDOUT: %.loc7_28.2: init i32 = initialize_from %.loc7_22 to %.loc7_28.1 -// CHECK:STDOUT: %.loc7_28.3: ref i32 = tuple_access %a, member1 -// CHECK:STDOUT: %.loc7_28.4: init i32 = initialize_from %.loc7_26 to %.loc7_28.3 -// CHECK:STDOUT: %.loc7_28.5: init (i32, i32) = tuple_init %.loc7_27, (%.loc7_28.2, %.loc7_28.4) -// CHECK:STDOUT: assign %a, %.loc7_28.5 +// CHECK:STDOUT: %.loc7_27.1: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_26) +// CHECK:STDOUT: %.loc7_27.2: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_27.3: init i32 = initialize_from %.loc7_22 to %.loc7_27.2 +// CHECK:STDOUT: %.loc7_27.4: ref i32 = tuple_access %a, member1 +// CHECK:STDOUT: %.loc7_27.5: init i32 = initialize_from %.loc7_26 to %.loc7_27.4 +// CHECK:STDOUT: %.loc7_27.6: init (i32, i32) = tuple_init %.loc7_27.1, (%.loc7_27.3, %.loc7_27.5) +// CHECK:STDOUT: assign %a, %.loc7_27.6 // CHECK:STDOUT: %b: ref i32 = var "b" // CHECK:STDOUT: %.loc11_16: i32 = int_literal 2 // CHECK:STDOUT: %.loc11_17: ref = tuple_index %a, diff --git a/toolchain/check/testdata/index/tuple_element_access.carbon b/toolchain/check/testdata/index/tuple_element_access.carbon index 1c2ac7a9755e..b962730df0a8 100644 --- a/toolchain/check/testdata/index/tuple_element_access.carbon +++ b/toolchain/check/testdata/index/tuple_element_access.carbon @@ -14,13 +14,15 @@ var c: i32 = b[0]; // CHECK:STDOUT: %.loc7_13.3: type = tuple_type (i32) // CHECK:STDOUT: %a: ref (i32,) = var "a" // CHECK:STDOUT: %.loc7_18: i32 = int_literal 12 -// CHECK:STDOUT: %.loc7_21: (i32,) = tuple_literal (%.loc7_18) -// CHECK:STDOUT: %.loc7_22: init (i32,) = tuple_init %.loc7_21, (%.loc7_18) -// CHECK:STDOUT: assign %a, %.loc7_22 +// CHECK:STDOUT: %.loc7_21.1: (i32,) = tuple_literal (%.loc7_18) +// CHECK:STDOUT: %.loc7_21.2: init (i32,) = tuple_init %.loc7_21.1, (%.loc7_18) +// CHECK:STDOUT: assign %a, %.loc7_21.2 // CHECK:STDOUT: %.loc8: (type,) = tuple_literal (i32) // CHECK:STDOUT: %b: ref (i32,) = var "b" -// CHECK:STDOUT: %.loc7_5: (i32,) = bind_value %a -// CHECK:STDOUT: assign %b, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = tuple_access %a, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: init (i32,) = tuple_init %a, (%.loc7_5.2) +// CHECK:STDOUT: assign %b, %.loc7_5.3 // CHECK:STDOUT: %c: ref i32 = var "c" // CHECK:STDOUT: %.loc9_16: i32 = int_literal 0 // CHECK:STDOUT: %.loc9_17.1: ref i32 = tuple_index %b, %.loc9_16 diff --git a/toolchain/check/testdata/operators/assignment.carbon b/toolchain/check/testdata/operators/assignment.carbon index ca528fe63129..60f8dfc8d5e4 100644 --- a/toolchain/check/testdata/operators/assignment.carbon +++ b/toolchain/check/testdata/operators/assignment.carbon @@ -39,13 +39,13 @@ fn Main() { // CHECK:STDOUT: %b: ref (i32, i32) = var "b" // CHECK:STDOUT: %.loc11_24: i32 = int_literal 1 // CHECK:STDOUT: %.loc11_27: i32 = int_literal 2 -// CHECK:STDOUT: %.loc11_28: (i32, i32) = tuple_literal (%.loc11_24, %.loc11_27) -// CHECK:STDOUT: %.loc11_29.1: ref i32 = tuple_access %b, member0 -// CHECK:STDOUT: %.loc11_29.2: init i32 = initialize_from %.loc11_24 to %.loc11_29.1 -// CHECK:STDOUT: %.loc11_29.3: ref i32 = tuple_access %b, member1 -// CHECK:STDOUT: %.loc11_29.4: init i32 = initialize_from %.loc11_27 to %.loc11_29.3 -// CHECK:STDOUT: %.loc11_29.5: init (i32, i32) = tuple_init %.loc11_28, (%.loc11_29.2, %.loc11_29.4) -// CHECK:STDOUT: assign %b, %.loc11_29.5 +// CHECK:STDOUT: %.loc11_28.1: (i32, i32) = tuple_literal (%.loc11_24, %.loc11_27) +// CHECK:STDOUT: %.loc11_28.2: ref i32 = tuple_access %b, member0 +// CHECK:STDOUT: %.loc11_28.3: init i32 = initialize_from %.loc11_24 to %.loc11_28.2 +// CHECK:STDOUT: %.loc11_28.4: ref i32 = tuple_access %b, member1 +// CHECK:STDOUT: %.loc11_28.5: init i32 = initialize_from %.loc11_27 to %.loc11_28.4 +// CHECK:STDOUT: %.loc11_28.6: init (i32, i32) = tuple_init %.loc11_28.1, (%.loc11_28.3, %.loc11_28.5) +// CHECK:STDOUT: assign %b, %.loc11_28.6 // CHECK:STDOUT: %.loc12_5: i32 = int_literal 0 // CHECK:STDOUT: %.loc12_6: ref i32 = tuple_index %b, %.loc12_5 // CHECK:STDOUT: %.loc12_10: i32 = int_literal 3 @@ -58,13 +58,13 @@ fn Main() { // CHECK:STDOUT: %c: ref {.a: i32, .b: i32} = var "c" // CHECK:STDOUT: %.loc15_37: i32 = int_literal 1 // CHECK:STDOUT: %.loc15_45: i32 = int_literal 2 -// CHECK:STDOUT: %.loc15_46: {.a: i32, .b: i32} = struct_literal (%.loc15_37, %.loc15_45) -// CHECK:STDOUT: %.loc15_47.1: ref i32 = struct_access %c, member0 -// CHECK:STDOUT: %.loc15_47.2: init i32 = initialize_from %.loc15_37 to %.loc15_47.1 -// CHECK:STDOUT: %.loc15_47.3: ref i32 = struct_access %c, member1 -// CHECK:STDOUT: %.loc15_47.4: init i32 = initialize_from %.loc15_45 to %.loc15_47.3 -// CHECK:STDOUT: %.loc15_47.5: init {.a: i32, .b: i32} = struct_init %.loc15_46, (%.loc15_47.2, %.loc15_47.4) -// CHECK:STDOUT: assign %c, %.loc15_47.5 +// CHECK:STDOUT: %.loc15_46.1: {.a: i32, .b: i32} = struct_literal (%.loc15_37, %.loc15_45) +// CHECK:STDOUT: %.loc15_46.2: ref i32 = struct_access %c, member0 +// CHECK:STDOUT: %.loc15_46.3: init i32 = initialize_from %.loc15_37 to %.loc15_46.2 +// CHECK:STDOUT: %.loc15_46.4: ref i32 = struct_access %c, member1 +// CHECK:STDOUT: %.loc15_46.5: init i32 = initialize_from %.loc15_45 to %.loc15_46.4 +// CHECK:STDOUT: %.loc15_46.6: init {.a: i32, .b: i32} = struct_init %.loc15_46.1, (%.loc15_46.3, %.loc15_46.5) +// CHECK:STDOUT: assign %c, %.loc15_46.6 // CHECK:STDOUT: %.loc16_4: ref i32 = struct_access %c, member0 // CHECK:STDOUT: %.loc16_9: i32 = int_literal 3 // CHECK:STDOUT: assign %.loc16_4, %.loc16_9 diff --git a/toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon b/toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon index 814b33d782e1..c26f16fe5f48 100644 --- a/toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon +++ b/toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon @@ -66,13 +66,13 @@ fn Main() { // CHECK:STDOUT: %.loc21_8.2: (i32, i32) = tuple_literal (%.loc21_4, %.loc21_7) // CHECK:STDOUT: %.loc21_13: i32 = int_literal 3 // CHECK:STDOUT: %.loc21_16: i32 = int_literal 4 -// CHECK:STDOUT: %.loc21_17: (i32, i32) = tuple_literal (%.loc21_13, %.loc21_16) -// CHECK:STDOUT: %.loc21_10.1: i32 = tuple_access %.loc21_8.2, member0 -// CHECK:STDOUT: %.loc21_10.2: init i32 = initialize_from %.loc21_13 to %.loc21_10.1 -// CHECK:STDOUT: %.loc21_10.3: i32 = tuple_access %.loc21_8.2, member1 -// CHECK:STDOUT: %.loc21_10.4: init i32 = initialize_from %.loc21_16 to %.loc21_10.3 -// CHECK:STDOUT: %.loc21_10.5: init (i32, i32) = tuple_init %.loc21_17, (%.loc21_10.2, %.loc21_10.4) -// CHECK:STDOUT: assign %.loc21_8.2, %.loc21_10.5 +// CHECK:STDOUT: %.loc21_17.1: (i32, i32) = tuple_literal (%.loc21_13, %.loc21_16) +// CHECK:STDOUT: %.loc21_17.2: i32 = tuple_access %.loc21_8.2, member0 +// CHECK:STDOUT: %.loc21_17.3: init i32 = initialize_from %.loc21_13 to %.loc21_17.2 +// CHECK:STDOUT: %.loc21_17.4: i32 = tuple_access %.loc21_8.2, member1 +// CHECK:STDOUT: %.loc21_17.5: init i32 = initialize_from %.loc21_16 to %.loc21_17.4 +// CHECK:STDOUT: %.loc21_17.6: init (i32, i32) = tuple_init %.loc21_17.1, (%.loc21_17.3, %.loc21_17.5) +// CHECK:STDOUT: assign %.loc21_8.2, %.loc21_17.6 // CHECK:STDOUT: %.loc21_8.3: (i32, i32) = tuple_value %.loc21_8.2, (%.loc21_4, %.loc21_7) // CHECK:STDOUT: %n: ref i32 = var "n" // CHECK:STDOUT: %.loc22_16: i32 = int_literal 0 @@ -80,13 +80,13 @@ fn Main() { // CHECK:STDOUT: %.loc26_8.1: (i32, i32) = tuple_literal (%n, %n) // CHECK:STDOUT: %.loc26_13: i32 = int_literal 1 // CHECK:STDOUT: %.loc26_16: i32 = int_literal 2 -// CHECK:STDOUT: %.loc26_17: (i32, i32) = tuple_literal (%.loc26_13, %.loc26_16) -// CHECK:STDOUT: %.loc26_10.1: i32 = tuple_access %.loc26_8.1, member0 -// CHECK:STDOUT: %.loc26_10.2: init i32 = initialize_from %.loc26_13 to %.loc26_10.1 -// CHECK:STDOUT: %.loc26_10.3: i32 = tuple_access %.loc26_8.1, member1 -// CHECK:STDOUT: %.loc26_10.4: init i32 = initialize_from %.loc26_16 to %.loc26_10.3 -// CHECK:STDOUT: %.loc26_10.5: init (i32, i32) = tuple_init %.loc26_17, (%.loc26_10.2, %.loc26_10.4) -// CHECK:STDOUT: assign %.loc26_8.1, %.loc26_10.5 +// CHECK:STDOUT: %.loc26_17.1: (i32, i32) = tuple_literal (%.loc26_13, %.loc26_16) +// CHECK:STDOUT: %.loc26_17.2: i32 = tuple_access %.loc26_8.1, member0 +// CHECK:STDOUT: %.loc26_17.3: init i32 = initialize_from %.loc26_13 to %.loc26_17.2 +// CHECK:STDOUT: %.loc26_17.4: i32 = tuple_access %.loc26_8.1, member1 +// CHECK:STDOUT: %.loc26_17.5: init i32 = initialize_from %.loc26_16 to %.loc26_17.4 +// CHECK:STDOUT: %.loc26_17.6: init (i32, i32) = tuple_init %.loc26_17.1, (%.loc26_17.3, %.loc26_17.5) +// CHECK:STDOUT: assign %.loc26_8.1, %.loc26_17.6 // CHECK:STDOUT: %.loc22_7.1: i32 = bind_value %n // CHECK:STDOUT: %.loc22_7.2: i32 = bind_value %n // CHECK:STDOUT: %.loc26_8.2: (i32, i32) = tuple_value %.loc26_8.1, (%.loc22_7.1, %.loc22_7.2) @@ -98,13 +98,13 @@ fn Main() { // CHECK:STDOUT: %.loc34_18.2: {.x: i32, .y: i32} = struct_literal (%.loc34_9, %.loc34_17) // CHECK:STDOUT: %.loc34_28: i32 = int_literal 3 // CHECK:STDOUT: %.loc34_36: i32 = int_literal 4 -// CHECK:STDOUT: %.loc34_37: {.x: i32, .y: i32} = struct_literal (%.loc34_28, %.loc34_36) -// CHECK:STDOUT: %.loc34_20.1: i32 = struct_access %.loc34_18.2, member0 -// CHECK:STDOUT: %.loc34_20.2: init i32 = initialize_from %.loc34_28 to %.loc34_20.1 -// CHECK:STDOUT: %.loc34_20.3: i32 = struct_access %.loc34_18.2, member1 -// CHECK:STDOUT: %.loc34_20.4: init i32 = initialize_from %.loc34_36 to %.loc34_20.3 -// CHECK:STDOUT: %.loc34_20.5: init {.x: i32, .y: i32} = struct_init %.loc34_37, (%.loc34_20.2, %.loc34_20.4) -// CHECK:STDOUT: assign %.loc34_18.2, %.loc34_20.5 +// CHECK:STDOUT: %.loc34_37.1: {.x: i32, .y: i32} = struct_literal (%.loc34_28, %.loc34_36) +// CHECK:STDOUT: %.loc34_37.2: i32 = struct_access %.loc34_18.2, member0 +// CHECK:STDOUT: %.loc34_37.3: init i32 = initialize_from %.loc34_28 to %.loc34_37.2 +// CHECK:STDOUT: %.loc34_37.4: i32 = struct_access %.loc34_18.2, member1 +// CHECK:STDOUT: %.loc34_37.5: init i32 = initialize_from %.loc34_36 to %.loc34_37.4 +// CHECK:STDOUT: %.loc34_37.6: init {.x: i32, .y: i32} = struct_init %.loc34_37.1, (%.loc34_37.3, %.loc34_37.5) +// CHECK:STDOUT: assign %.loc34_18.2, %.loc34_37.6 // CHECK:STDOUT: %.loc34_18.3: {.x: i32, .y: i32} = struct_value %.loc34_18.2, (%.loc34_9, %.loc34_17) // CHECK:STDOUT: %.loc38_7: bool = bool_literal true // CHECK:STDOUT: if %.loc38_7 br !if.expr.then.loc38 else br !if.expr.else.loc38 diff --git a/toolchain/check/testdata/pointer/address_of_lvalue.carbon b/toolchain/check/testdata/pointer/address_of_lvalue.carbon index 7c22f0edb2a6..5a4f825771db 100644 --- a/toolchain/check/testdata/pointer/address_of_lvalue.carbon +++ b/toolchain/check/testdata/pointer/address_of_lvalue.carbon @@ -26,13 +26,13 @@ fn F() { // CHECK:STDOUT: %s: ref {.a: i32, .b: i32} = var "s" // CHECK:STDOUT: %.loc8_37: i32 = int_literal 1 // CHECK:STDOUT: %.loc8_45: i32 = int_literal 2 -// CHECK:STDOUT: %.loc8_46: {.a: i32, .b: i32} = struct_literal (%.loc8_37, %.loc8_45) -// CHECK:STDOUT: %.loc8_47.1: ref i32 = struct_access %s, member0 -// CHECK:STDOUT: %.loc8_47.2: init i32 = initialize_from %.loc8_37 to %.loc8_47.1 -// CHECK:STDOUT: %.loc8_47.3: ref i32 = struct_access %s, member1 -// CHECK:STDOUT: %.loc8_47.4: init i32 = initialize_from %.loc8_45 to %.loc8_47.3 -// CHECK:STDOUT: %.loc8_47.5: init {.a: i32, .b: i32} = struct_init %.loc8_46, (%.loc8_47.2, %.loc8_47.4) -// CHECK:STDOUT: assign %s, %.loc8_47.5 +// CHECK:STDOUT: %.loc8_46.1: {.a: i32, .b: i32} = struct_literal (%.loc8_37, %.loc8_45) +// CHECK:STDOUT: %.loc8_46.2: ref i32 = struct_access %s, member0 +// CHECK:STDOUT: %.loc8_46.3: init i32 = initialize_from %.loc8_37 to %.loc8_46.2 +// CHECK:STDOUT: %.loc8_46.4: ref i32 = struct_access %s, member1 +// CHECK:STDOUT: %.loc8_46.5: init i32 = initialize_from %.loc8_45 to %.loc8_46.4 +// CHECK:STDOUT: %.loc8_46.6: init {.a: i32, .b: i32} = struct_init %.loc8_46.1, (%.loc8_46.3, %.loc8_46.5) +// CHECK:STDOUT: assign %s, %.loc8_46.6 // CHECK:STDOUT: %.loc10_27: type = struct_type {.a: i32, .b: i32} // CHECK:STDOUT: %.loc10_28: type = ptr_type {.a: i32, .b: i32} // CHECK:STDOUT: %p: ref {.a: i32, .b: i32}* = var "p" @@ -54,13 +54,13 @@ fn F() { // CHECK:STDOUT: %t: ref (i32, i32) = var "t" // CHECK:STDOUT: %.loc14_24: i32 = int_literal 1 // CHECK:STDOUT: %.loc14_27: i32 = int_literal 2 -// CHECK:STDOUT: %.loc14_28: (i32, i32) = tuple_literal (%.loc14_24, %.loc14_27) -// CHECK:STDOUT: %.loc14_29.1: ref i32 = tuple_access %t, member0 -// CHECK:STDOUT: %.loc14_29.2: init i32 = initialize_from %.loc14_24 to %.loc14_29.1 -// CHECK:STDOUT: %.loc14_29.3: ref i32 = tuple_access %t, member1 -// CHECK:STDOUT: %.loc14_29.4: init i32 = initialize_from %.loc14_27 to %.loc14_29.3 -// CHECK:STDOUT: %.loc14_29.5: init (i32, i32) = tuple_init %.loc14_28, (%.loc14_29.2, %.loc14_29.4) -// CHECK:STDOUT: assign %t, %.loc14_29.5 +// CHECK:STDOUT: %.loc14_28.1: (i32, i32) = tuple_literal (%.loc14_24, %.loc14_27) +// CHECK:STDOUT: %.loc14_28.2: ref i32 = tuple_access %t, member0 +// CHECK:STDOUT: %.loc14_28.3: init i32 = initialize_from %.loc14_24 to %.loc14_28.2 +// CHECK:STDOUT: %.loc14_28.4: ref i32 = tuple_access %t, member1 +// CHECK:STDOUT: %.loc14_28.5: init i32 = initialize_from %.loc14_27 to %.loc14_28.4 +// CHECK:STDOUT: %.loc14_28.6: init (i32, i32) = tuple_init %.loc14_28.1, (%.loc14_28.3, %.loc14_28.5) +// CHECK:STDOUT: assign %t, %.loc14_28.6 // CHECK:STDOUT: %.loc15_14: type = ptr_type i32 // CHECK:STDOUT: %t0: ref i32* = var "t0" // CHECK:STDOUT: %.loc15_21: i32 = int_literal 0 diff --git a/toolchain/check/testdata/return/tuple.carbon b/toolchain/check/testdata/return/tuple.carbon index 21d026aaa0db..d9b7bb9e1717 100644 --- a/toolchain/check/testdata/return/tuple.carbon +++ b/toolchain/check/testdata/return/tuple.carbon @@ -17,11 +17,11 @@ fn Main() -> (i32, i32) { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc9_11: i32 = int_literal 15 // CHECK:STDOUT: %.loc9_15: i32 = int_literal 35 -// CHECK:STDOUT: %.loc9_17: (i32, i32) = tuple_literal (%.loc9_11, %.loc9_15) -// CHECK:STDOUT: %.loc9_18.1: ref i32 = tuple_access %return, member0 -// CHECK:STDOUT: %.loc9_18.2: init i32 = initialize_from %.loc9_11 to %.loc9_18.1 -// CHECK:STDOUT: %.loc9_18.3: ref i32 = tuple_access %return, member1 -// CHECK:STDOUT: %.loc9_18.4: init i32 = initialize_from %.loc9_15 to %.loc9_18.3 -// CHECK:STDOUT: %.loc9_18.5: init (i32, i32) = tuple_init %.loc9_17, (%.loc9_18.2, %.loc9_18.4) -// CHECK:STDOUT: return %.loc9_18.5 +// CHECK:STDOUT: %.loc9_17.1: (i32, i32) = tuple_literal (%.loc9_11, %.loc9_15) +// CHECK:STDOUT: %.loc9_17.2: ref i32 = tuple_access %return, member0 +// CHECK:STDOUT: %.loc9_17.3: init i32 = initialize_from %.loc9_11 to %.loc9_17.2 +// CHECK:STDOUT: %.loc9_17.4: ref i32 = tuple_access %return, member1 +// CHECK:STDOUT: %.loc9_17.5: init i32 = initialize_from %.loc9_15 to %.loc9_17.4 +// CHECK:STDOUT: %.loc9_17.6: init (i32, i32) = tuple_init %.loc9_17.1, (%.loc9_17.3, %.loc9_17.5) +// CHECK:STDOUT: return %.loc9_17.6 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/empty.carbon b/toolchain/check/testdata/struct/empty.carbon index 4caa0d17c966..26af89b07982 100644 --- a/toolchain/check/testdata/struct/empty.carbon +++ b/toolchain/check/testdata/struct/empty.carbon @@ -11,11 +11,11 @@ var y: {} = x; // CHECK:STDOUT: %.loc7_9.1: type = struct_type {} // CHECK:STDOUT: %.loc7_9.2: {} = struct_literal () // CHECK:STDOUT: %x: ref {} = var "x" -// CHECK:STDOUT: %.loc7_14: {} = struct_literal () -// CHECK:STDOUT: %.loc7_15: init {} = struct_init %.loc7_14, () -// CHECK:STDOUT: assign %x, %.loc7_15 +// CHECK:STDOUT: %.loc7_14.1: {} = struct_literal () +// CHECK:STDOUT: %.loc7_14.2: init {} = struct_init %.loc7_14.1, () +// CHECK:STDOUT: assign %x, %.loc7_14.2 // CHECK:STDOUT: %.loc8: {} = struct_literal () // CHECK:STDOUT: %y: ref {} = var "y" -// CHECK:STDOUT: %.loc7_5: {} = bind_value %x +// CHECK:STDOUT: %.loc7_5: init {} = struct_init %x, () // CHECK:STDOUT: assign %y, %.loc7_5 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/fail_assign_empty.carbon b/toolchain/check/testdata/struct/fail_assign_empty.carbon index 621afcf8dd37..8bb50da638f8 100644 --- a/toolchain/check/testdata/struct/fail_assign_empty.carbon +++ b/toolchain/check/testdata/struct/fail_assign_empty.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:22: ERROR: Cannot implicitly convert from `{} as type` to `{.a: i32}`. +// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:21: ERROR: Cannot initialize struct of 1 element(s) from struct with 0 element(s). // CHECK:STDERR: var x: {.a: i32} = {}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {.a: i32} = {}; // CHECK:STDOUT: file "fail_assign_empty.carbon" { diff --git a/toolchain/check/testdata/struct/fail_assign_nested.carbon b/toolchain/check/testdata/struct/fail_assign_nested.carbon index ec418f488663..173e6d51bf13 100644 --- a/toolchain/check/testdata/struct/fail_assign_nested.carbon +++ b/toolchain/check/testdata/struct/fail_assign_nested.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:28: ERROR: Cannot implicitly convert from `{.b: {}}` to `{.a: {}}`. +// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:27: ERROR: Mismatched names for field 1 in struct initialization: source has field name `b`, destination has field name `a`. // CHECK:STDERR: var x: {.a: {}} = {.b = {}}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {.a: {}} = {.b = {}}; // CHECK:STDOUT: file "fail_assign_nested.carbon" { diff --git a/toolchain/check/testdata/struct/fail_assign_to_empty.carbon b/toolchain/check/testdata/struct/fail_assign_to_empty.carbon index b2537ac4d190..0cfde402398b 100644 --- a/toolchain/check/testdata/struct/fail_assign_to_empty.carbon +++ b/toolchain/check/testdata/struct/fail_assign_to_empty.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:21: ERROR: Cannot implicitly convert from `{.a: i32}` to `{} as type`. +// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:20: ERROR: Cannot initialize struct of 0 element(s) from struct with 1 element(s). // CHECK:STDERR: var x: {} = {.a = 1}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {} = {.a = 1}; // CHECK:STDOUT: file "fail_assign_to_empty.carbon" { diff --git a/toolchain/check/testdata/struct/fail_field_name_mismatch.carbon b/toolchain/check/testdata/struct/fail_field_name_mismatch.carbon index 781b0f3b0568..07aabb522269 100644 --- a/toolchain/check/testdata/struct/fail_field_name_mismatch.carbon +++ b/toolchain/check/testdata/struct/fail_field_name_mismatch.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_field_name_mismatch.carbon:[[@LINE+3]]:28: ERROR: Cannot implicitly convert from `{.b: i32}` to `{.a: i32}`. +// CHECK:STDERR: fail_field_name_mismatch.carbon:[[@LINE+3]]:27: ERROR: Mismatched names for field 1 in struct initialization: source has field name `b`, destination has field name `a`. // CHECK:STDERR: var x: {.a: i32} = {.b = 1}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {.a: i32} = {.b = 1}; // CHECK:STDOUT: file "fail_field_name_mismatch.carbon" { diff --git a/toolchain/check/testdata/struct/fail_field_type_mismatch.carbon b/toolchain/check/testdata/struct/fail_field_type_mismatch.carbon index 41f727524016..6712a6861864 100644 --- a/toolchain/check/testdata/struct/fail_field_type_mismatch.carbon +++ b/toolchain/check/testdata/struct/fail_field_type_mismatch.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_field_type_mismatch.carbon:[[@LINE+3]]:30: ERROR: Cannot implicitly convert from `{.b: f64}` to `{.a: i32}`. +// CHECK:STDERR: fail_field_type_mismatch.carbon:[[@LINE+3]]:29: ERROR: Mismatched names for field 1 in struct initialization: source has field name `b`, destination has field name `a`. // CHECK:STDERR: var x: {.a: i32} = {.b = 1.0}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {.a: i32} = {.b = 1.0}; // CHECK:STDOUT: file "fail_field_type_mismatch.carbon" { diff --git a/toolchain/check/testdata/struct/fail_member_access_type.carbon b/toolchain/check/testdata/struct/fail_member_access_type.carbon index b7473830a149..3381eb011fec 100644 --- a/toolchain/check/testdata/struct/fail_member_access_type.carbon +++ b/toolchain/check/testdata/struct/fail_member_access_type.carbon @@ -14,9 +14,9 @@ var y: i32 = x.b; // CHECK:STDOUT: %.loc7_16: type = struct_type {.a: f64} // CHECK:STDOUT: %x: ref {.a: f64} = var "x" // CHECK:STDOUT: %.loc7_26: f64 = real_literal 40e-1 -// CHECK:STDOUT: %.loc7_29: {.a: f64} = struct_literal (%.loc7_26) -// CHECK:STDOUT: %.loc7_30: init {.a: f64} = struct_init %.loc7_29, (%.loc7_26) -// CHECK:STDOUT: assign %x, %.loc7_30 +// CHECK:STDOUT: %.loc7_29.1: {.a: f64} = struct_literal (%.loc7_26) +// CHECK:STDOUT: %.loc7_29.2: init {.a: f64} = struct_init %.loc7_29.1, (%.loc7_26) +// CHECK:STDOUT: assign %x, %.loc7_29.2 // CHECK:STDOUT: %y: ref i32 = var "y" // CHECK:STDOUT: assign %y, // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/fail_non_member_access.carbon b/toolchain/check/testdata/struct/fail_non_member_access.carbon index 8ec6c7b338a7..f8069f584c2f 100644 --- a/toolchain/check/testdata/struct/fail_non_member_access.carbon +++ b/toolchain/check/testdata/struct/fail_non_member_access.carbon @@ -14,9 +14,9 @@ var y: i32 = x.b; // CHECK:STDOUT: %.loc7_16: type = struct_type {.a: i32} // CHECK:STDOUT: %x: ref {.a: i32} = var "x" // CHECK:STDOUT: %.loc7_26: i32 = int_literal 4 -// CHECK:STDOUT: %.loc7_27: {.a: i32} = struct_literal (%.loc7_26) -// CHECK:STDOUT: %.loc7_28: init {.a: i32} = struct_init %.loc7_27, (%.loc7_26) -// CHECK:STDOUT: assign %x, %.loc7_28 +// CHECK:STDOUT: %.loc7_27.1: {.a: i32} = struct_literal (%.loc7_26) +// CHECK:STDOUT: %.loc7_27.2: init {.a: i32} = struct_init %.loc7_27.1, (%.loc7_26) +// CHECK:STDOUT: assign %x, %.loc7_27.2 // CHECK:STDOUT: %y: ref i32 = var "y" // CHECK:STDOUT: assign %y, // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/fail_too_few_values.carbon b/toolchain/check/testdata/struct/fail_too_few_values.carbon index 07a4eba80507..3b95f37778b8 100644 --- a/toolchain/check/testdata/struct/fail_too_few_values.carbon +++ b/toolchain/check/testdata/struct/fail_too_few_values.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:37: ERROR: Cannot implicitly convert from `{.a: i32}` to `{.a: i32, .b: i32}`. +// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:36: ERROR: Cannot initialize struct of 2 element(s) from struct with 1 element(s). // CHECK:STDERR: var x: {.a: i32, .b: i32} = {.a = 1}; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: {.a: i32, .b: i32} = {.a = 1}; // CHECK:STDOUT: file "fail_too_few_values.carbon" { diff --git a/toolchain/check/testdata/struct/literal_member_access.carbon b/toolchain/check/testdata/struct/literal_member_access.carbon new file mode 100644 index 000000000000..ddc7f6ada94f --- /dev/null +++ b/toolchain/check/testdata/struct/literal_member_access.carbon @@ -0,0 +1,40 @@ +// 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 +// +// AUTOUPDATE + +fn G() -> {.x: i32, .y: i32, .z: i32}; + +fn F() -> i32 { + return {.a = 1, .b = G(), .c = 3}.b.y; +} + +// CHECK:STDOUT: file "literal_member_access.carbon" { +// CHECK:STDOUT: %.loc7 = fn_decl @G +// CHECK:STDOUT: %.loc9 = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> %return: {.x: i32, .y: i32, .z: i32}; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc10_16: i32 = int_literal 1 +// CHECK:STDOUT: %.loc10_25.1: ref {.x: i32, .y: i32, .z: i32} = temporary_storage +// CHECK:STDOUT: %.loc10_25.2: init {.x: i32, .y: i32, .z: i32} = call @G() to %.loc10_25.1 +// CHECK:STDOUT: %.loc10_34: i32 = int_literal 3 +// CHECK:STDOUT: %.loc10_35.1: type = struct_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} +// CHECK:STDOUT: %.loc10_35.2: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_literal (%.loc10_16, %.loc10_25.2, %.loc10_34) +// CHECK:STDOUT: %.loc10_25.3: ref {.x: i32, .y: i32, .z: i32} = temporary %.loc10_25.1, %.loc10_25.2 +// CHECK:STDOUT: %.loc10_25.4: ref i32 = struct_access %.loc10_25.3, member0 +// CHECK:STDOUT: %.loc10_25.5: i32 = bind_value %.loc10_25.4 +// CHECK:STDOUT: %.loc10_25.6: ref i32 = struct_access %.loc10_25.3, member1 +// CHECK:STDOUT: %.loc10_25.7: i32 = bind_value %.loc10_25.6 +// CHECK:STDOUT: %.loc10_25.8: ref i32 = struct_access %.loc10_25.3, member2 +// CHECK:STDOUT: %.loc10_25.9: i32 = bind_value %.loc10_25.8 +// CHECK:STDOUT: %.loc10_25.10: {.x: i32, .y: i32, .z: i32} = struct_value %.loc10_25.3, (%.loc10_25.5, %.loc10_25.7, %.loc10_25.9) +// CHECK:STDOUT: %.loc10_35.3: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_value %.loc10_35.2, (%.loc10_16, %.loc10_25.10, %.loc10_34) +// CHECK:STDOUT: %.loc10_36: {.x: i32, .y: i32, .z: i32} = struct_access %.loc10_35.3, member1 +// CHECK:STDOUT: %.loc10_38: i32 = struct_access %.loc10_36, member1 +// CHECK:STDOUT: return %.loc10_38 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/member_access.carbon b/toolchain/check/testdata/struct/member_access.carbon index 75eed1388cf3..395008f3036c 100644 --- a/toolchain/check/testdata/struct/member_access.carbon +++ b/toolchain/check/testdata/struct/member_access.carbon @@ -13,13 +13,13 @@ var z: i32 = y; // CHECK:STDOUT: %x: ref {.a: f64, .b: i32} = var "x" // CHECK:STDOUT: %.loc7_35: f64 = real_literal 0e-1 // CHECK:STDOUT: %.loc7_45: i32 = int_literal 1 -// CHECK:STDOUT: %.loc7_46: {.a: f64, .b: i32} = struct_literal (%.loc7_35, %.loc7_45) -// CHECK:STDOUT: %.loc7_47.1: ref f64 = struct_access %x, member0 -// CHECK:STDOUT: %.loc7_47.2: init f64 = initialize_from %.loc7_35 to %.loc7_47.1 -// CHECK:STDOUT: %.loc7_47.3: ref i32 = struct_access %x, member1 -// CHECK:STDOUT: %.loc7_47.4: init i32 = initialize_from %.loc7_45 to %.loc7_47.3 -// CHECK:STDOUT: %.loc7_47.5: init {.a: f64, .b: i32} = struct_init %.loc7_46, (%.loc7_47.2, %.loc7_47.4) -// CHECK:STDOUT: assign %x, %.loc7_47.5 +// CHECK:STDOUT: %.loc7_46.1: {.a: f64, .b: i32} = struct_literal (%.loc7_35, %.loc7_45) +// CHECK:STDOUT: %.loc7_46.2: ref f64 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_46.3: init f64 = initialize_from %.loc7_35 to %.loc7_46.2 +// CHECK:STDOUT: %.loc7_46.4: ref i32 = struct_access %x, member1 +// CHECK:STDOUT: %.loc7_46.5: init i32 = initialize_from %.loc7_45 to %.loc7_46.4 +// CHECK:STDOUT: %.loc7_46.6: init {.a: f64, .b: i32} = struct_init %.loc7_46.1, (%.loc7_46.3, %.loc7_46.5) +// CHECK:STDOUT: assign %x, %.loc7_46.6 // CHECK:STDOUT: %y: ref i32 = var "y" // CHECK:STDOUT: %.loc8_15.1: ref i32 = struct_access %x, member1 // CHECK:STDOUT: %.loc8_15.2: i32 = bind_value %.loc8_15.1 diff --git a/toolchain/check/testdata/struct/nested_struct_in_place.carbon b/toolchain/check/testdata/struct/nested_struct_in_place.carbon index dba62433fce9..af7464f039f1 100644 --- a/toolchain/check/testdata/struct/nested_struct_in_place.carbon +++ b/toolchain/check/testdata/struct/nested_struct_in_place.carbon @@ -23,12 +23,12 @@ fn G() { // CHECK:STDOUT: %.loc10_50: (type, type, type) = tuple_literal (i32, i32, i32) // CHECK:STDOUT: %.loc10_51: type = struct_type {.a: (i32, i32, i32), .b: (i32, i32, i32)} // CHECK:STDOUT: %v: ref {.a: (i32, i32, i32), .b: (i32, i32, i32)} = var "v" -// CHECK:STDOUT: %.loc10_75.1: ref (i32, i32, i32) = struct_access %v, member0 -// CHECK:STDOUT: %.loc10_62: init (i32, i32, i32) = call @F() to %.loc10_75.1 -// CHECK:STDOUT: %.loc10_75.2: ref (i32, i32, i32) = struct_access %v, member1 -// CHECK:STDOUT: %.loc10_72: init (i32, i32, i32) = call @F() to %.loc10_75.2 -// CHECK:STDOUT: %.loc10_74: {.a: (i32, i32, i32), .b: (i32, i32, i32)} = struct_literal (%.loc10_62, %.loc10_72) -// CHECK:STDOUT: %.loc10_75.3: init {.a: (i32, i32, i32), .b: (i32, i32, i32)} = struct_init %.loc10_74, (%.loc10_62, %.loc10_72) -// CHECK:STDOUT: assign %v, %.loc10_75.3 +// CHECK:STDOUT: %.loc10_74.1: ref (i32, i32, i32) = struct_access %v, member0 +// CHECK:STDOUT: %.loc10_62: init (i32, i32, i32) = call @F() to %.loc10_74.1 +// CHECK:STDOUT: %.loc10_74.2: ref (i32, i32, i32) = struct_access %v, member1 +// CHECK:STDOUT: %.loc10_72: init (i32, i32, i32) = call @F() to %.loc10_74.2 +// CHECK:STDOUT: %.loc10_74.3: {.a: (i32, i32, i32), .b: (i32, i32, i32)} = struct_literal (%.loc10_62, %.loc10_72) +// CHECK:STDOUT: %.loc10_74.4: init {.a: (i32, i32, i32), .b: (i32, i32, i32)} = struct_init %.loc10_74.3, (%.loc10_62, %.loc10_72) +// CHECK:STDOUT: assign %v, %.loc10_74.4 // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/one_entry.carbon b/toolchain/check/testdata/struct/one_entry.carbon index 7f1f60131b33..f532e6c384c9 100644 --- a/toolchain/check/testdata/struct/one_entry.carbon +++ b/toolchain/check/testdata/struct/one_entry.carbon @@ -11,11 +11,13 @@ var y: {.a: i32} = x; // CHECK:STDOUT: %.loc7_16: type = struct_type {.a: i32} // CHECK:STDOUT: %x: ref {.a: i32} = var "x" // CHECK:STDOUT: %.loc7_26: i32 = int_literal 4 -// CHECK:STDOUT: %.loc7_27: {.a: i32} = struct_literal (%.loc7_26) -// CHECK:STDOUT: %.loc7_28: init {.a: i32} = struct_init %.loc7_27, (%.loc7_26) -// CHECK:STDOUT: assign %x, %.loc7_28 +// CHECK:STDOUT: %.loc7_27.1: {.a: i32} = struct_literal (%.loc7_26) +// CHECK:STDOUT: %.loc7_27.2: init {.a: i32} = struct_init %.loc7_27.1, (%.loc7_26) +// CHECK:STDOUT: assign %x, %.loc7_27.2 // CHECK:STDOUT: %.loc8: type = struct_type {.a: i32} // CHECK:STDOUT: %y: ref {.a: i32} = var "y" -// CHECK:STDOUT: %.loc7_5: {.a: i32} = bind_value %x -// CHECK:STDOUT: assign %y, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: init {.a: i32} = struct_init %x, (%.loc7_5.2) +// CHECK:STDOUT: assign %y, %.loc7_5.3 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/tuple_as_element.carbon b/toolchain/check/testdata/struct/tuple_as_element.carbon index 5fe83ab53fe3..486efe82c5b3 100644 --- a/toolchain/check/testdata/struct/tuple_as_element.carbon +++ b/toolchain/check/testdata/struct/tuple_as_element.carbon @@ -15,18 +15,28 @@ var y: {.a: i32, .b: (i32,)} = x; // CHECK:STDOUT: %x: ref {.a: i32, .b: (i32,)} = var "x" // CHECK:STDOUT: %.loc7_38: i32 = int_literal 1 // CHECK:STDOUT: %.loc7_47: i32 = int_literal 2 -// CHECK:STDOUT: %.loc7_49: (i32,) = tuple_literal (%.loc7_47) -// CHECK:STDOUT: %.loc7_50: {.a: i32, .b: (i32,)} = struct_literal (%.loc7_38, %.loc7_49) -// CHECK:STDOUT: %.loc7_51.1: ref i32 = struct_access %x, member0 -// CHECK:STDOUT: %.loc7_51.2: init i32 = initialize_from %.loc7_38 to %.loc7_51.1 -// CHECK:STDOUT: %.loc7_51.3: init (i32,) = tuple_init %.loc7_49, (%.loc7_47) -// CHECK:STDOUT: %.loc7_51.4: ref (i32,) = struct_access %x, member1 -// CHECK:STDOUT: %.loc7_51.5: init (i32,) = initialize_from %.loc7_51.3 to %.loc7_51.4 -// CHECK:STDOUT: %.loc7_51.6: init {.a: i32, .b: (i32,)} = struct_init %.loc7_50, (%.loc7_51.2, %.loc7_51.5) -// CHECK:STDOUT: assign %x, %.loc7_51.6 +// CHECK:STDOUT: %.loc7_49.1: (i32,) = tuple_literal (%.loc7_47) +// CHECK:STDOUT: %.loc7_50.1: {.a: i32, .b: (i32,)} = struct_literal (%.loc7_38, %.loc7_49.1) +// CHECK:STDOUT: %.loc7_50.2: ref i32 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_50.3: init i32 = initialize_from %.loc7_38 to %.loc7_50.2 +// CHECK:STDOUT: %.loc7_49.2: init (i32,) = tuple_init %.loc7_49.1, (%.loc7_47) +// CHECK:STDOUT: %.loc7_50.4: ref (i32,) = struct_access %x, member1 +// CHECK:STDOUT: %.loc7_50.5: init (i32,) = initialize_from %.loc7_49.2 to %.loc7_50.4 +// CHECK:STDOUT: %.loc7_50.6: init {.a: i32, .b: (i32,)} = struct_init %.loc7_50.1, (%.loc7_50.3, %.loc7_50.5) +// CHECK:STDOUT: assign %x, %.loc7_50.6 // CHECK:STDOUT: %.loc8_27: (type,) = tuple_literal (i32) // CHECK:STDOUT: %.loc8_28: type = struct_type {.a: i32, .b: (i32,)} // CHECK:STDOUT: %y: ref {.a: i32, .b: (i32,)} = var "y" -// CHECK:STDOUT: %.loc7_5: {.a: i32, .b: (i32,)} = bind_value %x -// CHECK:STDOUT: assign %y, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: ref i32 = struct_access %y, member0 +// CHECK:STDOUT: %.loc7_5.4: init i32 = initialize_from %.loc7_5.2 to %.loc7_5.3 +// CHECK:STDOUT: %.loc7_5.5: ref (i32,) = struct_access %x, member1 +// CHECK:STDOUT: %.loc7_5.6: ref i32 = tuple_access %.loc7_5.5, member0 +// CHECK:STDOUT: %.loc7_5.7: i32 = bind_value %.loc7_5.6 +// CHECK:STDOUT: %.loc7_5.8: init (i32,) = tuple_init %.loc7_5.5, (%.loc7_5.7) +// CHECK:STDOUT: %.loc7_5.9: ref (i32,) = struct_access %y, member1 +// CHECK:STDOUT: %.loc7_5.10: init (i32,) = initialize_from %.loc7_5.8 to %.loc7_5.9 +// CHECK:STDOUT: %.loc7_5.11: init {.a: i32, .b: (i32,)} = struct_init %x, (%.loc7_5.4, %.loc7_5.10) +// CHECK:STDOUT: assign %y, %.loc7_5.11 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/two_entries.carbon b/toolchain/check/testdata/struct/two_entries.carbon index b368aae0a62b..9450426f24c2 100644 --- a/toolchain/check/testdata/struct/two_entries.carbon +++ b/toolchain/check/testdata/struct/two_entries.carbon @@ -12,15 +12,23 @@ var y: {.a: i32, .b: i32} = x; // CHECK:STDOUT: %x: ref {.a: i32, .b: i32} = var "x" // CHECK:STDOUT: %.loc7_35: i32 = int_literal 1 // CHECK:STDOUT: %.loc7_43: i32 = int_literal 2 -// CHECK:STDOUT: %.loc7_44: {.a: i32, .b: i32} = struct_literal (%.loc7_35, %.loc7_43) -// CHECK:STDOUT: %.loc7_45.1: ref i32 = struct_access %x, member0 -// CHECK:STDOUT: %.loc7_45.2: init i32 = initialize_from %.loc7_35 to %.loc7_45.1 -// CHECK:STDOUT: %.loc7_45.3: ref i32 = struct_access %x, member1 -// CHECK:STDOUT: %.loc7_45.4: init i32 = initialize_from %.loc7_43 to %.loc7_45.3 -// CHECK:STDOUT: %.loc7_45.5: init {.a: i32, .b: i32} = struct_init %.loc7_44, (%.loc7_45.2, %.loc7_45.4) -// CHECK:STDOUT: assign %x, %.loc7_45.5 +// CHECK:STDOUT: %.loc7_44.1: {.a: i32, .b: i32} = struct_literal (%.loc7_35, %.loc7_43) +// CHECK:STDOUT: %.loc7_44.2: ref i32 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_44.3: init i32 = initialize_from %.loc7_35 to %.loc7_44.2 +// CHECK:STDOUT: %.loc7_44.4: ref i32 = struct_access %x, member1 +// CHECK:STDOUT: %.loc7_44.5: init i32 = initialize_from %.loc7_43 to %.loc7_44.4 +// CHECK:STDOUT: %.loc7_44.6: init {.a: i32, .b: i32} = struct_init %.loc7_44.1, (%.loc7_44.3, %.loc7_44.5) +// CHECK:STDOUT: assign %x, %.loc7_44.6 // CHECK:STDOUT: %.loc8: type = struct_type {.a: i32, .b: i32} // CHECK:STDOUT: %y: ref {.a: i32, .b: i32} = var "y" -// CHECK:STDOUT: %.loc7_5: {.a: i32, .b: i32} = bind_value %x -// CHECK:STDOUT: assign %y, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = struct_access %x, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: ref i32 = struct_access %y, member0 +// CHECK:STDOUT: %.loc7_5.4: init i32 = initialize_from %.loc7_5.2 to %.loc7_5.3 +// CHECK:STDOUT: %.loc7_5.5: ref i32 = struct_access %x, member1 +// CHECK:STDOUT: %.loc7_5.6: i32 = bind_value %.loc7_5.5 +// CHECK:STDOUT: %.loc7_5.7: ref i32 = struct_access %y, member1 +// CHECK:STDOUT: %.loc7_5.8: init i32 = initialize_from %.loc7_5.6 to %.loc7_5.7 +// CHECK:STDOUT: %.loc7_5.9: init {.a: i32, .b: i32} = struct_init %x, (%.loc7_5.4, %.loc7_5.8) +// CHECK:STDOUT: assign %y, %.loc7_5.9 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/empty.carbon b/toolchain/check/testdata/tuples/empty.carbon index 9f74854a15f7..96c1cd280e1a 100644 --- a/toolchain/check/testdata/tuples/empty.carbon +++ b/toolchain/check/testdata/tuples/empty.carbon @@ -11,11 +11,11 @@ var y: () = x; // CHECK:STDOUT: %.loc7_9.1: type = tuple_type () // CHECK:STDOUT: %.loc7_9.2: () = tuple_literal () // CHECK:STDOUT: %x: ref () = var "x" -// CHECK:STDOUT: %.loc7_14: () = tuple_literal () -// CHECK:STDOUT: %.loc7_15: init () = tuple_init %.loc7_14, () -// CHECK:STDOUT: assign %x, %.loc7_15 +// CHECK:STDOUT: %.loc7_14.1: () = tuple_literal () +// CHECK:STDOUT: %.loc7_14.2: init () = tuple_init %.loc7_14.1, () +// CHECK:STDOUT: assign %x, %.loc7_14.2 // CHECK:STDOUT: %.loc8: () = tuple_literal () // CHECK:STDOUT: %y: ref () = var "y" -// CHECK:STDOUT: %.loc7_5: () = bind_value %x +// CHECK:STDOUT: %.loc7_5: init () = tuple_init %x, () // CHECK:STDOUT: assign %y, %.loc7_5 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/fail_assign_empty.carbon b/toolchain/check/testdata/tuples/fail_assign_empty.carbon index 29fd977a5698..cbf63a0106a2 100644 --- a/toolchain/check/testdata/tuples/fail_assign_empty.carbon +++ b/toolchain/check/testdata/tuples/fail_assign_empty.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:19: ERROR: Cannot implicitly convert from `() as type` to `(i32,) as type`. +// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:18: ERROR: Cannot initialize tuple of 1 element(s) from tuple with 0 element(s). // CHECK:STDERR: var x: (i32,) = (); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: (i32,) = (); // CHECK:STDOUT: file "fail_assign_empty.carbon" { diff --git a/toolchain/check/testdata/tuples/fail_assign_nested.carbon b/toolchain/check/testdata/tuples/fail_assign_nested.carbon index 390855153697..6f567f1a448d 100644 --- a/toolchain/check/testdata/tuples/fail_assign_nested.carbon +++ b/toolchain/check/testdata/tuples/fail_assign_nested.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:57: ERROR: Cannot implicitly convert from `((i32, i32, i32), (i32, i32, i32)) as type` to `((i32, i32), (i32, i32)) as type`. +// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:44: ERROR: Cannot initialize tuple of 2 element(s) from tuple with 3 element(s). // CHECK:STDERR: var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6)); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6)); // CHECK:STDOUT: file "fail_assign_nested.carbon" { @@ -15,8 +15,8 @@ var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6)); // CHECK:STDOUT: %.loc10_30: (type, type) = tuple_literal (i32, i32) // CHECK:STDOUT: %.loc10_31.1: type = tuple_type ((type, type), (type, type)) // CHECK:STDOUT: %.loc10_31.2: ((type, type), (type, type)) = tuple_literal (%.loc10_18.2, %.loc10_30) -// CHECK:STDOUT: %.loc10_18.3: type = tuple_type (i32, i32) -// CHECK:STDOUT: %.loc10_31.3: type = tuple_type ((i32, i32), (i32, i32)) +// CHECK:STDOUT: %.loc10_31.3: type = tuple_type (i32, i32) +// CHECK:STDOUT: %.loc10_31.4: type = tuple_type ((i32, i32), (i32, i32)) // CHECK:STDOUT: %x: ref ((i32, i32), (i32, i32)) = var "x" // CHECK:STDOUT: %.loc10_37: i32 = int_literal 1 // CHECK:STDOUT: %.loc10_40: i32 = int_literal 2 diff --git a/toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon b/toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon index cc91ac1e5ff8..28d1b9b0c8cc 100644 --- a/toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon +++ b/toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_element_type_mismatch.carbon:[[@LINE+3]]:31: ERROR: Cannot implicitly convert from `(i32, f64) as type` to `(i32, i32) as type`. +// CHECK:STDERR: fail_element_type_mismatch.carbon:[[@LINE+3]]:30: ERROR: Cannot implicitly convert from `f64` to `i32`. // CHECK:STDERR: var x: (i32, i32) = (2, 65.89); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: (i32, i32) = (2, 65.89); // CHECK:STDOUT: file "fail_element_type_mismatch.carbon" { @@ -18,5 +18,7 @@ var x: (i32, i32) = (2, 65.89); // CHECK:STDOUT: %.loc10_25: f64 = real_literal 6589e-2 // CHECK:STDOUT: %.loc10_30.1: type = tuple_type (i32, f64) // CHECK:STDOUT: %.loc10_30.2: (i32, f64) = tuple_literal (%.loc10_22, %.loc10_25) +// CHECK:STDOUT: %.loc10_30.3: ref i32 = tuple_access %x, member0 +// CHECK:STDOUT: %.loc10_30.4: init i32 = initialize_from %.loc10_22 to %.loc10_30.3 // CHECK:STDOUT: assign %x, // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/fail_too_few_element.carbon b/toolchain/check/testdata/tuples/fail_too_few_element.carbon index 7b8d6b375893..bc2f73669c3f 100644 --- a/toolchain/check/testdata/tuples/fail_too_few_element.carbon +++ b/toolchain/check/testdata/tuples/fail_too_few_element.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_too_few_element.carbon:[[@LINE+3]]:26: ERROR: Cannot implicitly convert from `(i32,) as type` to `(i32, i32) as type`. +// CHECK:STDERR: fail_too_few_element.carbon:[[@LINE+3]]:25: ERROR: Cannot initialize tuple of 2 element(s) from tuple with 1 element(s). // CHECK:STDERR: var x: (i32, i32) = (2, ); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: (i32, i32) = (2, ); // CHECK:STDOUT: file "fail_too_few_element.carbon" { diff --git a/toolchain/check/testdata/tuples/fail_type_assign.carbon b/toolchain/check/testdata/tuples/fail_type_assign.carbon index 152b9b3d3120..04b72cb1671b 100644 --- a/toolchain/check/testdata/tuples/fail_type_assign.carbon +++ b/toolchain/check/testdata/tuples/fail_type_assign.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:25: ERROR: Cannot implicitly convert from `(type,) as type` to `(i32,) as type`. +// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:24: ERROR: Cannot implicitly convert from `type` to `i32`. // CHECK:STDERR: var x: (i32, ) = (i32, ); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ var x: (i32, ) = (i32, ); // CHECK:STDOUT: file "fail_type_assign.carbon" { diff --git a/toolchain/check/testdata/tuples/nested_tuple.carbon b/toolchain/check/testdata/tuples/nested_tuple.carbon index c5d1523fa3ad..bf57a4c57cdc 100644 --- a/toolchain/check/testdata/tuples/nested_tuple.carbon +++ b/toolchain/check/testdata/tuples/nested_tuple.carbon @@ -11,22 +11,22 @@ var x: ((i32, i32), i32) = ((12, 76), 6); // CHECK:STDOUT: %.loc7_18.2: (type, type) = tuple_literal (i32, i32) // CHECK:STDOUT: %.loc7_24.1: type = tuple_type ((type, type), type) // CHECK:STDOUT: %.loc7_24.2: ((type, type), type) = tuple_literal (%.loc7_18.2, i32) -// CHECK:STDOUT: %.loc7_18.3: type = tuple_type (i32, i32) -// CHECK:STDOUT: %.loc7_24.3: type = tuple_type ((i32, i32), i32) +// CHECK:STDOUT: %.loc7_24.3: type = tuple_type (i32, i32) +// CHECK:STDOUT: %.loc7_24.4: type = tuple_type ((i32, i32), i32) // CHECK:STDOUT: %x: ref ((i32, i32), i32) = var "x" // CHECK:STDOUT: %.loc7_30: i32 = int_literal 12 // CHECK:STDOUT: %.loc7_34: i32 = int_literal 76 -// CHECK:STDOUT: %.loc7_36: (i32, i32) = tuple_literal (%.loc7_30, %.loc7_34) +// CHECK:STDOUT: %.loc7_36.1: (i32, i32) = tuple_literal (%.loc7_30, %.loc7_34) // CHECK:STDOUT: %.loc7_39: i32 = int_literal 6 -// CHECK:STDOUT: %.loc7_40: ((i32, i32), i32) = tuple_literal (%.loc7_36, %.loc7_39) -// CHECK:STDOUT: %.loc7_41.1: ref (i32, i32) = tuple_access %x, member0 -// CHECK:STDOUT: %.loc7_41.2: ref i32 = tuple_access %.loc7_41.1, member0 -// CHECK:STDOUT: %.loc7_41.3: init i32 = initialize_from %.loc7_30 to %.loc7_41.2 -// CHECK:STDOUT: %.loc7_41.4: ref i32 = tuple_access %.loc7_41.1, member1 -// CHECK:STDOUT: %.loc7_41.5: init i32 = initialize_from %.loc7_34 to %.loc7_41.4 -// CHECK:STDOUT: %.loc7_41.6: init (i32, i32) = tuple_init %.loc7_36, (%.loc7_41.3, %.loc7_41.5) -// CHECK:STDOUT: %.loc7_41.7: ref i32 = tuple_access %x, member1 -// CHECK:STDOUT: %.loc7_41.8: init i32 = initialize_from %.loc7_39 to %.loc7_41.7 -// CHECK:STDOUT: %.loc7_41.9: init ((i32, i32), i32) = tuple_init %.loc7_40, (%.loc7_41.6, %.loc7_41.8) -// CHECK:STDOUT: assign %x, %.loc7_41.9 +// CHECK:STDOUT: %.loc7_40.1: ((i32, i32), i32) = tuple_literal (%.loc7_36.1, %.loc7_39) +// CHECK:STDOUT: %.loc7_40.2: ref (i32, i32) = tuple_access %x, member0 +// CHECK:STDOUT: %.loc7_36.2: ref i32 = tuple_access %.loc7_40.2, member0 +// CHECK:STDOUT: %.loc7_36.3: init i32 = initialize_from %.loc7_30 to %.loc7_36.2 +// CHECK:STDOUT: %.loc7_36.4: ref i32 = tuple_access %.loc7_40.2, member1 +// CHECK:STDOUT: %.loc7_36.5: init i32 = initialize_from %.loc7_34 to %.loc7_36.4 +// CHECK:STDOUT: %.loc7_36.6: init (i32, i32) = tuple_init %.loc7_36.1, (%.loc7_36.3, %.loc7_36.5) +// CHECK:STDOUT: %.loc7_40.3: ref i32 = tuple_access %x, member1 +// CHECK:STDOUT: %.loc7_40.4: init i32 = initialize_from %.loc7_39 to %.loc7_40.3 +// CHECK:STDOUT: %.loc7_40.5: init ((i32, i32), i32) = tuple_init %.loc7_40.1, (%.loc7_36.6, %.loc7_40.4) +// CHECK:STDOUT: assign %x, %.loc7_40.5 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/nested_tuple_in_place.carbon b/toolchain/check/testdata/tuples/nested_tuple_in_place.carbon index 5f4c1f6fd6aa..5bb96c5d968d 100644 --- a/toolchain/check/testdata/tuples/nested_tuple_in_place.carbon +++ b/toolchain/check/testdata/tuples/nested_tuple_in_place.carbon @@ -30,13 +30,13 @@ fn H() { // CHECK:STDOUT: %.loc10_43.2: ((type, type, type), (type, type, type)) = tuple_literal (%.loc10_25, %.loc10_42) // CHECK:STDOUT: %.loc10_43.3: type = tuple_type ((i32, i32, i32), (i32, i32, i32)) // CHECK:STDOUT: %v: ref ((i32, i32, i32), (i32, i32, i32)) = var "v" -// CHECK:STDOUT: %.loc10_57.1: ref (i32, i32, i32) = tuple_access %v, member0 -// CHECK:STDOUT: %.loc10_49: init (i32, i32, i32) = call @F() to %.loc10_57.1 -// CHECK:STDOUT: %.loc10_57.2: ref (i32, i32, i32) = tuple_access %v, member1 -// CHECK:STDOUT: %.loc10_54: init (i32, i32, i32) = call @F() to %.loc10_57.2 -// CHECK:STDOUT: %.loc10_56: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_49, %.loc10_54) -// CHECK:STDOUT: %.loc10_57.3: init ((i32, i32, i32), (i32, i32, i32)) = tuple_init %.loc10_56, (%.loc10_49, %.loc10_54) -// CHECK:STDOUT: assign %v, %.loc10_57.3 +// CHECK:STDOUT: %.loc10_56.1: ref (i32, i32, i32) = tuple_access %v, member0 +// CHECK:STDOUT: %.loc10_49: init (i32, i32, i32) = call @F() to %.loc10_56.1 +// CHECK:STDOUT: %.loc10_56.2: ref (i32, i32, i32) = tuple_access %v, member1 +// CHECK:STDOUT: %.loc10_54: init (i32, i32, i32) = call @F() to %.loc10_56.2 +// CHECK:STDOUT: %.loc10_56.3: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_49, %.loc10_54) +// CHECK:STDOUT: %.loc10_56.4: init ((i32, i32, i32), (i32, i32, i32)) = tuple_init %.loc10_56.3, (%.loc10_49, %.loc10_54) +// CHECK:STDOUT: assign %v, %.loc10_56.4 // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -48,15 +48,15 @@ fn H() { // CHECK:STDOUT: %.loc14_36.3: type = tuple_type (i32, (i32, i32, i32), i32) // CHECK:STDOUT: %v: ref (i32, (i32, i32, i32), i32) = var "v" // CHECK:STDOUT: %.loc14_41: i32 = int_literal 1 -// CHECK:STDOUT: %.loc14_51.1: ref (i32, i32, i32) = tuple_access %v, member1 -// CHECK:STDOUT: %.loc14_45: init (i32, i32, i32) = call @F() to %.loc14_51.1 +// CHECK:STDOUT: %.loc14_50.1: ref (i32, i32, i32) = tuple_access %v, member1 +// CHECK:STDOUT: %.loc14_45: init (i32, i32, i32) = call @F() to %.loc14_50.1 // CHECK:STDOUT: %.loc14_49: i32 = int_literal 2 -// CHECK:STDOUT: %.loc14_50: (i32, (i32, i32, i32), i32) = tuple_literal (%.loc14_41, %.loc14_45, %.loc14_49) -// CHECK:STDOUT: %.loc14_51.2: ref i32 = tuple_access %v, member0 -// CHECK:STDOUT: %.loc14_51.3: init i32 = initialize_from %.loc14_41 to %.loc14_51.2 -// CHECK:STDOUT: %.loc14_51.4: ref i32 = tuple_access %v, member2 -// CHECK:STDOUT: %.loc14_51.5: init i32 = initialize_from %.loc14_49 to %.loc14_51.4 -// CHECK:STDOUT: %.loc14_51.6: init (i32, (i32, i32, i32), i32) = tuple_init %.loc14_50, (%.loc14_51.3, %.loc14_45, %.loc14_51.5) -// CHECK:STDOUT: assign %v, %.loc14_51.6 +// CHECK:STDOUT: %.loc14_50.2: (i32, (i32, i32, i32), i32) = tuple_literal (%.loc14_41, %.loc14_45, %.loc14_49) +// CHECK:STDOUT: %.loc14_50.3: ref i32 = tuple_access %v, member0 +// CHECK:STDOUT: %.loc14_50.4: init i32 = initialize_from %.loc14_41 to %.loc14_50.3 +// CHECK:STDOUT: %.loc14_50.5: ref i32 = tuple_access %v, member2 +// CHECK:STDOUT: %.loc14_50.6: init i32 = initialize_from %.loc14_49 to %.loc14_50.5 +// CHECK:STDOUT: %.loc14_50.7: init (i32, (i32, i32, i32), i32) = tuple_init %.loc14_50.2, (%.loc14_50.4, %.loc14_45, %.loc14_50.6) +// CHECK:STDOUT: assign %v, %.loc14_50.7 // CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/one_element.carbon b/toolchain/check/testdata/tuples/one_element.carbon index 33924adc46be..b12479b1e290 100644 --- a/toolchain/check/testdata/tuples/one_element.carbon +++ b/toolchain/check/testdata/tuples/one_element.carbon @@ -13,11 +13,13 @@ var y: (i32,) = x; // CHECK:STDOUT: %.loc7_13.3: type = tuple_type (i32) // CHECK:STDOUT: %x: ref (i32,) = var "x" // CHECK:STDOUT: %.loc7_18: i32 = int_literal 4 -// CHECK:STDOUT: %.loc7_20: (i32,) = tuple_literal (%.loc7_18) -// CHECK:STDOUT: %.loc7_21: init (i32,) = tuple_init %.loc7_20, (%.loc7_18) -// CHECK:STDOUT: assign %x, %.loc7_21 +// CHECK:STDOUT: %.loc7_20.1: (i32,) = tuple_literal (%.loc7_18) +// CHECK:STDOUT: %.loc7_20.2: init (i32,) = tuple_init %.loc7_20.1, (%.loc7_18) +// CHECK:STDOUT: assign %x, %.loc7_20.2 // CHECK:STDOUT: %.loc8: (type,) = tuple_literal (i32) // CHECK:STDOUT: %y: ref (i32,) = var "y" -// CHECK:STDOUT: %.loc7_5: (i32,) = bind_value %x -// CHECK:STDOUT: assign %y, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = tuple_access %x, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: init (i32,) = tuple_init %x, (%.loc7_5.2) +// CHECK:STDOUT: assign %y, %.loc7_5.3 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/tuples/two_elements.carbon b/toolchain/check/testdata/tuples/two_elements.carbon index 3b62f9c87c95..82fd2f594c88 100644 --- a/toolchain/check/testdata/tuples/two_elements.carbon +++ b/toolchain/check/testdata/tuples/two_elements.carbon @@ -14,15 +14,23 @@ var y: (i32, i32) = x; // CHECK:STDOUT: %x: ref (i32, i32) = var "x" // CHECK:STDOUT: %.loc7_22: i32 = int_literal 4 // CHECK:STDOUT: %.loc7_25: i32 = int_literal 102 -// CHECK:STDOUT: %.loc7_28: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25) -// CHECK:STDOUT: %.loc7_29.1: ref i32 = tuple_access %x, member0 -// CHECK:STDOUT: %.loc7_29.2: init i32 = initialize_from %.loc7_22 to %.loc7_29.1 -// CHECK:STDOUT: %.loc7_29.3: ref i32 = tuple_access %x, member1 -// CHECK:STDOUT: %.loc7_29.4: init i32 = initialize_from %.loc7_25 to %.loc7_29.3 -// CHECK:STDOUT: %.loc7_29.5: init (i32, i32) = tuple_init %.loc7_28, (%.loc7_29.2, %.loc7_29.4) -// CHECK:STDOUT: assign %x, %.loc7_29.5 +// CHECK:STDOUT: %.loc7_28.1: (i32, i32) = tuple_literal (%.loc7_22, %.loc7_25) +// CHECK:STDOUT: %.loc7_28.2: ref i32 = tuple_access %x, member0 +// CHECK:STDOUT: %.loc7_28.3: init i32 = initialize_from %.loc7_22 to %.loc7_28.2 +// CHECK:STDOUT: %.loc7_28.4: ref i32 = tuple_access %x, member1 +// CHECK:STDOUT: %.loc7_28.5: init i32 = initialize_from %.loc7_25 to %.loc7_28.4 +// CHECK:STDOUT: %.loc7_28.6: init (i32, i32) = tuple_init %.loc7_28.1, (%.loc7_28.3, %.loc7_28.5) +// CHECK:STDOUT: assign %x, %.loc7_28.6 // CHECK:STDOUT: %.loc8: (type, type) = tuple_literal (i32, i32) // CHECK:STDOUT: %y: ref (i32, i32) = var "y" -// CHECK:STDOUT: %.loc7_5: (i32, i32) = bind_value %x -// CHECK:STDOUT: assign %y, %.loc7_5 +// CHECK:STDOUT: %.loc7_5.1: ref i32 = tuple_access %x, member0 +// CHECK:STDOUT: %.loc7_5.2: i32 = bind_value %.loc7_5.1 +// CHECK:STDOUT: %.loc7_5.3: ref i32 = tuple_access %y, member0 +// CHECK:STDOUT: %.loc7_5.4: init i32 = initialize_from %.loc7_5.2 to %.loc7_5.3 +// CHECK:STDOUT: %.loc7_5.5: ref i32 = tuple_access %x, member1 +// CHECK:STDOUT: %.loc7_5.6: i32 = bind_value %.loc7_5.5 +// CHECK:STDOUT: %.loc7_5.7: ref i32 = tuple_access %y, member1 +// CHECK:STDOUT: %.loc7_5.8: init i32 = initialize_from %.loc7_5.6 to %.loc7_5.7 +// CHECK:STDOUT: %.loc7_5.9: init (i32, i32) = tuple_init %x, (%.loc7_5.4, %.loc7_5.8) +// CHECK:STDOUT: assign %y, %.loc7_5.9 // CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 270b5c9142ee..f4281f27f7c0 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -125,7 +125,10 @@ CARBON_DIAGNOSTIC_KIND(RepeatedConst) CARBON_DIAGNOSTIC_KIND(InvalidArrayExpression) CARBON_DIAGNOSTIC_KIND(TypeNotIndexable) CARBON_DIAGNOSTIC_KIND(IndexOutOfBounds) +CARBON_DIAGNOSTIC_KIND(StructInitElementCountMismatch) +CARBON_DIAGNOSTIC_KIND(StructInitFieldNameMismatch) CARBON_DIAGNOSTIC_KIND(TupleIndexIntegerLiteral) +CARBON_DIAGNOSTIC_KIND(TupleInitElementCountMismatch) CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression) CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote) CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression) diff --git a/toolchain/lower/testdata/struct/one_entry.carbon b/toolchain/lower/testdata/struct/one_entry.carbon index f815ed492bd9..99f74299ab56 100644 --- a/toolchain/lower/testdata/struct/one_entry.carbon +++ b/toolchain/lower/testdata/struct/one_entry.carbon @@ -17,7 +17,9 @@ fn Run() -> i32 { // CHECK:STDOUT: %x = alloca { i32 }, align 8 // CHECK:STDOUT: store { i32 } { i32 4 }, ptr %x, align 4 // CHECK:STDOUT: %y = alloca { i32 }, align 8 -// CHECK:STDOUT: %1 = load { i32 }, ptr %x, align 4 -// CHECK:STDOUT: store { i32 } %1, ptr %y, align 4 +// CHECK:STDOUT: %a = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %a, align 4 +// CHECK:STDOUT: %2 = insertvalue { i32 } poison, i32 %1, 0 +// CHECK:STDOUT: store { i32 } %2, ptr %y, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/struct/two_entries.carbon b/toolchain/lower/testdata/struct/two_entries.carbon index bee9d133ecb0..80454d956db7 100644 --- a/toolchain/lower/testdata/struct/two_entries.carbon +++ b/toolchain/lower/testdata/struct/two_entries.carbon @@ -20,5 +20,13 @@ fn Run() -> i32 { // CHECK:STDOUT: %b = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1 // CHECK:STDOUT: store i32 2, ptr %b, align 4 // CHECK:STDOUT: %y = alloca { i32, i32 }, align 8 +// CHECK:STDOUT: %a1 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %a1, align 4 +// CHECK:STDOUT: %a2 = getelementptr inbounds { i32, i32 }, ptr %y, i32 0, i32 0 +// CHECK:STDOUT: store i32 %1, ptr %a2, align 4 +// CHECK:STDOUT: %b3 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1 +// CHECK:STDOUT: %2 = load i32, ptr %b3, align 4 +// CHECK:STDOUT: %b4 = getelementptr inbounds { i32, i32 }, ptr %y, i32 0, i32 1 +// CHECK:STDOUT: store i32 %2, ptr %b4, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/tuple/one_entry.carbon b/toolchain/lower/testdata/tuple/one_entry.carbon index 30ccec8b0105..a50218415d74 100644 --- a/toolchain/lower/testdata/tuple/one_entry.carbon +++ b/toolchain/lower/testdata/tuple/one_entry.carbon @@ -17,7 +17,9 @@ fn Run() -> i32 { // CHECK:STDOUT: %x = alloca { i32 }, align 8 // CHECK:STDOUT: store { i32 } { i32 1 }, ptr %x, align 4 // CHECK:STDOUT: %y = alloca { i32 }, align 8 -// CHECK:STDOUT: %1 = load { i32 }, ptr %x, align 4 -// CHECK:STDOUT: store { i32 } %1, ptr %y, align 4 +// CHECK:STDOUT: %tuple.elem = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %tuple.elem, align 4 +// CHECK:STDOUT: %2 = insertvalue { i32 } poison, i32 %1, 0 +// CHECK:STDOUT: store { i32 } %2, ptr %y, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/tuple/two_entries.carbon b/toolchain/lower/testdata/tuple/two_entries.carbon index 6d81dc0989fd..93cf947b14ab 100644 --- a/toolchain/lower/testdata/tuple/two_entries.carbon +++ b/toolchain/lower/testdata/tuple/two_entries.carbon @@ -20,5 +20,13 @@ fn Run() -> i32 { // CHECK:STDOUT: %tuple.elem1 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1 // CHECK:STDOUT: store i32 7, ptr %tuple.elem1, align 4 // CHECK:STDOUT: %y = alloca { i32, i32 }, align 8 +// CHECK:STDOUT: %tuple.elem2 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %tuple.elem2, align 4 +// CHECK:STDOUT: %tuple.elem3 = getelementptr inbounds { i32, i32 }, ptr %y, i32 0, i32 0 +// CHECK:STDOUT: store i32 %1, ptr %tuple.elem3, align 4 +// CHECK:STDOUT: %tuple.elem4 = getelementptr inbounds { i32, i32 }, ptr %x, i32 0, i32 1 +// CHECK:STDOUT: %2 = load i32, ptr %tuple.elem4, align 4 +// CHECK:STDOUT: %tuple.elem5 = getelementptr inbounds { i32, i32 }, ptr %y, i32 0, i32 1 +// CHECK:STDOUT: store i32 %2, ptr %tuple.elem5, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/tuple/value_formation.carbon b/toolchain/lower/testdata/tuple/value_formation.carbon new file mode 100644 index 000000000000..9e12e7e1e859 --- /dev/null +++ b/toolchain/lower/testdata/tuple/value_formation.carbon @@ -0,0 +1,56 @@ +// 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 +// +// AUTOUPDATE + +fn G(ab: ((i32, i32, i32), (i32, i32, i32))); + +fn F() { + var a: (i32, i32, i32); + var b: (i32, i32, i32); + G((a, b)); +} + +// CHECK:STDOUT: ; ModuleID = 'value_formation.carbon' +// CHECK:STDOUT: source_filename = "value_formation.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @G(ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: define void @F() { +// CHECK:STDOUT: %a = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %b = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %tuple.elem, align 4 +// CHECK:STDOUT: %tuple.elem1 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1 +// CHECK:STDOUT: %2 = load i32, ptr %tuple.elem1, align 4 +// CHECK:STDOUT: %tuple.elem2 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2 +// CHECK:STDOUT: %3 = load i32, ptr %tuple.elem2, align 4 +// CHECK:STDOUT: %tuple = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %4 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 0 +// CHECK:STDOUT: store i32 %1, ptr %4, align 4 +// CHECK:STDOUT: %5 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 1 +// CHECK:STDOUT: store i32 %2, ptr %5, align 4 +// CHECK:STDOUT: %6 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 2 +// CHECK:STDOUT: store i32 %3, ptr %6, align 4 +// CHECK:STDOUT: %tuple.elem3 = getelementptr inbounds { i32, i32, i32 }, ptr %b, i32 0, i32 0 +// CHECK:STDOUT: %7 = load i32, ptr %tuple.elem3, align 4 +// CHECK:STDOUT: %tuple.elem4 = getelementptr inbounds { i32, i32, i32 }, ptr %b, i32 0, i32 1 +// CHECK:STDOUT: %8 = load i32, ptr %tuple.elem4, align 4 +// CHECK:STDOUT: %tuple.elem5 = getelementptr inbounds { i32, i32, i32 }, ptr %b, i32 0, i32 2 +// CHECK:STDOUT: %9 = load i32, ptr %tuple.elem5, align 4 +// CHECK:STDOUT: %tuple6 = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %10 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple6, i32 0, i32 0 +// CHECK:STDOUT: store i32 %7, ptr %10, align 4 +// CHECK:STDOUT: %11 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple6, i32 0, i32 1 +// CHECK:STDOUT: store i32 %8, ptr %11, align 4 +// CHECK:STDOUT: %12 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple6, i32 0, i32 2 +// CHECK:STDOUT: store i32 %9, ptr %12, align 4 +// CHECK:STDOUT: %tuple7 = alloca { { i32, i32, i32 }, { i32, i32, i32 } }, align 8 +// CHECK:STDOUT: %13 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32, i32 } }, ptr %tuple7, i32 0, i32 0 +// CHECK:STDOUT: store ptr %tuple, ptr %13, align 8 +// CHECK:STDOUT: %14 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32, i32 } }, ptr %tuple7, i32 0, i32 1 +// CHECK:STDOUT: store ptr %tuple6, ptr %14, align 8 +// CHECK:STDOUT: call void @G(ptr %tuple7) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/tuple/value_forwarding.carbon b/toolchain/lower/testdata/tuple/value_forwarding.carbon new file mode 100644 index 000000000000..5112292a8f65 --- /dev/null +++ b/toolchain/lower/testdata/tuple/value_forwarding.carbon @@ -0,0 +1,26 @@ +// 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 +// +// AUTOUPDATE + +fn G(ab: ((i32, i32, i32), (i32, i32, i32))); + +fn F(a: (i32, i32, i32), b: (i32, i32, i32)) { + G((a, b)); +} + +// CHECK:STDOUT: ; ModuleID = 'value_forwarding.carbon' +// CHECK:STDOUT: source_filename = "value_forwarding.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @G(ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: define void @F(ptr %a, ptr %b) { +// CHECK:STDOUT: %tuple = alloca { { i32, i32, i32 }, { i32, i32, i32 } }, align 8 +// CHECK:STDOUT: %1 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32, i32 } }, ptr %tuple, i32 0, i32 0 +// CHECK:STDOUT: store ptr %a, ptr %1, align 8 +// CHECK:STDOUT: %2 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32, i32 } }, ptr %tuple, i32 0, i32 1 +// CHECK:STDOUT: store ptr %b, ptr %2, align 8 +// CHECK:STDOUT: call void @G(ptr %tuple) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index a0cba077826a..62017df4ac6b 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -192,6 +192,13 @@ class File : public Printable { return id; } + // Adds a node block of the given size. + auto AddUninitializedNodeBlock(size_t size) -> NodeBlockId { + NodeBlockId id(node_blocks_.size()); + node_blocks_.push_back(AllocateUninitialized(size)); + return id; + } + // Returns the requested node block. auto GetNodeBlock(NodeBlockId block_id) const -> llvm::ArrayRef { CARBON_CHECK(block_id != NodeBlockId::Unreachable); @@ -305,16 +312,23 @@ class File : public Printable { auto filename() const -> llvm::StringRef { return filename_; } private: - // Allocates a copy of the given data using our slab allocator. + // Allocates an uninitialized array using our slab allocator. template - auto AllocateCopy(llvm::ArrayRef data) -> llvm::MutableArrayRef { + auto AllocateUninitialized(std::size_t size) -> llvm::MutableArrayRef { // We're not going to run a destructor, so ensure that's OK. static_assert(std::is_trivially_destructible_v); - T* storage = static_cast( - allocator_.Allocate(data.size() * sizeof(T), alignof(T))); - std::uninitialized_copy(data.begin(), data.end(), storage); - return llvm::MutableArrayRef(storage, data.size()); + T* storage = + static_cast(allocator_.Allocate(size * sizeof(T), alignof(T))); + return llvm::MutableArrayRef(storage, size); + } + + // Allocates a copy of the given data using our slab allocator. + template + auto AllocateCopy(llvm::ArrayRef data) -> llvm::MutableArrayRef { + auto result = AllocateUninitialized(data.size()); + std::uninitialized_copy(data.begin(), data.end(), result.begin()); + return result; } bool has_errors_ = false; @@ -390,6 +404,7 @@ enum class ExpressionCategory : int8_t { // and struct literals, where the subexpressions for different elements can // have different categories. Mixed, + Last = Mixed }; // Returns the expression category for a node.