diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def
index a3a2c3cad9e4..d225bfed2791 100644
--- a/toolchain/diagnostics/diagnostic_kind.def
+++ b/toolchain/diagnostics/diagnostic_kind.def
@@ -103,6 +103,8 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote)
CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression)
CARBON_DIAGNOSTIC_KIND(ReturnStatementTypeMismatch)
CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure)
+CARBON_DIAGNOSTIC_KIND(DesignatorExpressionUnsupported)
+CARBON_DIAGNOSTIC_KIND(DesignatorExpressionNameNotFound)
// ============================================================================
// Other diagnostics
diff --git a/toolchain/lowering/lowering.cpp b/toolchain/lowering/lowering.cpp
index 7803e379aeeb..a17c8930e382 100644
--- a/toolchain/lowering/lowering.cpp
+++ b/toolchain/lowering/lowering.cpp
@@ -176,6 +176,11 @@ auto Lowering::HandleStringLiteralNode(SemanticsNodeId /*node_id*/,
CARBON_FATAL() << "TODO: Add support: " << node;
}
+auto Lowering::HandleStructMemberAccessNode(SemanticsNodeId /*node_id*/,
+ SemanticsNode node) -> void {
+ CARBON_FATAL() << "TODO: Add support: " << node;
+}
+
auto Lowering::HandleStructTypeNode(SemanticsNodeId /*node_id*/,
SemanticsNode node) -> void {
CARBON_FATAL() << "TODO: Add support: " << node;
diff --git a/toolchain/semantics/semantics_ir.cpp b/toolchain/semantics/semantics_ir.cpp
index 5649935e4cd4..cb98c33bfc97 100644
--- a/toolchain/semantics/semantics_ir.cpp
+++ b/toolchain/semantics/semantics_ir.cpp
@@ -159,6 +159,7 @@ auto SemanticsIR::StringifyNodeImpl(llvm::raw_ostream& out,
case SemanticsNodeKind::Return:
case SemanticsNodeKind::ReturnExpression:
case SemanticsNodeKind::StringLiteral:
+ case SemanticsNodeKind::StructMemberAccess:
case SemanticsNodeKind::StructValue:
case SemanticsNodeKind::StubReference:
case SemanticsNodeKind::VarStorage:
diff --git a/toolchain/semantics/semantics_node.h b/toolchain/semantics/semantics_node.h
index 504c51b8eda6..1a54a52eedcb 100644
--- a/toolchain/semantics/semantics_node.h
+++ b/toolchain/semantics/semantics_node.h
@@ -124,6 +124,15 @@ struct SemanticsStringId : public IndexBase {
}
};
+// An index for member access.
+struct SemanticsMemberIndex : public IndexBase {
+ using IndexBase::IndexBase;
+ auto Print(llvm::raw_ostream& out) const -> void {
+ out << "member";
+ IndexBase::Print(out);
+ }
+};
+
// The standard structure for SemanticsNode. This is trying to provide a minimal
// amount of information for a node:
//
@@ -299,6 +308,10 @@ class SemanticsNode {
SemanticsBuiltinKind::StringType.AsInt(),
SemanticsStringId /*string_id*/>;
+ using StructMemberAccess = Factory;
+
using StructType = FactoryPreTyped<
SemanticsNodeKind::StructType, SemanticsBuiltinKind::TypeType.AsInt(),
SemanticsNodeBlockId /*ir_id*/, SemanticsNodeBlockId /*refs_id*/>;
diff --git a/toolchain/semantics/semantics_node_kind.def b/toolchain/semantics/semantics_node_kind.def
index d9cac417859f..e1ec3d7fe701 100644
--- a/toolchain/semantics/semantics_node_kind.def
+++ b/toolchain/semantics/semantics_node_kind.def
@@ -33,6 +33,7 @@ CARBON_SEMANTICS_NODE_KIND(RealLiteral)
CARBON_SEMANTICS_NODE_KIND(Return)
CARBON_SEMANTICS_NODE_KIND(ReturnExpression)
CARBON_SEMANTICS_NODE_KIND(StringLiteral)
+CARBON_SEMANTICS_NODE_KIND(StructMemberAccess)
CARBON_SEMANTICS_NODE_KIND(StructType)
CARBON_SEMANTICS_NODE_KIND(StructTypeField)
CARBON_SEMANTICS_NODE_KIND(StructValue)
diff --git a/toolchain/semantics/semantics_parse_tree_handler.cpp b/toolchain/semantics/semantics_parse_tree_handler.cpp
index f1a7ac1a9450..7071616a2949 100644
--- a/toolchain/semantics/semantics_parse_tree_handler.cpp
+++ b/toolchain/semantics/semantics_parse_tree_handler.cpp
@@ -495,8 +495,47 @@ auto SemanticsParseTreeHandler::HandleDesignatedName(ParseTree::Node parse_node)
auto SemanticsParseTreeHandler::HandleDesignatorExpression(
ParseTree::Node parse_node) -> bool {
- emitter_->Emit(parse_node, SemanticsTodo, "HandleDesignatorExpression");
- return false;
+ auto [_, name_id] =
+ node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
+
+ auto base_id = node_stack_.PopForNodeId();
+ auto base = semantics_->GetNode(base_id);
+ auto base_type = semantics_->GetNode(base.type_id());
+
+ switch (base_type.kind()) {
+ case SemanticsNodeKind::StructType: {
+ auto refs = semantics_->GetNodeBlock(base_type.GetAsStructType().second);
+ // TODO: Do we need to optimize this with a lookup table for O(1)?
+ for (int i = 0; i < static_cast(refs.size()); ++i) {
+ auto ref = semantics_->GetNode(refs[i]);
+ if (name_id == ref.GetAsStructTypeField()) {
+ AddNodeAndPush(parse_node, SemanticsNode::StructMemberAccess::Make(
+ parse_node, ref.type_id(), base_id,
+ SemanticsMemberIndex(i)));
+ return true;
+ }
+ }
+ CARBON_DIAGNOSTIC(DesignatorExpressionNameNotFound, Error,
+ "Type `{0}` does not have a member `{1}`.", std::string,
+ llvm::StringRef);
+ emitter_->Emit(parse_node, DesignatorExpressionNameNotFound,
+ semantics_->StringifyNode(base.type_id()),
+ semantics_->GetString(name_id));
+ break;
+ }
+ default: {
+ CARBON_DIAGNOSTIC(DesignatorExpressionUnsupported, Error,
+ "Type `{0}` does not support designator expressions.",
+ std::string);
+ emitter_->Emit(parse_node, DesignatorExpressionUnsupported,
+ semantics_->StringifyNode(base.type_id()));
+ break;
+ }
+ }
+
+ // Should only be reached on error.
+ node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
+ return true;
}
auto SemanticsParseTreeHandler::HandleEmptyDeclaration(
diff --git a/toolchain/semantics/testdata/designators/fail_unsupported.carbon b/toolchain/semantics/testdata/designators/fail_unsupported.carbon
new file mode 100644
index 000000000000..aebc7fa910c1
--- /dev/null
+++ b/toolchain/semantics/testdata/designators/fail_unsupported.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: %{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: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str0, arg1: node+0, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str1, arg1: node+2, type: nodeIntegerType},
+// 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: node+1,
+// CHECK:STDOUT: node+2,
+// CHECK:STDOUT: node+3,
+// CHECK:STDOUT: node+4,
+// CHECK:STDOUT: ],
+// CHECK:STDOUT: ]
+
+var x: i32;
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/designators/fail_unsupported.carbon:[[@LINE+1]]:15: Type `i32` does not support designator expressions.
+var y: i32 = x.b;
diff --git a/toolchain/semantics/testdata/struct/fail_member_access_type.carbon b/toolchain/semantics/testdata/struct/fail_member_access_type.carbon
new file mode 100644
index 000000000000..73d15c3ca9bf
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_member_access_type.carbon
@@ -0,0 +1,72 @@
+// 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: 40, exponent: -1, is_decimal: 1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeFloatingPointType},
+// 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: str0, 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: node+8, type: node+7},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str2, arg1: node+10, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+10, 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: node+10,
+// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+12,
+// 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: ]
+
+var x: {.a: f64} = {.a = 4.0};
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_member_access_type.carbon:[[@LINE+1]]:15: Type `{.a: f64}` does not have a member `b`.
+var y: i32 = x.b;
diff --git a/toolchain/semantics/testdata/struct/fail_non_member_access.carbon b/toolchain/semantics/testdata/struct/fail_non_member_access.carbon
new file mode 100644
index 000000000000..3c24a00b8915
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/fail_non_member_access.carbon
@@ -0,0 +1,72 @@
+// 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: 4,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// 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: 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: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str2, arg1: node+10, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+10, 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: node+10,
+// CHECK:STDOUT: node+11,
+// CHECK:STDOUT: node+12,
+// 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: ]
+
+var x: {.a: i32} = {.a = 4};
+// CHECK:STDERR: {{.*}}/toolchain/semantics/testdata/struct/fail_non_member_access.carbon:[[@LINE+1]]:15: Type `{.a: i32}` does not have a member `b`.
+var y: i32 = x.b;
diff --git a/toolchain/semantics/testdata/struct/member_access.carbon b/toolchain/semantics/testdata/struct/member_access.carbon
new file mode 100644
index 000000000000..19f8f238994b
--- /dev/null
+++ b/toolchain/semantics/testdata/struct/member_access.carbon
@@ -0,0 +1,92 @@
+// 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: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: {mantissa: 0, exponent: -1, is_decimal: 1},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT: a,
+// CHECK:STDOUT: b,
+// CHECK:STDOUT: x,
+// CHECK:STDOUT: y,
+// CHECK:STDOUT: z,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeFloatingPointType},
+// 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: RealLiteral, arg0: real0, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StructTypeField, arg0: str0, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: StubReference, arg0: node+5, type: nodeFloatingPointType},
+// CHECK:STDOUT: {kind: IntegerLiteral, arg0: int0, 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: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str3, arg1: node+14, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: StructMemberAccess, arg0: node+3, arg1: member1, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+14, arg1: node+16, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: VarStorage, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: BindName, arg0: str4, arg1: node+18, type: nodeIntegerType},
+// CHECK:STDOUT: {kind: Assign, arg0: node+18, arg1: node+14, type: nodeIntegerType},
+// 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+14,
+// CHECK:STDOUT: node+15,
+// CHECK:STDOUT: node+16,
+// CHECK:STDOUT: node+17,
+// CHECK:STDOUT: node+18,
+// CHECK:STDOUT: node+19,
+// CHECK:STDOUT: node+20,
+// 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: ]
+
+var x: {.a: f64, .b: i32} = {.a = 0.0, .b = 1};
+var y: i32 = x.b;
+var z: i32 = y;