diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def
index 97889bd1fe80..a3a2c3cad9e4 100644
--- a/toolchain/diagnostics/diagnostic_kind.def
+++ b/toolchain/diagnostics/diagnostic_kind.def
@@ -102,6 +102,7 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression)
CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote)
CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression)
CARBON_DIAGNOSTIC_KIND(ReturnStatementTypeMismatch)
+CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure)
// ============================================================================
// Other diagnostics
diff --git a/toolchain/lowering/lowering.cpp b/toolchain/lowering/lowering.cpp
index 1e3bd1689570..7803e379aeeb 100644
--- a/toolchain/lowering/lowering.cpp
+++ b/toolchain/lowering/lowering.cpp
@@ -176,6 +176,26 @@ auto Lowering::HandleStringLiteralNode(SemanticsNodeId /*node_id*/,
CARBON_FATAL() << "TODO: Add support: " << node;
}
+auto Lowering::HandleStructTypeNode(SemanticsNodeId /*node_id*/,
+ SemanticsNode node) -> void {
+ CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto Lowering::HandleStructTypeFieldNode(SemanticsNodeId /*node_id*/,
+ SemanticsNode node) -> void {
+ CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto Lowering::HandleStructValueNode(SemanticsNodeId /*node_id*/,
+ SemanticsNode node) -> void {
+ CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
+auto Lowering::HandleStubReferenceNode(SemanticsNodeId /*node_id*/,
+ SemanticsNode node) -> void {
+ CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
auto Lowering::HandleVarStorageNode(SemanticsNodeId /*node_id*/,
SemanticsNode node) -> void {
CARBON_FATAL() << "TODO: Add support: " << node;
diff --git a/toolchain/semantics/semantics_builtin_kind.def b/toolchain/semantics/semantics_builtin_kind.def
index 036f1c7c29fd..b4eafcaad79a 100644
--- a/toolchain/semantics/semantics_builtin_kind.def
+++ b/toolchain/semantics/semantics_builtin_kind.def
@@ -60,6 +60,12 @@ CARBON_SEMANTICS_BUILTIN_KIND(FloatingPointType, TypeType, "f64")
// The type of string values and String literals.
CARBON_SEMANTICS_BUILTIN_KIND(StringType, TypeType, "String")
+// The canonical empty struct type.
+CARBON_SEMANTICS_BUILTIN_KIND(EmptyStructType, TypeType, "{} as Type")
+
+// The canonical empty struct.
+CARBON_SEMANTICS_BUILTIN_KIND(EmptyStruct, EmptyStructType, "{}")
+
// The canonical empty tuple type.
CARBON_SEMANTICS_BUILTIN_KIND(EmptyTupleType, TypeType, "() as Type")
diff --git a/toolchain/semantics/semantics_ir.cpp b/toolchain/semantics/semantics_ir.cpp
index fa00d3401f7f..5649935e4cd4 100644
--- a/toolchain/semantics/semantics_ir.cpp
+++ b/toolchain/semantics/semantics_ir.cpp
@@ -128,6 +128,23 @@ auto SemanticsIR::StringifyNodeImpl(llvm::raw_ostream& out,
auto node = GetNode(node_id);
switch (node.kind()) {
+ case SemanticsNodeKind::StructType: {
+ out << "{";
+ auto refs = GetNodeBlock(node.GetAsStructType().second);
+ llvm::ListSeparator sep;
+ for (const auto& ref_id : refs) {
+ out << sep;
+ // TODO: Bound recursion depth or remove recursive step.
+ StringifyNodeImpl(out, ref_id);
+ }
+ out << "}";
+ break;
+ }
+ case SemanticsNodeKind::StructTypeField: {
+ out << "." << GetString(node.GetAsStructTypeField()) << ": ";
+ StringifyNodeImpl(out, node.type_id());
+ break;
+ }
case SemanticsNodeKind::Assign:
case SemanticsNodeKind::BinaryOperatorAdd:
case SemanticsNodeKind::BindName:
@@ -142,6 +159,8 @@ auto SemanticsIR::StringifyNodeImpl(llvm::raw_ostream& out,
case SemanticsNodeKind::Return:
case SemanticsNodeKind::ReturnExpression:
case SemanticsNodeKind::StringLiteral:
+ case SemanticsNodeKind::StructValue:
+ case SemanticsNodeKind::StubReference:
case SemanticsNodeKind::VarStorage:
// We don't need to handle stringification for nodes that don't show up in
// errors, but make it clear what's going on so that it's clearer when
diff --git a/toolchain/semantics/semantics_ir.h b/toolchain/semantics/semantics_ir.h
index 70f0b617655b..780053a34b2b 100644
--- a/toolchain/semantics/semantics_ir.h
+++ b/toolchain/semantics/semantics_ir.h
@@ -106,6 +106,12 @@ class SemanticsIR {
return node_blocks_[block_id.index];
}
+ // Returns the requested real literal.
+ auto GetRealLiteral(SemanticsRealLiteralId int_id) const
+ -> const SemanticsRealLiteral& {
+ return real_literals_[int_id.index];
+ }
+
// Returns the requested string.
auto GetString(SemanticsStringId string_id) const -> llvm::StringRef {
return strings_[string_id.index];
diff --git a/toolchain/semantics/semantics_node.h b/toolchain/semantics/semantics_node.h
index 70cdfe8dd6cb..504c51b8eda6 100644
--- a/toolchain/semantics/semantics_node.h
+++ b/toolchain/semantics/semantics_node.h
@@ -299,6 +299,20 @@ class SemanticsNode {
SemanticsBuiltinKind::StringType.AsInt(),
SemanticsStringId /*string_id*/>;
+ using StructType = FactoryPreTyped<
+ SemanticsNodeKind::StructType, SemanticsBuiltinKind::TypeType.AsInt(),
+ SemanticsNodeBlockId /*ir_id*/, SemanticsNodeBlockId /*refs_id*/>;
+
+ using StructTypeField = Factory;
+
+ using StructValue =
+ Factory;
+
+ using StubReference =
+ Factory;
+
using VarStorage = Factory;
SemanticsNode()
diff --git a/toolchain/semantics/semantics_node_kind.def b/toolchain/semantics/semantics_node_kind.def
index 417c199b4003..d9cac417859f 100644
--- a/toolchain/semantics/semantics_node_kind.def
+++ b/toolchain/semantics/semantics_node_kind.def
@@ -33,6 +33,10 @@ CARBON_SEMANTICS_NODE_KIND(RealLiteral)
CARBON_SEMANTICS_NODE_KIND(Return)
CARBON_SEMANTICS_NODE_KIND(ReturnExpression)
CARBON_SEMANTICS_NODE_KIND(StringLiteral)
+CARBON_SEMANTICS_NODE_KIND(StructType)
+CARBON_SEMANTICS_NODE_KIND(StructTypeField)
+CARBON_SEMANTICS_NODE_KIND(StructValue)
+CARBON_SEMANTICS_NODE_KIND(StubReference)
CARBON_SEMANTICS_NODE_KIND(VarStorage)
#undef CARBON_SEMANTICS_NODE_KIND
diff --git a/toolchain/semantics/semantics_node_stack.cpp b/toolchain/semantics/semantics_node_stack.cpp
index 9a3650e7e86e..c3f4bbc19e63 100644
--- a/toolchain/semantics/semantics_node_stack.cpp
+++ b/toolchain/semantics/semantics_node_stack.cpp
@@ -130,16 +130,17 @@ auto SemanticsNodeStack::PopForParseNodeAndNodeId(ParseNodeKind pop_parse_kind)
return {back.parse_node, back.node_id};
}
-auto SemanticsNodeStack::PopForParseNodeAndNameId()
+auto SemanticsNodeStack::PopForParseNodeAndNameId(ParseNodeKind pop_parse_kind)
-> std::pair {
- auto back = PopEntry(ParseNodeKind::PatternBinding);
+ auto back = PopEntry(pop_parse_kind);
RequireValidId(back);
return {back.parse_node, back.name_id};
}
-auto SemanticsNodeStack::PeekForNameId() -> SemanticsStringId {
+auto SemanticsNodeStack::PeekForNameId(ParseNodeKind parse_kind)
+ -> SemanticsStringId {
auto back = stack_.back();
- RequireParseKind(back, ParseNodeKind::PatternBinding);
+ RequireParseKind(back, parse_kind);
RequireValidId(back);
return back.name_id;
}
diff --git a/toolchain/semantics/semantics_node_stack.h b/toolchain/semantics/semantics_node_stack.h
index 5f9ff0a7f93c..1b22eeaf131d 100644
--- a/toolchain/semantics/semantics_node_stack.h
+++ b/toolchain/semantics/semantics_node_stack.h
@@ -45,10 +45,8 @@ class SemanticsNodeStack {
PushEntry({.parse_node = parse_node, .node_id = node_id}, DebugLog::NodeId);
}
- // Pushes a PatternBinding parse tree node onto the stack with its name.
+ // Pushes a parse tree node onto the stack with its name.
auto Push(ParseTree::Node parse_node, SemanticsStringId name_id) -> void {
- CARBON_CHECK(parse_tree_->node_kind(parse_node) ==
- ParseNodeKind::PatternBinding);
PushEntry({.parse_node = parse_node, .name_id = name_id}, DebugLog::NameId);
}
@@ -85,16 +83,14 @@ class SemanticsNodeStack {
auto PopForNodeId() -> SemanticsNodeId;
// Pops the top of the stack and returns the parse_node and name_id.
- // Verifies that the parse_node is a PatternBinding.
- auto PopForParseNodeAndNameId()
+ auto PopForParseNodeAndNameId(ParseNodeKind pop_parse_kind)
-> std::pair;
// Peeks at the parse_node of the top of the stack.
auto PeekParseNode() -> ParseTree::Node { return stack_.back().parse_node; }
// Peeks at the name_id of the top of the stack.
- // Verifies that the parse_node is a PatternBinding.
- auto PeekForNameId() -> SemanticsStringId;
+ auto PeekForNameId(ParseNodeKind parse_kind) -> SemanticsStringId;
// Prints the stack for a stack dump.
auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
@@ -115,8 +111,6 @@ class SemanticsNodeStack {
// is used based on the ParseNodeKind.
union {
SemanticsNodeId node_id;
-
- // Right now name_id is exclusively for PatternBinding, which is enforced.
SemanticsStringId name_id;
};
};
diff --git a/toolchain/semantics/semantics_parse_tree_handler.cpp b/toolchain/semantics/semantics_parse_tree_handler.cpp
index 26e0d90c51bf..1d94d1348b24 100644
--- a/toolchain/semantics/semantics_parse_tree_handler.cpp
+++ b/toolchain/semantics/semantics_parse_tree_handler.cpp
@@ -232,68 +232,123 @@ auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
return true;
}
+auto SemanticsParseTreeHandler::ImplicitAs(ParseTree::Node parse_node,
+ SemanticsNodeId value_id,
+ SemanticsNodeId as_type_id)
+ -> SemanticsNodeId {
+ // 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 == SemanticsNodeId::BuiltinInvalidType ||
+ as_type_id == SemanticsNodeId::BuiltinInvalidType) {
+ return SemanticsNodeId::BuiltinInvalidType;
+ }
+ auto value_type_id = semantics_->GetType(value_id);
+ if (value_type_id == SemanticsNodeId::BuiltinInvalidType) {
+ return SemanticsNodeId::BuiltinInvalidType;
+ }
+
+ // If the type doesn't need to change, we can return the value directly.
+ if (value_type_id == as_type_id) {
+ return value_id;
+ }
+
+ // When converting to a Type, there are some automatic conversions that can be
+ // done.
+ if (as_type_id == SemanticsNodeId::BuiltinTypeType) {
+ if (value_id == SemanticsNodeId::BuiltinEmptyTuple) {
+ return SemanticsNodeId::BuiltinEmptyTupleType;
+ }
+ if (value_id == SemanticsNodeId::BuiltinEmptyStruct) {
+ return SemanticsNodeId::BuiltinEmptyStructType;
+ }
+ }
+
+ auto value_type = semantics_->GetNode(value_type_id);
+ auto as_type = semantics_->GetNode(as_type_id);
+ if (CanImplicitAsStruct(value_type, as_type)) {
+ return value_id;
+ }
+
+ CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error,
+ "Cannot implicitly convert from {0} to {1}.", std::string,
+ std::string);
+ emitter_
+ ->Build(parse_node, ImplicitAsConversionFailure,
+ semantics_->StringifyNode(value_type_id),
+ semantics_->StringifyNode(as_type_id))
+ .Emit();
+ return SemanticsNodeId::BuiltinInvalidType;
+}
+
+auto SemanticsParseTreeHandler::CanImplicitAsStruct(SemanticsNode value_type,
+ SemanticsNode as_type)
+ -> bool {
+ if (value_type.kind() != SemanticsNodeKind::StructType ||
+ as_type.kind() != SemanticsNodeKind::StructType) {
+ return false;
+ }
+ auto value_type_refs =
+ semantics_->GetNodeBlock(value_type.GetAsStructType().second);
+ auto as_type_refs =
+ semantics_->GetNodeBlock(as_type.GetAsStructType().second);
+ if (value_type_refs.size() != as_type_refs.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < static_cast(value_type_refs.size()); ++i) {
+ auto value_type_field = semantics_->GetNode(value_type_refs[i]);
+ auto as_type_field = semantics_->GetNode(as_type_refs[i]);
+ if (value_type_field.type_id() != as_type_field.type_id() ||
+ value_type_field.GetAsStructTypeField() !=
+ as_type_field.GetAsStructTypeField()) {
+ return false;
+ }
+ }
+ return true;
+}
+
auto SemanticsParseTreeHandler::ParamOrArgStart() -> void {
params_or_args_stack_.Push();
node_block_stack_.Push();
}
-auto SemanticsParseTreeHandler::ParamOrArgComma(ParseTree::Node parse_node)
- -> bool {
- node_stack_.Push(parse_node);
-
- // Copy the last node added to the IR block into the params block.
- if (!ParamOrArgSave()) {
- emitter_->Emit(
- parse_node, SemanticsTodo,
- "Should have a param before comma, will need error recovery");
- return false;
- }
-
- return true;
+auto SemanticsParseTreeHandler::ParamOrArgComma(bool for_args) -> void {
+ ParamOrArgSave(for_args);
}
-auto SemanticsParseTreeHandler::ParamOrArgEnd(
- ParseNodeKind start_kind, ParseNodeKind comma_kind,
- std::function on_start)
- -> bool {
- // If there's a node in the IR block that has yet to be added to the params
- // block, add it now.
- ParamOrArgSave();
-
- while (true) {
- auto parse_kind = parse_tree_->node_kind(node_stack_.PeekParseNode());
- if (parse_kind == start_kind) {
- return on_start(node_block_stack_.Pop(), params_or_args_stack_.Pop());
- } else if (parse_kind == comma_kind) {
- node_stack_.PopAndDiscardSoloParseNode(comma_kind);
- } else {
- node_stack_.PopAndIgnore();
- }
+auto SemanticsParseTreeHandler::ParamOrArgEnd(bool for_args,
+ ParseNodeKind start_kind)
+ -> std::pair {
+ if (parse_tree_->node_kind(node_stack_.PeekParseNode()) != start_kind) {
+ ParamOrArgSave(for_args);
}
+ return {node_block_stack_.Pop(), params_or_args_stack_.Pop()};
}
-auto SemanticsParseTreeHandler::ParamOrArgSave() -> bool {
- // Copy the last node added to the IR block into the params block.
- auto ir_id = node_block_stack_.Peek();
- if (!ir_id.is_valid()) {
- return false;
+auto SemanticsParseTreeHandler::ParamOrArgSave(bool for_args) -> void {
+ SemanticsNodeId param_or_arg_id = SemanticsNodeId::Invalid;
+ if (for_args) {
+ // For an argument, we add a stub reference to the expression on the top of
+ // the stack. There may not be anything on the IR prior to this.
+ auto [entry_parse_node, entry_node_id] =
+ node_stack_.PopForParseNodeAndNodeId();
+ param_or_arg_id = AddNode(SemanticsNode::StubReference::Make(
+ entry_parse_node, semantics_->GetNode(entry_node_id).type_id(),
+ entry_node_id));
+ } else {
+ // For a parameter, there should always be something in the IR.
+ node_stack_.PopAndIgnore();
+ auto ir_id = node_block_stack_.Peek();
+ CARBON_CHECK(ir_id.is_valid());
+ auto& ir = semantics_->GetNodeBlock(ir_id);
+ CARBON_CHECK(!ir.empty()) << "Should have had a param";
+ param_or_arg_id = ir.back();
}
- // We get params before ir because it may add a node block, which can
- // invalidate the ir reference.
- auto& params = semantics_->GetNodeBlock(params_or_args_stack_.PeekForAdd());
-
- auto& ir = semantics_->GetNodeBlock(ir_id);
- CARBON_CHECK(!ir.empty())
- << "Should only have a valid ID if a node was added";
- auto& param = ir.back();
-
- if (!params.empty() && param == params.back()) {
- // The param was already added after a comma.
- return false;
- }
- params.push_back(ir.back());
- return true;
+ // Save the param or arg ID.
+ auto& params_or_args =
+ semantics_->GetNodeBlock(params_or_args_stack_.PeekForAdd());
+ params_or_args.push_back(param_or_arg_id);
}
auto SemanticsParseTreeHandler::HandleAddress(ParseTree::Node parse_node)
@@ -316,45 +371,44 @@ auto SemanticsParseTreeHandler::HandleBreakStatementStart(
auto SemanticsParseTreeHandler::HandleCallExpression(ParseTree::Node parse_node)
-> bool {
- auto on_start = [&](SemanticsNodeBlockId ir_id,
- SemanticsNodeBlockId refs_id) -> bool {
- // TODO: Convert to call expression.
- auto [call_expr_parse_node, name_id] = node_stack_.PopForParseNodeAndNodeId(
- ParseNodeKind::CallExpressionStart);
- auto name_node = semantics_->GetNode(name_id);
- if (name_node.kind() != SemanticsNodeKind::FunctionDeclaration) {
- // TODO: Work on error.
- emitter_->Emit(parse_node, SemanticsTodo, "Not a callable name");
- node_stack_.Push(parse_node, name_id);
- return true;
- }
+ auto [ir_id, refs_id] =
+ ParamOrArgEnd(/*for_args=*/true, ParseNodeKind::CallExpressionStart);
- auto [_, callable_id] = name_node.GetAsFunctionDeclaration();
- auto callable = semantics_->GetCallable(callable_id);
-
- if (!TryTypeConversionOnArgs(call_expr_parse_node, ir_id, refs_id,
- name_node.parse_node(),
- callable.param_refs_id)) {
- node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
- return true;
- }
-
- auto call_id = semantics_->AddCall({ir_id, refs_id});
- // TODO: Propagate return types from callable.
- auto call_node_id = AddNode(SemanticsNode::Call::Make(
- call_expr_parse_node, SemanticsNodeId::BuiltinEmptyTuple, call_id,
- callable_id));
-
- node_stack_.Push(parse_node, call_node_id);
+ // TODO: Convert to call expression.
+ auto [call_expr_parse_node, name_id] =
+ node_stack_.PopForParseNodeAndNodeId(ParseNodeKind::CallExpressionStart);
+ auto name_node = semantics_->GetNode(name_id);
+ if (name_node.kind() != SemanticsNodeKind::FunctionDeclaration) {
+ // TODO: Work on error.
+ emitter_->Emit(parse_node, SemanticsTodo, "Not a callable name");
+ node_stack_.Push(parse_node, name_id);
return true;
- };
- return ParamOrArgEnd(ParseNodeKind::CallExpressionStart,
- ParseNodeKind::CallExpressionComma, on_start);
+ }
+
+ auto [_, callable_id] = name_node.GetAsFunctionDeclaration();
+ auto callable = semantics_->GetCallable(callable_id);
+
+ if (!TryTypeConversionOnArgs(call_expr_parse_node, ir_id, refs_id,
+ name_node.parse_node(),
+ callable.param_refs_id)) {
+ node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
+ return true;
+ }
+
+ auto call_id = semantics_->AddCall({ir_id, refs_id});
+ // TODO: Propagate return types from callable.
+ auto call_node_id = AddNode(SemanticsNode::Call::Make(
+ call_expr_parse_node, SemanticsNodeId::BuiltinEmptyTuple, call_id,
+ callable_id));
+
+ node_stack_.Push(parse_node, call_node_id);
+ return true;
}
auto SemanticsParseTreeHandler::HandleCallExpressionComma(
- ParseTree::Node parse_node) -> bool {
- return ParamOrArgComma(parse_node);
+ ParseTree::Node /*parse_node*/) -> bool {
+ ParamOrArgComma(/*for_args=*/true);
+ return true;
}
auto SemanticsParseTreeHandler::HandleCallExpressionStart(
@@ -434,8 +488,11 @@ auto SemanticsParseTreeHandler::HandleDeducedParameterListStart(
auto SemanticsParseTreeHandler::HandleDesignatedName(ParseTree::Node parse_node)
-> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleDesignatedName");
- return false;
+ auto name_str = parse_tree_->GetNodeText(parse_node);
+ auto name_id = semantics_->AddString(name_str);
+ // The parent is responsible for binding the name.
+ node_stack_.Push(parse_node, name_id);
+ return true;
}
auto SemanticsParseTreeHandler::HandleDesignatorExpression(
@@ -782,21 +839,20 @@ auto SemanticsParseTreeHandler::HandlePackageLibrary(ParseTree::Node parse_node)
auto SemanticsParseTreeHandler::HandleParameterList(ParseTree::Node parse_node)
-> bool {
- auto on_start = [&](SemanticsNodeBlockId ir_id,
- SemanticsNodeBlockId refs_id) -> bool {
- PopScope();
- node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
- finished_params_stack_.push_back({ir_id, refs_id});
- node_stack_.Push(parse_node);
- return true;
- };
- return ParamOrArgEnd(ParseNodeKind::ParameterListStart,
- ParseNodeKind::ParameterListComma, on_start);
+ auto [ir_id, refs_id] =
+ ParamOrArgEnd(/*for_args=*/false, ParseNodeKind::ParameterListStart);
+
+ PopScope();
+ node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
+ finished_params_stack_.push_back({ir_id, refs_id});
+ node_stack_.Push(parse_node);
+ return true;
}
auto SemanticsParseTreeHandler::HandleParameterListComma(
- ParseTree::Node parse_node) -> bool {
- return ParamOrArgComma(parse_node);
+ ParseTree::Node /*parse_node*/) -> bool {
+ ParamOrArgComma(/*for_args=*/false);
+ return true;
}
auto SemanticsParseTreeHandler::HandleParameterListStart(
@@ -822,16 +878,19 @@ auto SemanticsParseTreeHandler::HandleParenExpressionOrTupleLiteralStart(
auto SemanticsParseTreeHandler::HandlePatternBinding(ParseTree::Node parse_node)
-> bool {
- auto type = node_stack_.PopForNodeId();
+ auto [type_node, parsed_type] = node_stack_.PopForParseNodeAndNodeId();
+ auto cast_type_id =
+ ImplicitAs(type_node, parsed_type, SemanticsNodeId::BuiltinTypeType);
// Get the name.
auto name_node = node_stack_.PopForSoloParseNode();
// Allocate storage, linked to the name for error locations.
- auto storage_id = AddNode(SemanticsNode::VarStorage::Make(name_node, type));
+ auto storage_id =
+ AddNode(SemanticsNode::VarStorage::Make(name_node, cast_type_id));
// Bind the name to storage.
- auto name_id = BindName(name_node, type, storage_id);
+ auto name_id = BindName(name_node, cast_type_id, storage_id);
// If this node's result is used, it'll be for either the name or the storage
// address. The storage address can be found through the name, so we push the
@@ -939,22 +998,35 @@ auto SemanticsParseTreeHandler::HandleSelfValueIdentifier(
return false;
}
-auto SemanticsParseTreeHandler::HandleStructComma(ParseTree::Node parse_node)
- -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructComma");
- return false;
+auto SemanticsParseTreeHandler::HandleStructComma(
+ ParseTree::Node /*parse_node*/) -> bool {
+ ParamOrArgComma(
+ /*for_args=*/parse_tree_->node_kind(node_stack_.PeekParseNode()) !=
+ ParseNodeKind::StructFieldType);
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldDesignator(
- ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldDesignator");
- return false;
+ ParseTree::Node /*parse_node*/) -> bool {
+ // This leaves the designated name on top because the `.` isn't interesting.
+ CARBON_CHECK(parse_tree_->node_kind(node_stack_.PeekParseNode()) ==
+ ParseNodeKind::DesignatedName);
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldType(
ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldType");
- return false;
+ auto [type_node, type_id] = node_stack_.PopForParseNodeAndNodeId();
+ auto cast_type_id =
+ ImplicitAs(type_node, type_id, SemanticsNodeId::BuiltinTypeType);
+
+ auto [name_node, name_id] =
+ node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
+
+ AddNode(
+ SemanticsNode::StructTypeField::Make(name_node, cast_type_id, name_id));
+ node_stack_.Push(parse_node);
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldUnknown(
@@ -965,27 +1037,81 @@ auto SemanticsParseTreeHandler::HandleStructFieldUnknown(
auto SemanticsParseTreeHandler::HandleStructFieldValue(
ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldValue");
- return false;
+ auto [value_parse_node, value_node_id] =
+ node_stack_.PopForParseNodeAndNodeId();
+ auto [_, name_id] =
+ node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
+
+ // Store the name for the type.
+ auto type_block_id = args_type_info_stack_.PeekForAdd();
+ semantics_->AddNode(
+ type_block_id,
+ SemanticsNode::StructTypeField::Make(
+ parse_node, semantics_->GetNode(value_node_id).type_id(), name_id));
+
+ // Push the value back on the stack as an argument.
+ node_stack_.Push(parse_node, value_node_id);
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructLiteral(ParseTree::Node parse_node)
-> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructLiteral");
- return false;
+ auto [ir_id, refs_id] = ParamOrArgEnd(
+ /*for_args=*/true, ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
+
+ PopScope();
+ node_stack_.PopAndDiscardSoloParseNode(
+ ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
+ auto type_block_id = args_type_info_stack_.Pop();
+
+ // Special-case `{}`.
+ if (refs_id == SemanticsNodeBlockId::Empty) {
+ node_stack_.Push(parse_node, SemanticsNodeId::BuiltinEmptyStruct);
+ return true;
+ }
+
+ // Construct a type for the literal. Each field is one node, so ir_id and
+ // refs_id match.
+ auto refs = semantics_->GetNodeBlock(refs_id);
+ auto type_id = AddNode(SemanticsNode::StructType::Make(
+ parse_node, type_block_id, type_block_id));
+
+ auto value_id = AddNode(
+ SemanticsNode::StructValue::Make(parse_node, type_id, ir_id, refs_id));
+ node_stack_.Push(parse_node, value_id);
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructLiteralOrStructTypeLiteralStart(
ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo,
- "HandleStructLiteralOrStructTypeLiteralStart");
- return false;
+ PushScope();
+ node_stack_.Push(parse_node);
+ // At this point we aren't sure whether this will be a value or type literal,
+ // so we push onto args irrespective. It just won't be used for a type
+ // literal.
+ args_type_info_stack_.Push();
+ ParamOrArgStart();
+ return true;
}
auto SemanticsParseTreeHandler::HandleStructTypeLiteral(
ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleStructTypeLiteral");
- return false;
+ auto [ir_id, refs_id] = ParamOrArgEnd(
+ /*for_args=*/false, ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
+
+ PopScope();
+ node_stack_.PopAndDiscardSoloParseNode(
+ ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
+ // This is only used for value literals.
+ args_type_info_stack_.Pop();
+
+ CARBON_CHECK(refs_id != SemanticsNodeBlockId::Empty)
+ << "{} is handled by StructLiteral.";
+
+ auto type_id =
+ AddNode(SemanticsNode::StructType::Make(parse_node, ir_id, refs_id));
+ node_stack_.Push(parse_node, type_id);
+ return true;
}
auto SemanticsParseTreeHandler::HandleTemplate(ParseTree::Node parse_node)
@@ -1015,16 +1141,17 @@ auto SemanticsParseTreeHandler::HandleVariableDeclaration(
auto storage_id =
node_stack_.PopForNodeId(ParseNodeKind::VariableInitializer);
- auto binding = node_stack_.PopForParseNodeAndNameId();
+ auto binding =
+ node_stack_.PopForParseNodeAndNameId(ParseNodeKind::PatternBinding);
// Restore the name now that the initializer is complete.
ReaddNameToLookup(binding.second, storage_id);
- auto storage_type =
- TryTypeConversion(parse_node, storage_id, last_child.second,
- /*can_convert_lhs=*/false);
- AddNode(SemanticsNode::Assign::Make(parse_node, storage_type, storage_id,
- last_child.second));
+ auto cast_value_id = ImplicitAs(parse_node, last_child.second,
+ semantics_->GetType(storage_id));
+ AddNode(SemanticsNode::Assign::Make(parse_node,
+ semantics_->GetType(cast_value_id),
+ storage_id, cast_value_id));
}
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::VariableIntroducer);
@@ -1046,7 +1173,8 @@ auto SemanticsParseTreeHandler::HandleVariableInitializer(
// restored by `VariableDeclaration`.
// Save the storage ID.
- auto it = name_lookup_.find(node_stack_.PeekForNameId());
+ auto it = name_lookup_.find(
+ node_stack_.PeekForNameId(ParseNodeKind::PatternBinding));
CARBON_CHECK(it != name_lookup_.end());
CARBON_CHECK(!it->second.empty());
auto storage_id = it->second.back();
diff --git a/toolchain/semantics/semantics_parse_tree_handler.h b/toolchain/semantics/semantics_parse_tree_handler.h
index 20b0ed73488b..02f89fdbfe3a 100644
--- a/toolchain/semantics/semantics_parse_tree_handler.h
+++ b/toolchain/semantics/semantics_parse_tree_handler.h
@@ -33,6 +33,8 @@ class SemanticsParseTreeHandler {
node_block_stack_("node_block_stack_", semantics.node_blocks_,
vlog_stream),
params_or_args_stack_("params_or_args_stack_", semantics.node_blocks_,
+ vlog_stream),
+ args_type_info_stack_("args_type_info_stack_", semantics.node_blocks_,
vlog_stream) {}
// Outputs the ParseTree information into SemanticsIR.
@@ -127,16 +129,37 @@ class SemanticsParseTreeHandler {
ParseTree::Node param_parse_node,
SemanticsNodeBlockId param_refs_id) -> bool;
- auto ParamOrArgStart() -> void;
- auto ParamOrArgComma(ParseTree::Node parse_node) -> bool;
- auto ParamOrArgEnd(
- ParseNodeKind start_kind, ParseNodeKind comma_kind,
- std::function on_start)
+ // Runs ImplicitAs behavior to convert `value` to `as_type`, returning the
+ // result type. The result will be the node to use to replace `value`. The
+ // result will be BuiltinInvalidType for errors; this handles printing
+ // diagnostics.
+ auto ImplicitAs(ParseTree::Node parse_node, SemanticsNodeId value,
+ SemanticsNodeId as_type) -> SemanticsNodeId;
+
+ // Returns true if the ImplicitAs can use struct conversion.
+ // TODO: This currently only supports struct types that precisely match.
+ auto CanImplicitAsStruct(SemanticsNode value_type, SemanticsNode as_type)
-> bool;
+ // Starts handling parameters or arguments.
+ auto ParamOrArgStart() -> void;
+
+ // On a comma, pushes the entry. On return, the top of node_stack_ will be
+ // start_kind.
+ auto ParamOrArgComma(bool for_args) -> void;
+
+ // Detects whether there's an entry to push. On return, the top of
+ // node_stack_ will be start_kind, and the caller should do type-specific
+ // processing. Returns a pair of {ir_id, refs_id}.
+ auto ParamOrArgEnd(bool for_args, ParseNodeKind start_kind)
+ -> std::pair;
+
// Saves a parameter from the top block in node_stack_ to the top block in
- // params_or_args_stack_. Returns false if nothing is copied.
- auto ParamOrArgSave() -> bool;
+ // params_or_args_stack_. If for_args, adds a StubReference of the previous
+ // node's result to the IR.
+ //
+ // This should only be called by other ParamOrArg functions, not directly.
+ auto ParamOrArgSave(bool for_args) -> void;
// Parse node handlers. Returns false for unrecoverable errors.
#define CARBON_PARSE_NODE_KIND(Name) \
@@ -172,6 +195,12 @@ class SemanticsParseTreeHandler {
// node in blocks here.
SemanticsNodeBlockStack params_or_args_stack_;
+ // The stack of node blocks being used for type information while processing
+ // arguments. This is used in parallel with params_or_args_stack_. It's
+ // currently only used for struct literals, where we need to track names
+ // for a type separate from the literal arguments.
+ SemanticsNodeBlockStack args_type_info_stack_;
+
// Completed parameters that are held temporarily on a side-channel for a
// function. This can't use node_stack_ because it has space for only one
// value, whereas parameters return two values.
diff --git a/toolchain/semantics/testdata/basics/builtin_nodes.carbon b/toolchain/semantics/testdata/basics/builtin_nodes.carbon
index 2ba79471f120..b55ff2042351 100644
--- a/toolchain/semantics/testdata/basics/builtin_nodes.carbon
+++ b/toolchain/semantics/testdata/basics/builtin_nodes.carbon
@@ -22,6 +22,8 @@
// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeIntegerType, type: nodeTypeType},
// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeFloatingPointType, type: nodeTypeType},
// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeStringType, type: nodeTypeType},
+// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeEmptyStructType, type: nodeTypeType},
+// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeEmptyStruct, type: nodeEmptyStructType},
// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeEmptyTupleType, type: nodeTypeType},
// CHECK:STDOUT: {kind: CrossReference, arg0: ir0, arg1: nodeEmptyTuple, type: nodeEmptyTupleType},
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/function/call/empty_struct.carbon b/toolchain/semantics/testdata/function/call/empty_struct.carbon
new file mode 100644
index 000000000000..482a7629c151
--- /dev/null
+++ b/toolchain/semantics/testdata/function/call/empty_struct.carbon
@@ -0,0 +1,66 @@
+// 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
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: {arg_ir: block4, arg_refs: block5},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: {param_ir: block1, param_refs: block2},
+// CHECK:STDOUT: {param_ir: block0, param_refs: block0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: Echo,
+// CHECK:STDOUT: Main,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: VarStorage, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: BindName, arg0: str0, arg1: node+0, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str1, arg1: callable0},
+// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+2, arg1: block0},
+// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str2, arg1: callable1},
+// CHECK:STDOUT: {kind: StubReference, arg0: nodeEmptyStruct, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
+// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block6},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+fn Echo(a: {}) {
+}
+
+fn Main() {
+ Echo({});
+}
diff --git a/toolchain/semantics/testdata/function/call/fail_param_count.carbon b/toolchain/semantics/testdata/function/call/fail_param_count.carbon
index 8e9ad2a55d0d..59113d92e23a 100644
--- a/toolchain/semantics/testdata/function/call/fail_param_count.carbon
+++ b/toolchain/semantics/testdata/function/call/fail_param_count.carbon
@@ -46,11 +46,17 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+10, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str5, arg1: callable3},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+13, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+15, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int2, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+17, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int3, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+19, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int4, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+21, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int5, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+23, type: nodeIntegerType},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+12, arg1: block0},
// CHECK:STDOUT: ]
// CHECK:STDOUT: node_blocks: [
@@ -64,7 +70,7 @@
// CHECK:STDOUT: node+10,
// CHECK:STDOUT: node+11,
// CHECK:STDOUT: node+12,
-// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: node+25,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+2,
@@ -85,32 +91,38 @@
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+13,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+14,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+14,
-// CHECK:STDOUT: node+15,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+14,
// CHECK:STDOUT: node+15,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
// CHECK:STDOUT: node+16,
// CHECK:STDOUT: node+17,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+16,
-// CHECK:STDOUT: node+17,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
// CHECK:STDOUT: node+18,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
+// CHECK:STDOUT: node+16,
// CHECK:STDOUT: node+18,
// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: node+20,
+// CHECK:STDOUT: node+21,
+// CHECK:STDOUT: node+22,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+20,
+// CHECK:STDOUT: node+22,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+23,
+// CHECK:STDOUT: node+24,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+24,
+// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
fn Run0() {}
diff --git a/toolchain/semantics/testdata/function/call/fail_param_type.carbon b/toolchain/semantics/testdata/function/call/fail_param_type.carbon
index 119243483251..1e172265cd4e 100644
--- a/toolchain/semantics/testdata/function/call/fail_param_type.carbon
+++ b/toolchain/semantics/testdata/function/call/fail_param_type.carbon
@@ -28,6 +28,7 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+2, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str2, arg1: callable1},
// CHECK:STDOUT: {kind: RealLiteral, arg0: real0, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeFloatingPointType},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block0},
// CHECK:STDOUT: ]
// CHECK:STDOUT: node_blocks: [
@@ -44,13 +45,14 @@
// CHECK:STDOUT: node+2,
// CHECK:STDOUT: node+3,
// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
// CHECK:STDOUT: node+6,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+5,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/function/call/more_param_ir.carbon b/toolchain/semantics/testdata/function/call/more_param_ir.carbon
index 08cb2b1209b8..db24fee8b340 100644
--- a/toolchain/semantics/testdata/function/call/more_param_ir.carbon
+++ b/toolchain/semantics/testdata/function/call/more_param_ir.carbon
@@ -44,10 +44,13 @@
// CHECK:STDOUT: {kind: BinaryOperatorAdd, arg0: node+9, arg1: node+10, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int2, type: nodeIntegerType},
// CHECK:STDOUT: {kind: BinaryOperatorAdd, arg0: node+11, arg1: node+12, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+13, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int3, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int4, type: nodeIntegerType},
-// CHECK:STDOUT: {kind: BinaryOperatorAdd, arg0: node+14, arg1: node+15, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BinaryOperatorAdd, arg0: node+15, arg1: node+16, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+17, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int5, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+19, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+8, arg1: block6},
// CHECK:STDOUT: ]
@@ -71,7 +74,7 @@
// CHECK:STDOUT: node+6,
// CHECK:STDOUT: node+7,
// CHECK:STDOUT: node+8,
-// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: node+22,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+9,
@@ -83,14 +86,17 @@
// CHECK:STDOUT: node+15,
// CHECK:STDOUT: node+16,
// CHECK:STDOUT: node+17,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+13,
-// CHECK:STDOUT: node+16,
-// CHECK:STDOUT: node+17,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
// CHECK:STDOUT: node+18,
+// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: node+20,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+14,
+// CHECK:STDOUT: node+18,
+// CHECK:STDOUT: node+20,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+21,
// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/function/call/params_one.carbon b/toolchain/semantics/testdata/function/call/params_one.carbon
index eebc96cfe3d9..51369a24c5d7 100644
--- a/toolchain/semantics/testdata/function/call/params_one.carbon
+++ b/toolchain/semantics/testdata/function/call/params_one.carbon
@@ -29,6 +29,7 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+2, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str2, arg1: callable1},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block6},
// CHECK:STDOUT: ]
@@ -46,17 +47,18 @@
// CHECK:STDOUT: node+2,
// CHECK:STDOUT: node+3,
// CHECK:STDOUT: node+4,
-// CHECK:STDOUT: node+7,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+8,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+6,
// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
fn Foo(a: i32) {}
diff --git a/toolchain/semantics/testdata/function/call/params_one_comma.carbon b/toolchain/semantics/testdata/function/call/params_one_comma.carbon
index 89a0c3aaa07c..b2fad16c30a9 100644
--- a/toolchain/semantics/testdata/function/call/params_one_comma.carbon
+++ b/toolchain/semantics/testdata/function/call/params_one_comma.carbon
@@ -31,8 +31,10 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+2, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str2, arg1: callable1},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+8, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call1, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block6},
// CHECK:STDOUT: ]
@@ -50,23 +52,25 @@
// CHECK:STDOUT: node+2,
// CHECK:STDOUT: node+3,
// CHECK:STDOUT: node+4,
-// CHECK:STDOUT: node+9,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+11,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+7,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+9,
// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/function/call/params_two.carbon b/toolchain/semantics/testdata/function/call/params_two.carbon
index f9f004dd56a8..cb9c1ae34499 100644
--- a/toolchain/semantics/testdata/function/call/params_two.carbon
+++ b/toolchain/semantics/testdata/function/call/params_two.carbon
@@ -33,7 +33,9 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str3, arg1: callable1},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+7, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+9, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+6, arg1: block6},
// CHECK:STDOUT: ]
@@ -54,18 +56,20 @@
// CHECK:STDOUT: node+4,
// CHECK:STDOUT: node+5,
// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: node+12,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
// CHECK:STDOUT: node+10,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+7,
// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+10,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+7,
-// CHECK:STDOUT: node+8,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: node+11,
// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/function/call/params_two_comma.carbon b/toolchain/semantics/testdata/function/call/params_two_comma.carbon
index 85ef89fb6df8..c5c2dc0251ee 100644
--- a/toolchain/semantics/testdata/function/call/params_two_comma.carbon
+++ b/toolchain/semantics/testdata/function/call/params_two_comma.carbon
@@ -36,10 +36,14 @@
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+4, arg1: block0},
// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str3, arg1: callable1},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+7, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+9, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call0, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int2, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+12, type: nodeIntegerType},
// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int3, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+14, type: nodeIntegerType},
// CHECK:STDOUT: {kind: Call, arg0: call1, arg1: callable0, type: nodeEmptyTuple},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+6, arg1: block6},
// CHECK:STDOUT: ]
@@ -60,27 +64,31 @@
// CHECK:STDOUT: node+4,
// CHECK:STDOUT: node+5,
// CHECK:STDOUT: node+6,
-// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+17,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
// CHECK:STDOUT: node+7,
// CHECK:STDOUT: node+8,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+7,
-// CHECK:STDOUT: node+8,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+16,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
// CHECK:STDOUT: node+12,
+// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+14,
+// CHECK:STDOUT: node+15,
// CHECK:STDOUT: ],
// CHECK:STDOUT: [
-// CHECK:STDOUT: node+10,
-// CHECK:STDOUT: node+11,
-// CHECK:STDOUT: ],
-// CHECK:STDOUT: [
-// CHECK:STDOUT: node+10,
-// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+15,
// CHECK:STDOUT: ],
// CHECK:STDOUT: ]
diff --git a/toolchain/semantics/testdata/struct/empty.carbon b/toolchain/semantics/testdata/struct/empty.carbon
new file mode 100644
index 000000000000..765efd01ae91
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/empty.carbon
@@ -0,0 +1,42 @@
+// 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
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: VarStorage, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: BindName, arg0: str0, arg1: node+0, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+0, arg1: nodeEmptyStruct, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+3, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+3, arg1: node+0, type: nodeEmptyStructType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+var x: {} = {};
+var y: {} = x;
diff --git a/toolchain/semantics/testdata/struct/fail_assign_empty.carbon b/toolchain/semantics/testdata/struct/fail_assign_empty.carbon
new file mode 100644
index 000000000000..9191d55bc32d
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_assign_empty.carbon
@@ -0,0 +1,45 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+1},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: node+1},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_assign_empty.carbon:[[@LINE+1]]:22: Cannot implicitly convert from {} as Type to {.a: i32}.
+var x: {.a: i32} = {};
diff --git a/toolchain/semantics/testdata/struct/fail_assign_to_empty.carbon b/toolchain/semantics/testdata/struct/fail_assign_to_empty.carbon
new file mode 100644
index 000000000000..a2c8c5687e42
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_assign_to_empty.carbon
@@ -0,0 +1,54 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: VarStorage, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: BindName, arg0: str0, arg1: node+0, type: nodeEmptyStructType},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+2, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block3, arg1: block3, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block2, arg1: block4, type: node+5},
+// CHECK:STDOUT: {kind: Assign, arg0: node+0, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_assign_to_empty.carbon:[[@LINE+1]]:21: Cannot implicitly convert from {.a: i32} to {} as Type.
+var x: {} = {.a = 1};
diff --git a/toolchain/semantics/testdata/struct/fail_field_name_mismatch.carbon b/toolchain/semantics/testdata/struct/fail_field_name_mismatch.carbon
new file mode 100644
index 000000000000..070080b5cf16
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_field_name_mismatch.carbon
@@ -0,0 +1,64 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+1},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: node+1},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str2, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+4, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block5, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block4, arg1: block6, type: node+7},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_field_name_mismatch.carbon:[[@LINE+1]]:28: Cannot implicitly convert from {.b: i32} to {.a: i32}.
+var x: {.a: i32} = {.b = 1};
diff --git a/toolchain/semantics/testdata/struct/fail_field_type_mismatch.carbon b/toolchain/semantics/testdata/struct/fail_field_type_mismatch.carbon
new file mode 100644
index 000000000000..c326bd11233d
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_field_type_mismatch.carbon
@@ -0,0 +1,64 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: {mantissa: 10, exponent: -1, is_decimal: 1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+1},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: node+1},
+// CHECK:STDOUT: {kind: RealLiteral, arg0: real0, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str2, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+4, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StructType, arg0: block5, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block4, arg1: block6, type: node+7},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_field_type_mismatch.carbon:[[@LINE+1]]:30: Cannot implicitly convert from {.b: f64} to {.a: i32}.
+var x: {.a: i32} = {.b = 1.0};
diff --git a/toolchain/semantics/testdata/struct/fail_too_few_values.carbon b/toolchain/semantics/testdata/struct/fail_too_few_values.carbon
new file mode 100644
index 000000000000..28443ed8482e
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_too_few_values.carbon
@@ -0,0 +1,67 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+2},
+// CHECK:STDOUT: {kind: BindName, arg0: str2, arg1: node+3, type: node+2},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block5, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block4, arg1: block6, type: node+8},
+// CHECK:STDOUT: {kind: Assign, arg0: node+3, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_too_few_values.carbon:[[@LINE+1]]:37: Cannot implicitly convert from {.a: i32} to {.a: i32, .b: i32}.
+var x: {.a: i32, .b: i32} = {.a = 1};
diff --git a/toolchain/semantics/testdata/struct/fail_type_assign.carbon b/toolchain/semantics/testdata/struct/fail_type_assign.carbon
new file mode 100644
index 000000000000..944f4d79233a
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_type_assign.carbon
@@ -0,0 +1,54 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+1},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: node+1},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block4, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_type_assign.carbon:[[@LINE+1]]:29: Cannot implicitly convert from Type to {.a: i32}.
+var x: {.a: i32} = {.a: i32};
diff --git a/toolchain/semantics/testdata/struct/fail_value_as_type.carbon b/toolchain/semantics/testdata/struct/fail_value_as_type.carbon
new file mode 100644
index 000000000000..361e19a55bff
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_value_as_type.carbon
@@ -0,0 +1,52 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block2, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block1, arg1: block3, type: node+3},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeInvalidType},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+5, type: nodeInvalidType},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_value_as_type.carbon:[[@LINE+1]]:15: Cannot implicitly convert from {.a: i32} to Type.
+var x: {.a = 1};
diff --git a/toolchain/semantics/testdata/struct/one_entry.carbon b/toolchain/semantics/testdata/struct/one_entry.carbon
new file mode 100644
index 000000000000..e48f76eccb47
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/one_entry.carbon
@@ -0,0 +1,79 @@
+// 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
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 4,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+1},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: node+1},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+4, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block5, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block4, arg1: block6, type: node+7},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: node+8, type: node+7},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block7, arg1: block8, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+11},
+// CHECK:STDOUT: {kind: BindName, arg0: str2, arg1: node+12, type: node+11},
+// CHECK:STDOUT: {kind: Assign, arg0: node+12, arg1: node+2, type: node+1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+12,
+// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+14,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+var x: {.a: i32} = {.a = 4};
+var y: {.a: i32} = x;
diff --git a/toolchain/semantics/testdata/struct/two_entries.carbon b/toolchain/semantics/testdata/struct/two_entries.carbon
new file mode 100644
index 000000000000..20e4fdb0df09
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/two_entries.carbon
@@ -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
+//
+// AUTOUPDATE
+// RUN: %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: 2,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block1, arg1: block2, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+2},
+// CHECK:STDOUT: {kind: BindName, arg0: str2, arg1: node+3, type: node+2},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+8, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block5, arg1: block5, type: nodeTypeType},
+// CHECK:STDOUT: {kind: StructValue, arg0: block4, arg1: block6, type: node+11},
+// CHECK:STDOUT: {kind: Assign, arg0: node+3, arg1: node+12, type: node+11},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructType, arg0: block7, arg1: block8, type: nodeTypeType},
+// CHECK:STDOUT: {kind: VarStorage, type: node+16},
+// CHECK:STDOUT: {kind: BindName, arg0: str3, arg1: node+17, type: node+16},
+// CHECK:STDOUT: {kind: Assign, arg0: node+17, arg1: node+3, type: node+2},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+12,
+// CHECK:STDOUT: node+13,
+// CHECK:STDOUT: node+16,
+// CHECK:STDOUT: node+17,
+// CHECK:STDOUT: node+18,
+// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+8,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: node+9,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+7,
+// CHECK:STDOUT: node+10,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+14,
+// CHECK:STDOUT: node+15,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+14,
+// CHECK:STDOUT: node+15,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+var x: {.a: i32, .b: i32} = {.a = 1, .b = 2};
+var y: {.a: i32, .b: i32} = x;
diff --git a/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon b/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon
index 2e31f5a1d640..4103e902c164 100644
--- a/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon
+++ b/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon
@@ -24,7 +24,7 @@
// CHECK:STDOUT: {kind: VarStorage, type: nodeIntegerType},
// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+1, type: nodeIntegerType},
// CHECK:STDOUT: {kind: RealLiteral, arg0: real0, type: nodeFloatingPointType},
-// CHECK:STDOUT: {kind: Assign, arg0: node+1, arg1: node+3, type: nodeInvalidType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+1, arg1: nodeInvalidType, type: nodeInvalidType},
// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+0, arg1: block2},
// CHECK:STDOUT: ]
// CHECK:STDOUT: node_blocks: [
@@ -43,6 +43,6 @@
// CHECK:STDOUT: ]
fn Main() {
- // CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon:[[@LINE+1]]:19: Type mismatch: lhs is i32, rhs is f64
+ // CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/var/fail_init_type_mismatch.carbon:[[@LINE+1]]:19: Cannot implicitly convert from f64 to i32.
var x: i32 = 1.0;
}
diff --git a/toolchain/semantics/testdata/var/fail_storage_is_literal.carbon b/toolchain/semantics/testdata/var/fail_storage_is_literal.carbon
new file mode 100644
index 000000000000..79086593e645
--- /dev/null
+++ b/toolchain/semantics/testdata/var/fail_storage_is_literal.carbon
@@ -0,0 +1,51 @@
+// 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
+// RUN: %{not} %{carbon-run-semantics}
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: calls: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: callables: [
+// CHECK:STDOUT: {param_ir: block0, param_refs: block0},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: 1,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: Main,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: str0, arg1: callable0},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeInvalidType},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: nodeInvalidType},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+2, arg1: nodeInvalidType, type: nodeInvalidType},
+// CHECK:STDOUT: {kind: FunctionDefinition, arg0: node+0, arg1: block2},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT: [
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+0,
+// CHECK:STDOUT: node+6,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: [
+// CHECK:STDOUT: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: node+5,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+fn Main() {
+ // CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/var/fail_storage_is_literal.carbon:[[@LINE+1]]:10: Cannot implicitly convert from i32 to Type.
+ var x: 1 = 1;
+}