[toolchain] Parsing support for struct literals, following #561 / #653. (#699)

This supports both struct type literals, `{.x: i32, .y: i32}`, and struct
value literals, `{.x = 3, .y = 4}`. The degenerate case of `{}` is
treated as a struct value literal, with the expectation that an empty
struct value has the same type/value duality as an empty tuple value.
This commit is contained in:
Richard Smith
2021-08-27 18:18:33 -07:00
committed by GitHub
parent bd7502261e
commit fe28dd9a33
5 changed files with 269 additions and 24 deletions
+110 -2
View File
@@ -12,6 +12,7 @@
#include "llvm/Support/SourceMgr.h"
#include "toolchain/common/yaml_test_helpers.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/diagnostics/mocks.h"
#include "toolchain/lexer/tokenized_buffer.h"
#include "toolchain/parser/parse_node_kind.h"
#include "toolchain/parser/parse_test_helpers.h"
@@ -19,11 +20,13 @@
namespace Carbon {
namespace {
using Carbon::Testing::DiagnosticMessage;
using Carbon::Testing::ExpectedNode;
using Carbon::Testing::MatchParseTreeNodes;
using namespace Carbon::Testing::NodeMatchers;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Ne;
using ::testing::NotNull;
using ::testing::StrEq;
@@ -405,10 +408,11 @@ TEST_F(ParseTreeTest, InvalidDesignators) {
MatchExpressionStatement(
MatchDesignatorExpression(
MatchNameReference("a"), ".", HasError),
";"),
HasError, ";"),
MatchExpressionStatement(
MatchDesignatorExpression(
MatchNameReference("a"), ".", HasError),
MatchNameReference("a"), ".",
MatchDesignatedName("fn", HasError)),
";"),
MatchExpressionStatement(
MatchDesignatorExpression(
@@ -970,6 +974,110 @@ TEST_F(ParseTreeTest, Tuples) {
MatchFileEnd()}));
}
TEST_F(ParseTreeTest, Structs) {
TokenizedBuffer tokens = GetTokenizedBuffer(R"(
var x: {.a: i32, .b: i32} = {.a = 1, .b = 2};
var y: {} = {};
var z: {.n: i32,} = {.n = 4,};
)");
ParseTree tree = ParseTree::Parse(tokens, consumer);
EXPECT_FALSE(tree.HasErrors());
EXPECT_THAT(
tree,
MatchParseTreeNodes(
{MatchVariableDeclaration(
MatchPatternBinding(
MatchDeclaredName("x"), ":",
MatchStructTypeLiteral(
MatchStructFieldType(MatchStructFieldDesignator(
".", MatchDesignatedName("a")),
":", MatchLiteral("i32")),
MatchStructComma(),
MatchStructFieldType(MatchStructFieldDesignator(
".", MatchDesignatedName("b")),
":", MatchLiteral("i32")),
MatchStructEnd())),
MatchVariableInitializer(MatchStructLiteral(
MatchStructFieldValue(MatchStructFieldDesignator(
".", MatchDesignatedName("a")),
"=", MatchLiteral("1")),
MatchStructComma(),
MatchStructFieldValue(MatchStructFieldDesignator(
".", MatchDesignatedName("b")),
"=", MatchLiteral("2")),
MatchStructEnd())),
MatchDeclarationEnd()),
MatchVariableDeclaration(
MatchPatternBinding(MatchDeclaredName("y"), ":",
MatchStructLiteral(MatchStructEnd())),
MatchVariableInitializer(MatchStructLiteral(MatchStructEnd())),
MatchDeclarationEnd()),
MatchVariableDeclaration(
MatchPatternBinding(
MatchDeclaredName("z"), ":",
MatchStructTypeLiteral(
MatchStructFieldType(MatchStructFieldDesignator(
".", MatchDesignatedName("n")),
":", MatchLiteral("i32")),
MatchStructComma(), MatchStructEnd())),
MatchVariableInitializer(MatchStructLiteral(
MatchStructFieldValue(MatchStructFieldDesignator(
".", MatchDesignatedName("n")),
"=", MatchLiteral("4")),
MatchStructComma(), MatchStructEnd())),
MatchDeclarationEnd()),
MatchFileEnd()}));
}
TEST_F(ParseTreeTest, StructErrors) {
struct Testcase {
llvm::StringLiteral input;
::testing::Matcher<const Diagnostic&> diag_matcher;
};
Testcase testcases[] = {
{"var x: {i32} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {a} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {a:} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {a=} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {.} = {};", DiagnosticMessage("Expected identifier after `.`.")},
{"var x: {.\"hello\" = 0, .y = 4} = {};",
DiagnosticMessage("Expected identifier after `.`.")},
{"var x: {.\"hello\": i32, .y: i32} = {};",
DiagnosticMessage("Expected identifier after `.`.")},
{"var x: {.a} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {.a:} = {};", DiagnosticMessage("Expected expression.")},
{"var x: {.a=} = {};", DiagnosticMessage("Expected expression.")},
{"var x: {.a: i32, .b = 0} = {};",
DiagnosticMessage("Expected `.field: type`.")},
{"var x: {.a = 0, b: i32} = {};",
DiagnosticMessage("Expected `.field = value`.")},
{"var x: {,} = {};",
DiagnosticMessage("Expected `.field: type` or `.field = value`.")},
{"var x: {.a: i32,,} = {};",
DiagnosticMessage("Expected `.field: type`.")},
{"var x: {.a = 0,,} = {};",
DiagnosticMessage("Expected `.field = value`.")},
{"var x: {.a: i32 banana} = {.a = 0};",
DiagnosticMessage("Expected `,` or `}`.")},
{"var x: {.a: i32} = {.a = 0 banana};",
DiagnosticMessage("Expected `,` or `}`.")},
};
for (Testcase testcase : testcases) {
TokenizedBuffer tokens = GetTokenizedBuffer(testcase.input);
Testing::MockDiagnosticConsumer consumer;
EXPECT_CALL(consumer, HandleDiagnostic(testcase.diag_matcher));
ParseTree tree = ParseTree::Parse(tokens, consumer);
EXPECT_TRUE(tree.HasErrors());
}
}
auto GetAndDropLine(llvm::StringRef& s) -> std::string {
auto newline_offset = s.find_first_of('\n');
llvm::StringRef line = s.slice(0, newline_offset);