diff --git a/common/struct_reflection.h b/common/struct_reflection.h index 2583e24a0681..89ef79081367 100644 --- a/common/struct_reflection.h +++ b/common/struct_reflection.h @@ -21,7 +21,7 @@ // - Only simple aggregate structs are supported. Types with base classes, // non-public data members, constructors, or virtual functions are not // supported. -// - Structs with more than 5 fields are not supported. This limit is easy to +// - Structs with more than 6 fields are not supported. This limit is easy to // increase if needed, but removing it entirely is hard. // - Structs containing a reference to the same type are not supported. @@ -75,7 +75,8 @@ constexpr auto CountFields() -> int { if constexpr (CanListInitialize(0)) { return CountFields>(); } else if constexpr (AnyWorkedSoFar) { - static_assert(sizeof...(Fields) <= 5, + // Note: Compare against the maximum number of fields supported *PLUS 1*. + static_assert(sizeof...(Fields) <= 7, "Unsupported: too many fields in struct"); return sizeof...(Fields) - 1; } else if constexpr (sizeof...(Fields) > 32) { @@ -150,6 +151,17 @@ struct FieldAccessor<5> { } }; +template <> +struct FieldAccessor<6> { + template + static auto Get(T& value) -> auto { + auto& [field0, field1, field2, field3, field4, field5] = value; + return std::tuple( + field0, field1, field2, field3, field4, field5); + } +}; + } // namespace Internal // Get the fields of the struct `T` as a tuple. diff --git a/common/struct_reflection_test.cpp b/common/struct_reflection_test.cpp index 619cb231e9bf..8ea54a59866c 100644 --- a/common/struct_reflection_test.cpp +++ b/common/struct_reflection_test.cpp @@ -20,6 +20,15 @@ struct TwoFields { int y; }; +struct SixFields { + int one; + int two; + int three; + int four; + int five; + int six; +}; + struct ReferenceField { int& ref; }; @@ -60,6 +69,7 @@ TEST(StructReflectionTest, CountFields) { static_assert(Internal::CountFields() == 0); static_assert(Internal::CountFields() == 1); static_assert(Internal::CountFields() == 2); + static_assert(Internal::CountFields() == 6); static_assert(Internal::CountFields() == 1); static_assert(Internal::CountFields() == 1); } @@ -74,12 +84,23 @@ TEST(StructReflectionTest, OneField) { EXPECT_EQ(std::get<0>(fields), 1); } -TEST(StructReflectionTest, TwoField) { +TEST(StructReflectionTest, TwoFields) { std::tuple fields = AsTuple(TwoFields{.x = 1, .y = 2}); EXPECT_EQ(std::get<0>(fields), 1); EXPECT_EQ(std::get<1>(fields), 2); } +TEST(StructReflectionTest, SixFields) { + std::tuple fields = AsTuple(SixFields{ + .one = 1, .two = 2, .three = 3, .four = 4, .five = 5, .six = 6}); + EXPECT_EQ(std::get<0>(fields), 1); + EXPECT_EQ(std::get<1>(fields), 2); + EXPECT_EQ(std::get<2>(fields), 3); + EXPECT_EQ(std::get<3>(fields), 4); + EXPECT_EQ(std::get<4>(fields), 5); + EXPECT_EQ(std::get<5>(fields), 6); +} + TEST(StructReflectionTest, NoDefaultConstructor) { std::tuple fields = AsTuple(TwoFieldsNoDefaultConstructor{.x = NoDefaultConstructor(1), diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index 5c2e2f2ed253..5534740b76f7 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -155,6 +155,7 @@ static auto ProcessParseNodes(Context& context, // clang warns on unhandled enum values; clang-tidy is incorrect here. // NOLINTNEXTLINE(bugprone-switch-missing-default-case) switch (auto parse_kind = context.parse_tree().node_kind(parse_node)) { + // TODO: Switch to `Parse::Name##Id(parse_node)` here. #define CARBON_PARSE_NODE_KIND(Name) \ case Parse::NodeKind::Name: { \ if (!Check::Handle##Name(context, parse_node)) { \ diff --git a/toolchain/check/decl_state.h b/toolchain/check/decl_state.h index 162d8f23c6d1..0813c164a528 100644 --- a/toolchain/check/decl_state.h +++ b/toolchain/check/decl_state.h @@ -43,7 +43,7 @@ enum class KeywordModifierSet : uint32_t { }; inline auto operator!(KeywordModifierSet k) -> bool { - return !static_cast(k); + return !static_cast(k); } // State stored for each declaration we are currently in: the kind of diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 9493ab5778ce..eaec94ba6145 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -92,6 +92,7 @@ class NodeStack { } // Pops the top of the stack and returns the parse_node. + // TODO: return a parse::NodeIdForKind instead. template auto PopForSoloParseNode() -> Parse::NodeId { Entry back = PopEntry(); @@ -102,6 +103,7 @@ class NodeStack { // Pops the top of the stack if it is the given kind, and returns the // parse_node. Otherwise, returns std::nullopt. + // TODO: Return a `Parse::NodeIdForKind` instead. template auto PopForSoloParseNodeIf() -> std::optional { if (PeekIs()) { @@ -200,6 +202,9 @@ class NodeStack { // Pops a name from the top of the stack and returns the ID. auto PopName() -> SemIR::NameId { return PopNameWithParseNode().second; } + // TODO: Can we add a `Pop<...>` that takes a parse node category? See + // https://github.com/carbon-language/carbon-lang/pull/3534/files#r1432067519 + // Pops the top of the stack and returns the ID. template auto Pop() -> auto { diff --git a/toolchain/parse/BUILD b/toolchain/parse/BUILD index 759784b349a7..55f146795919 100644 --- a/toolchain/parse/BUILD +++ b/toolchain/parse/BUILD @@ -16,12 +16,34 @@ filegroup( cc_library( name = "node_kind", srcs = ["node_kind.cpp"], - hdrs = ["node_kind.h"], + hdrs = [ + "node_ids.h", + "node_kind.h", + "typed_nodes.h", + ], textual_hdrs = ["node_kind.def"], deps = [ "//common:check", "//common:enum_base", + "//toolchain/base:index_base", "//toolchain/lex:token_kind", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "typed_nodes_test", + size = "small", + srcs = ["typed_nodes_test.cpp"], + deps = [ + ":node_kind", + ":tree", + "//testing/base:gtest_main", + "//toolchain/diagnostics:diagnostic_emitter", + "//toolchain/diagnostics:mocks", + "//toolchain/lex", + "//toolchain/lex:tokenized_buffer", + "@com_google_googletest//:gtest", ], ) @@ -38,6 +60,7 @@ cc_library( srcs = [ "context.cpp", "context.h", + "extract.cpp", "tree.cpp", ] + # Glob handler files to avoid missing any. @@ -52,6 +75,7 @@ cc_library( "//common:check", "//common:error", "//common:ostream", + "//common:struct_reflection", "//common:vlog", "//toolchain/base:pretty_stack_trace_function", "//toolchain/base:value_store", diff --git a/toolchain/parse/extract.cpp b/toolchain/parse/extract.cpp new file mode 100644 index 000000000000..3b6fd5fd8084 --- /dev/null +++ b/toolchain/parse/extract.cpp @@ -0,0 +1,342 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include "common/error.h" +#include "common/struct_reflection.h" +#include "toolchain/parse/tree.h" +#include "toolchain/parse/typed_nodes.h" + +namespace Carbon::Parse { + +// A trait type that should be specialized by types that can be extracted +// from a parse tree. A specialization should provide the following API: +// +// ```cpp +// template<> +// struct Extractable { +// // Extract a value of this type from the sequence of nodes starting at +// // `it`, and increment `it` past this type. Returns `std::nullopt` if +// // the tree is malformed. If `trace != nullptr`, writes what actions +// // were taken to `*trace`. +// static auto Extract(Tree* tree, Tree::SiblingIterator& it, +// Tree::SiblingIterator end, +// ErrorBuilder* trace) -> std::optional; +// }; +// ``` +// +// Note that `Tree::SiblingIterator`s iterate in reverse order through the +// children of a node. +// +// This class is only in this file. +template +struct Extractable; + +// Extract a `NodeId` as a single child. +template <> +struct Extractable { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional { + if (it == end) { + if (trace) { + *trace << "NodeId error: no more children\n"; + } + return std::nullopt; + } + if (trace) { + *trace << "NodeId: " << tree->node_kind(*it) << " consumed\n"; + } + return NodeId(*it++); + } +}; + +// Extract a `FooId`, which is the same as `NodeIdForKind`, +// as a single required child. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + if (it == end || tree->node_kind(*it) != Kind) { + if (trace) { + if (it == end) { + *trace << "NodeIdForKind error: no more children, expected " << Kind + << "\n"; + } else { + *trace << "NodeIdForKind error: wrong kind " << tree->node_kind(*it) + << ", expected " << Kind << "\n"; + } + } + return std::nullopt; + } + if (trace) { + *trace << "NodeIdForKind: " << Kind << " consumed\n"; + } + return NodeIdForKind(*it++); + } +}; + +// Extract a `NodeIdInCategory` as a single child. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + if (trace) { + *trace << "NodeIdInCategory"; + // TODO: Make NodeCategory printable instead. + if (!Category) { + *trace << " "; + } +#define CARBON_NODE_CATEGORY(Name) \ + if (!!(Category & NodeCategory::Name)) { \ + *trace << " " #Name; \ + } + CARBON_NODE_CATEGORY(Decl); + CARBON_NODE_CATEGORY(Expr); + CARBON_NODE_CATEGORY(Modifier); + CARBON_NODE_CATEGORY(NameComponent); + CARBON_NODE_CATEGORY(Pattern); + CARBON_NODE_CATEGORY(Statement); +#undef CARBON_NODE_CATEGORY + } + + if (it == end || !(tree->node_kind(*it).category() & Category)) { + if (trace) { + if (it == end) { + *trace << " error: no more children\n"; + } else { + *trace << " error: kind " << tree->node_kind(*it) + << " doesn't match\n"; + } + } + return std::nullopt; + } + if (trace) { + *trace << ": kind " << tree->node_kind(*it) << " consumed\n"; + } + return NodeIdInCategory(*it++); + } +}; + +// Extract a `NodeIdOneOf` as a single required child. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + auto kind = tree->node_kind(*it); + if (it == end || (kind != T::Kind && kind != U::Kind)) { + if (trace) { + if (it == end) { + *trace << "NodeIdOneOf error: no more children, expected " << T::Kind + << " or " << U::Kind << "\n"; + } else { + *trace << "NodeIdOneOf error: wrong kind " << tree->node_kind(*it) + << ", expected " << T::Kind << " or " << U::Kind << "\n"; + } + } + return std::nullopt; + } + if (trace) { + *trace << "NodeIdOneOf " << T::Kind << " or " << U::Kind << ": " + << tree->node_kind(*it) << " consumed"; + } + return NodeIdOneOf(*it++); + } +}; + +// Extract a `NodeIdNot` as a single required child. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + if (it == end || tree->node_kind(*it) == T::Kind) { + if (trace) { + if (it == end) { + *trace << "NodeIdNot " << T::Kind << " error: no more children\n"; + } else { + *trace << "NodeIdNot error: unexpected " << T::Kind << "\n"; + } + } + return std::nullopt; + } + if (trace) { + *trace << "NodeIdNot " << T::Kind << ": " << tree->node_kind(*it) + << " consumed\n"; + } + return NodeIdNot(*it++); + } +}; + +// Extract an `llvm::SmallVector` by extracting `T`s until we can't. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + if (trace) { + *trace << "Vector: begin\n"; + } + llvm::SmallVector result; + while (it != end) { + auto old_it = it; + auto item = Extractable::Extract(tree, it, end, trace); + if (!item.has_value()) { + it = old_it; + break; + } + result.push_back(*item); + } + std::reverse(result.begin(), result.end()); + if (trace) { + *trace << "Vector: end\n"; + } + return result; + } +}; + +// Extract an `optional` from a list of child nodes by attempting to extract +// a `T`, and extracting nothing if that fails. +template +struct Extractable> { + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + if (trace) { + *trace << "Optional" << typeid(T).name() << ": begin\n"; + } + auto old_it = it; + std::optional value = Extractable::Extract(tree, it, end, trace); + if (value) { + if (trace) { + *trace << "Optional" << typeid(T).name() << ": found\n"; + } + return value; + } + if (trace) { + *trace << "Optional" << typeid(T).name() << ": missing\n"; + } + it = old_it; + return value; + } +}; + +// Extract a `tuple` from a list of child nodes by extracting each `T` in +// reverse order. +template +struct Extractable> { + template + static auto ExtractImpl(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace, + std::index_sequence) + -> std::optional> { + std::tuple...> fields; + if (trace) { + *trace << "Tuple: begin\n"; + } + + // Use a fold over the `=` operator to parse fields from right to left. + [[maybe_unused]] int unused; + bool ok = true; + static_cast( + ((ok && (ok = (std::get(fields) = + Extractable::Extract(tree, it, end, trace)) + .has_value()), + unused) = ... = 0)); + + if (!ok) { + if (trace) { + *trace << "Tuple: error\n"; + } + return std::nullopt; + } + + if (trace) { + *trace << "Tuple: success\n"; + } + return std::tuple{std::move(std::get(fields).value())...}; + } + + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional> { + return ExtractImpl(tree, it, end, trace, + std::make_index_sequence()); + } +}; + +// Extract the fields of a simple aggregate type. +template +struct Extractable { + static_assert(std::is_aggregate_v, "Unsupported child type"); + static auto ExtractImpl(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional { + if (trace) { + *trace << "Aggregate " << typeid(T).name() << ": begin\n"; + } + // Extract the corresponding tuple type. + using TupleType = decltype(StructReflection::AsTuple(std::declval())); + auto tuple = Extractable::Extract(tree, it, end, trace); + if (!tuple.has_value()) { + if (trace) { + *trace << "Aggregate" << typeid(T).name() << ": error\n"; + } + return std::nullopt; + } + + if (trace) { + *trace << "Aggregate" << typeid(T).name() << ": success\n"; + } + // Convert the tuple to the struct type. + return std::apply( + [](auto&&... value) { + return T{std::forward(value)...}; + }, + *tuple); + } + + static auto Extract(const Tree* tree, Tree::SiblingIterator& it, + Tree::SiblingIterator end, ErrorBuilder* trace) + -> std::optional { + static_assert(!HasKindMember, "Missing Id suffix"); + return ExtractImpl(tree, it, end, trace); + } +}; + +template +auto Tree::TryExtractNodeFromChildren( + llvm::iterator_range children, + ErrorBuilder* trace) const -> std::optional { + auto it = children.begin(); + auto result = Extractable::ExtractImpl(this, it, children.end(), trace); + if (it != children.end()) { + if (trace) { + *trace << "Error: " << node_kind(*it) << " node left unconsumed."; + } + return std::nullopt; + } + return result; +} + +// Manually instantiate Tree::TryExtractNodeFromChildren +#define CARBON_PARSE_NODE_KIND(KindName) \ + template auto Tree::TryExtractNodeFromChildren( \ + llvm::iterator_range children, \ + ErrorBuilder * trace) const -> std::optional; + +// Also instantiate for `File`, even though it isn't a parse node. +CARBON_PARSE_NODE_KIND(File) +#include "toolchain/parse/node_kind.def" + +auto Tree::ExtractFile() const -> File { + return ExtractNodeFromChildren(roots()); +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/handle_brace_expr.cpp b/toolchain/parse/handle_brace_expr.cpp index 20675c0247b7..44f6cd8e56c3 100644 --- a/toolchain/parse/handle_brace_expr.cpp +++ b/toolchain/parse/handle_brace_expr.cpp @@ -149,6 +149,7 @@ static auto HandleBraceExprParamFinish(Context& context, NodeKind node_kind, if (state.has_error) { context.AddLeafNode(NodeKind::InvalidParse, state.token, /*has_error=*/true); + context.ReturnErrorOnState(); } else { context.AddNode(node_kind, state.token, state.subtree_start, /*has_error=*/false); diff --git a/toolchain/parse/handle_let.cpp b/toolchain/parse/handle_let.cpp index 22330b016c76..4f3eb3f62b67 100644 --- a/toolchain/parse/handle_let.cpp +++ b/toolchain/parse/handle_let.cpp @@ -30,11 +30,13 @@ auto HandleLetAfterPattern(Context& context) -> void { if (auto equals = context.ConsumeIf(Lex::TokenKind::Equal)) { context.AddLeafNode(NodeKind::LetInitializer, *equals); context.PushState(State::Expr); - } else if (!state.has_error) { - CARBON_DIAGNOSTIC( - ExpectedInitializerAfterLet, Error, - "Expected `=`; `let` declaration must have an initializer."); - context.emitter().Emit(*context.position(), ExpectedInitializerAfterLet); + } else { + if (!state.has_error) { + CARBON_DIAGNOSTIC( + ExpectedInitializerAfterLet, Error, + "Expected `=`; `let` declaration must have an initializer."); + context.emitter().Emit(*context.position(), ExpectedInitializerAfterLet); + } context.ReturnErrorOnState(); } } diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h new file mode 100644 index 000000000000..f8ae31910d30 --- /dev/null +++ b/toolchain/parse/node_ids.h @@ -0,0 +1,99 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_PARSE_NODE_IDS_H_ +#define CARBON_TOOLCHAIN_PARSE_NODE_IDS_H_ + +#include "toolchain/base/index_base.h" +#include "toolchain/parse/node_kind.h" + +namespace Carbon::Parse { + +// A lightweight handle representing a node in the tree. +// +// Objects of this type are small and cheap to copy and store. They don't +// contain any of the information about the node, and serve as a handle that +// can be used with the underlying tree to query for detailed information. +struct NodeId : public IdBase { + // An explicitly invalid instance. + static const NodeId Invalid; + + using IdBase::IdBase; +}; + +constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex); + +// For looking up the type associated with a given id type. +template +struct NodeForId; + +// `Id` is a typed version of `NodeId` that references a node of kind +// ``: +template +struct NodeIdForKind : public NodeId { + static const NodeIdForKind Invalid; + + explicit NodeIdForKind(NodeId node_id) : NodeId(node_id) {} +}; +template +constexpr NodeIdForKind NodeIdForKind::Invalid = + NodeIdForKind(NodeId::Invalid.index); + +#define CARBON_PARSE_NODE_KIND(KindName) \ + using KindName##Id = NodeIdForKind; +#include "toolchain/parse/node_kind.def" + +// NodeId that matches any NodeKind whose `category()` overlaps with `Category`. +template +struct NodeIdInCategory : public NodeId { + // An explicitly invalid instance. + static const NodeIdInCategory Invalid; + + explicit NodeIdInCategory(NodeId node_id) : NodeId(node_id) {} +}; + +template +constexpr NodeIdInCategory NodeIdInCategory::Invalid = + NodeIdInCategory(NodeId::InvalidIndex); + +// Aliases for `NodeIdInCategory` to describe particular categories of nodes. +using AnyDeclId = NodeIdInCategory; +using AnyExprId = NodeIdInCategory; +using AnyModifierId = NodeIdInCategory; +using AnyNameComponentId = NodeIdInCategory; +using AnyPatternId = NodeIdInCategory; +using AnyStatementId = NodeIdInCategory; + +// NodeId with kind that matches either T::Kind or U::Kind. +template +struct NodeIdOneOf : public NodeId { + // An explicitly invalid instance. + static const NodeIdOneOf Invalid; + + explicit NodeIdOneOf(NodeId node_id) : NodeId(node_id) {} +}; + +template +constexpr NodeIdOneOf NodeIdOneOf::Invalid = + NodeIdOneOf(NodeId::InvalidIndex); + +// NodeId with kind that is anything but T::Kind. +template +struct NodeIdNot : public NodeId { + // An explicitly invalid instance. + static const NodeIdNot Invalid; + + explicit NodeIdNot(NodeId node_id) : NodeId(node_id) {} +}; + +template +constexpr NodeIdNot NodeIdNot::Invalid = + NodeIdNot(NodeId::InvalidIndex); + +// Note that the support for extracting these types using the `Tree::Extract*` +// functions is defined in `extract.cpp`. + +} // namespace Carbon::Parse + +#endif // CARBON_TOOLCHAIN_PARSE_NODE_IDS_H_ diff --git a/toolchain/parse/node_kind.cpp b/toolchain/parse/node_kind.cpp index 2000fd6d6c36..27acda93d7ea 100644 --- a/toolchain/parse/node_kind.cpp +++ b/toolchain/parse/node_kind.cpp @@ -5,6 +5,7 @@ #include "toolchain/parse/node_kind.h" #include "common/check.h" +#include "toolchain/parse/typed_nodes.h" namespace Carbon::Parse { @@ -76,4 +77,16 @@ auto NodeKind::CheckMatchesTokenKind(Lex::TokenKind token_kind, bool has_error) << ", but expected token kind " << expected_token_kind; } +auto NodeKind::category() const -> NodeCategory { + return definition().category(); +} + +auto NodeKind::definition() const -> const Definition& { + static constexpr const Definition* Table[] = { +#define CARBON_PARSE_NODE_KIND(Name) &Parse::Name::Kind, +#include "toolchain/parse/node_kind.def" + }; + return *Table[AsInt()]; +} + } // namespace Carbon::Parse diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 649fd4e75876..9884a0c16629 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -203,6 +203,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(ImportDirective, ImportIntroducer, // `library` as directive: // LibraryIntroducer // DefaultLibrary or _external_: LibraryName +// PackageApi or PackageImpl // LibraryDirective CARBON_PARSE_NODE_KIND_CHILD_COUNT(DefaultLibrary, 0, Default) CARBON_PARSE_NODE_KIND_CHILD_COUNT(LibraryIntroducer, 0, Library) @@ -210,14 +211,14 @@ CARBON_PARSE_NODE_KIND_BRACKET(LibraryDirective, LibraryIntroducer, CARBON_IF_VALID(Semi)) // `library` in `package` or `import`: -// _external_: LibraryName +// _external_: LibraryName or DefaultLibrary // LibrarySpecifier CARBON_PARSE_NODE_KIND_CHILD_COUNT(LibrarySpecifier, 1, Library) // `namespace`: // NamespaceStart // _repeated_ _external_: modifier -// _external_: Name or QualifiedDecl +// _external_: IdentifierName or QualifiedDecl // Namespace CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0, Namespace) CARBON_PARSE_NODE_KIND_BRACKET(Namespace, NamespaceStart, CARBON_IF_VALID(Semi)) @@ -234,7 +235,8 @@ CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart, // `fn`: // FunctionIntroducer // _repeated_ _external_: modifier -// _external_: Name or QualifiedDecl +// _external_: IdentifierName or QualifiedDecl +// _optional_ _external_: ImplicitParamList // _external_: TuplePattern // _external_: type expression // ReturnType @@ -291,7 +293,7 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(ArrayExprSemi, 2, CARBON_IF_VALID(Semi)) CARBON_PARSE_NODE_KIND_BRACKET(ArrayExpr, ArrayExprSemi, CloseSquareBracket) // A binding pattern, such as `name: Type`: -// Name or SelfValueName +// IdentifierName or SelfValueName // _external_: type expression // [Generic]BindingPattern // _optional_ Address @@ -446,8 +448,8 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExprComma, 0, Comma) CARBON_PARSE_NODE_KIND_BRACKET(CallExpr, CallExprStart, CloseParen) // A qualified declaration, such as `a.b`: -// _external_: Name or QualifiedDecl -// _external_: Name +// _external_: IdentifierName or QualifiedDecl +// _external_: IdentifierName // QualifiedDecl // // TODO: This will eventually more general expressions, for example with @@ -458,14 +460,14 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(QualifiedDecl, 2, Period) // `GetObject().(Interface.member)`: // _external_: lhs expression // _external_: rhs expression -// QualifiedExpr +// MemberAccessExpr CARBON_PARSE_NODE_KIND_CHILD_COUNT(MemberAccessExpr, 2, Period) // A pointer member access expression, such as `a->b` or // `GetObject()->(Interface.member)`: // _external_: lhs expression // _external_: rhs expression -// QualifiedExpr +// PointerMemberAccessExpr CARBON_PARSE_NODE_KIND_CHILD_COUNT(PointerMemberAccessExpr, 2, MinusGreater) // A value literal. @@ -558,7 +560,7 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExprElse, 3, CARBON_IF_VALID(Else)) // Struct literals, such as `{.a = 0}`: // StructLiteralOrStructTypeLiteralStart -// _external_: Name +// _external_: IdentifierName or BaseName // StructFieldDesignator // _external_: expression // StructFieldValue @@ -568,7 +570,7 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExprElse, 3, CARBON_IF_VALID(Else)) // // Struct type literals, such as `{.a: i32}`: // StructLiteralOrStructTypeLiteralStart -// _external_: Name +// _external_: IdentifierName or BaseName // StructFieldDesignator // _external_: type expression // StructFieldType @@ -609,7 +611,9 @@ CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Virtual) // `class`: // ClassIntroducer // _repeated_ _external_: modifier -// _external_: Name or QualifiedDecl +// _external_: IdentifierName or QualifiedDecl +// _optional_ _external_: ImplicitParamList +// _optional_ _external_: TuplePattern // ClassDefinitionStart // _external_: declarations // ClassDefinition @@ -638,7 +642,9 @@ CARBON_PARSE_NODE_KIND_BRACKET(BaseDecl, BaseIntroducer, CARBON_IF_VALID(Semi)) // `interface`: // InterfaceIntroducer // _repeated_ _external_: modifier -// _external_: Name or QualifiedDecl +// _external_: IdentifierName or QualifiedDecl +// _optional_ _external_: ImplicitParamList +// _optional_ _external_: TuplePattern // InterfaceDefinitionStart // _external_: declarations // InterfaceDefinition @@ -684,7 +690,9 @@ CARBON_PARSE_NODE_KIND_BRACKET(ImplDecl, ImplIntroducer, CARBON_IF_VALID(Semi)) // `constraint`: // NamedConstraintIntroducer // _repeated_ _external_: modifier -// _external_: Name or QualifiedDecl +// _external_: IdentifierName or QualifiedDecl +// _optional_ _external_: ImplicitParamList +// _optional_ _external_: TuplePattern // NamedConstraintDefinitionStart // _external_: declarations // NamedConstraintDefinition diff --git a/toolchain/parse/node_kind.h b/toolchain/parse/node_kind.h index 19b7ecaf0069..18c88659ec55 100644 --- a/toolchain/parse/node_kind.h +++ b/toolchain/parse/node_kind.h @@ -8,10 +8,33 @@ #include #include "common/enum_base.h" +#include "llvm/ADT/BitmaskEnum.h" #include "toolchain/lex/token_kind.h" namespace Carbon::Parse { +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + +// Represents a set of keyword modifiers, using a separate bit per modifier. +// +// We expect this to grow, so are using a bigger size than needed. +// NOLINTNEXTLINE(performance-enum-size) +enum class NodeCategory : uint32_t { + Decl = 1 << 0, + Expr = 1 << 1, + Modifier = 1 << 2, + NameComponent = 1 << 3, + Pattern = 1 << 4, + Statement = 1 << 5, + None = 0, + + LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Statement) +}; + +inline auto operator!(NodeCategory k) -> bool { + return !static_cast(k); +} + CARBON_DEFINE_RAW_ENUM_CLASS(NodeKind, uint8_t) { #define CARBON_PARSE_NODE_KIND(Name) CARBON_RAW_ENUM_ENUMERATOR(Name) #include "toolchain/parse/node_kind.def" @@ -40,7 +63,21 @@ class NodeKind : public CARBON_ENUM_BASE(NodeKind) { // that has_bracket is false. auto child_count() const -> int32_t; + // Returns which categories this node kind is in. + auto category() const -> NodeCategory; + using EnumBase::Create; + + class Definition; + + // Provides a definition for this parse node kind. Should only be called + // once, to construct the kind as part of defining it in `typed_nodes.h`. + constexpr auto Define(NodeCategory category = NodeCategory::None) const + -> Definition; + + private: + // Looks up the definition for this instruction kind. + auto definition() const -> const Definition&; }; #define CARBON_PARSE_NODE_KIND(Name) \ @@ -50,6 +87,40 @@ class NodeKind : public CARBON_ENUM_BASE(NodeKind) { // We expect the parse node kind to fit compactly into 8 bits. static_assert(sizeof(NodeKind) == 1, "Kind objects include padding!"); +// A definition of a parse node kind. This is a NodeKind value, plus +// ancillary data such as the name to use for the node kind in LLVM IR. These +// are not copyable, and only one instance of this type is expected to exist per +// parse node kind, specifically `TypedNode::Kind`. Use `NodeKind` instead as a +// thin wrapper around a parse node kind index. +class NodeKind::Definition : public NodeKind { + public: + // Not copyable. + Definition(const Definition&) = delete; + auto operator=(const Definition&) -> Definition& = delete; + + // Returns which categories this node kind is in. + constexpr auto category() const -> NodeCategory { return category_; } + + private: + friend class NodeKind; + + constexpr Definition(NodeKind kind, NodeCategory category) + : NodeKind(kind), category_(category) {} + + NodeCategory category_; +}; + +constexpr auto NodeKind::Define(NodeCategory category) const -> Definition { + return Definition(*this, category); +} + +// HasKindMember is true if T has a `static const NodeKind::Definition Kind` +// member. +template +inline constexpr bool HasKindMember = false; +template +inline constexpr bool HasKindMember = true; + } // namespace Carbon::Parse #endif // CARBON_TOOLCHAIN_PARSE_NODE_KIND_H_ diff --git a/toolchain/parse/testdata/let/fail_empty.carbon b/toolchain/parse/testdata/let/fail_empty.carbon index 8ed75545887f..4fade9f7ad75 100644 --- a/toolchain/parse/testdata/let/fail_empty.carbon +++ b/toolchain/parse/testdata/let/fail_empty.carbon @@ -16,6 +16,6 @@ let; // CHECK:STDOUT: {kind: 'IdentifierName', text: ';', has_error: yes}, // CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ';', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/struct/fail_comma_only.carbon b/toolchain/parse/testdata/struct/fail_comma_only.carbon index a0b8a58f38ee..89ef26a19b59 100644 --- a/toolchain/parse/testdata/struct/fail_comma_only.carbon +++ b/toolchain/parse/testdata/struct/fail_comma_only.carbon @@ -17,7 +17,7 @@ var x: {,} = {}; // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: ',', has_error: yes}, // CHECK:STDOUT: {kind: 'StructComma', text: ','}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 6}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_comma_repeat_in_type.carbon b/toolchain/parse/testdata/struct/fail_comma_repeat_in_type.carbon index a6fe2e1446f3..0efd14d8abbb 100644 --- a/toolchain/parse/testdata/struct/fail_comma_repeat_in_type.carbon +++ b/toolchain/parse/testdata/struct/fail_comma_repeat_in_type.carbon @@ -22,7 +22,7 @@ var x: {.a: i32,,} = {}; // CHECK:STDOUT: {kind: 'StructComma', text: ','}, // CHECK:STDOUT: {kind: 'InvalidParse', text: ',', has_error: yes}, // CHECK:STDOUT: {kind: 'StructComma', text: ','}, -// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', has_error: yes, subtree_size: 9}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 11}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_comma_repeat_in_value.carbon b/toolchain/parse/testdata/struct/fail_comma_repeat_in_value.carbon index 21315a3bd34f..e36176e0a4cc 100644 --- a/toolchain/parse/testdata/struct/fail_comma_repeat_in_value.carbon +++ b/toolchain/parse/testdata/struct/fail_comma_repeat_in_value.carbon @@ -22,7 +22,7 @@ var x: {.a = 0,,} = {}; // CHECK:STDOUT: {kind: 'StructComma', text: ','}, // CHECK:STDOUT: {kind: 'InvalidParse', text: ',', has_error: yes}, // CHECK:STDOUT: {kind: 'StructComma', text: ','}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 9}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 11}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_dot_only.carbon b/toolchain/parse/testdata/struct/fail_dot_only.carbon index f1e17964c957..31a5310c3757 100644 --- a/toolchain/parse/testdata/struct/fail_dot_only.carbon +++ b/toolchain/parse/testdata/struct/fail_dot_only.carbon @@ -18,7 +18,7 @@ var x: {.} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: '}', has_error: yes}, // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '.', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 7}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_dot_string_colon.carbon b/toolchain/parse/testdata/struct/fail_dot_string_colon.carbon index 598e514023bf..41b5e47cb8f4 100644 --- a/toolchain/parse/testdata/struct/fail_dot_string_colon.carbon +++ b/toolchain/parse/testdata/struct/fail_dot_string_colon.carbon @@ -24,7 +24,7 @@ var x: {."hello": i32, .y: i32} = {}; // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, // CHECK:STDOUT: {kind: 'StructFieldType', text: ':', subtree_size: 4}, -// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', has_error: yes, subtree_size: 11}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 13}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_dot_string_equals.carbon b/toolchain/parse/testdata/struct/fail_dot_string_equals.carbon index ffeafafae49f..60312cba14c6 100644 --- a/toolchain/parse/testdata/struct/fail_dot_string_equals.carbon +++ b/toolchain/parse/testdata/struct/fail_dot_string_equals.carbon @@ -24,7 +24,7 @@ var x: {."hello" = 0, .y = 4} = {}; // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '4'}, // CHECK:STDOUT: {kind: 'StructFieldValue', text: '=', subtree_size: 4}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 11}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 13}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_identifier_colon.carbon b/toolchain/parse/testdata/struct/fail_identifier_colon.carbon index b1bc833b0437..31174a149749 100644 --- a/toolchain/parse/testdata/struct/fail_identifier_colon.carbon +++ b/toolchain/parse/testdata/struct/fail_identifier_colon.carbon @@ -16,7 +16,7 @@ var x: {a:} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'a', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 5}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_identifier_equals.carbon b/toolchain/parse/testdata/struct/fail_identifier_equals.carbon index 3f0ee3c16c72..ab3099b32480 100644 --- a/toolchain/parse/testdata/struct/fail_identifier_equals.carbon +++ b/toolchain/parse/testdata/struct/fail_identifier_equals.carbon @@ -16,7 +16,7 @@ var x: {a=} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'a', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 5}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_identifier_only.carbon b/toolchain/parse/testdata/struct/fail_identifier_only.carbon index 632b4386893a..7e326c33f7ab 100644 --- a/toolchain/parse/testdata/struct/fail_identifier_only.carbon +++ b/toolchain/parse/testdata/struct/fail_identifier_only.carbon @@ -16,7 +16,7 @@ var x: {a} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'a', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 5}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_missing_type.carbon b/toolchain/parse/testdata/struct/fail_missing_type.carbon index c86608dbe568..f09551e8f594 100644 --- a/toolchain/parse/testdata/struct/fail_missing_type.carbon +++ b/toolchain/parse/testdata/struct/fail_missing_type.carbon @@ -19,7 +19,7 @@ var x: {.a:} = {}; // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '}', has_error: yes}, // CHECK:STDOUT: {kind: 'InvalidParse', text: ':', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', has_error: yes, subtree_size: 6}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 8}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_missing_value.carbon b/toolchain/parse/testdata/struct/fail_missing_value.carbon index 20763f7a1f06..5688750db069 100644 --- a/toolchain/parse/testdata/struct/fail_missing_value.carbon +++ b/toolchain/parse/testdata/struct/fail_missing_value.carbon @@ -19,7 +19,7 @@ var x: {.a=} = {}; // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '}', has_error: yes}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 6}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 8}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_mix_type_and_value.carbon b/toolchain/parse/testdata/struct/fail_mix_type_and_value.carbon index c52ac3024932..9e44b9bc6905 100644 --- a/toolchain/parse/testdata/struct/fail_mix_type_and_value.carbon +++ b/toolchain/parse/testdata/struct/fail_mix_type_and_value.carbon @@ -23,7 +23,7 @@ var x: {.a: i32, .b = 0} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'b'}, // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '.', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', has_error: yes, subtree_size: 10}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 12}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_mix_value_and_type.carbon b/toolchain/parse/testdata/struct/fail_mix_value_and_type.carbon index d91f8a7fcdad..c289520cd7fa 100644 --- a/toolchain/parse/testdata/struct/fail_mix_value_and_type.carbon +++ b/toolchain/parse/testdata/struct/fail_mix_value_and_type.carbon @@ -21,7 +21,7 @@ var x: {.a = 0, b: i32} = {}; // CHECK:STDOUT: {kind: 'StructFieldValue', text: '=', subtree_size: 4}, // CHECK:STDOUT: {kind: 'StructComma', text: ','}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'b', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 8}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 10}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_mix_with_unknown.carbon b/toolchain/parse/testdata/struct/fail_mix_with_unknown.carbon index 46edbf2c3cd8..60d5ab25dce8 100644 --- a/toolchain/parse/testdata/struct/fail_mix_with_unknown.carbon +++ b/toolchain/parse/testdata/struct/fail_mix_with_unknown.carbon @@ -41,7 +41,7 @@ var x: i32 = {.a: i32, .b, .c = 1}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'c'}, // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '.', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 14}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 20}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, // CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, @@ -61,7 +61,7 @@ var x: i32 = {.a: i32, .b, .c = 1}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'c'}, // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '.', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'StructTypeLiteral', text: '}', has_error: yes, subtree_size: 14}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 20}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/struct/fail_no_colon_or_equals.carbon b/toolchain/parse/testdata/struct/fail_no_colon_or_equals.carbon index f053680851f1..5b3c25328ba2 100644 --- a/toolchain/parse/testdata/struct/fail_no_colon_or_equals.carbon +++ b/toolchain/parse/testdata/struct/fail_no_colon_or_equals.carbon @@ -18,7 +18,7 @@ var x: {.a} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'a'}, // CHECK:STDOUT: {kind: 'StructFieldDesignator', text: '.', subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '.', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 7}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/testdata/struct/fail_type_no_designator.carbon b/toolchain/parse/testdata/struct/fail_type_no_designator.carbon index 2f1187fd2724..2dc5dc9d9aeb 100644 --- a/toolchain/parse/testdata/struct/fail_type_no_designator.carbon +++ b/toolchain/parse/testdata/struct/fail_type_no_designator.carbon @@ -16,7 +16,7 @@ var x: {i32} = {}; // CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'i32', has_error: yes}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 5}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StructLiteralOrStructTypeLiteralStart', text: '{'}, diff --git a/toolchain/parse/tree.cpp b/toolchain/parse/tree.cpp index 206f33312ae8..a5d0ee5733e8 100644 --- a/toolchain/parse/tree.cpp +++ b/toolchain/parse/tree.cpp @@ -12,6 +12,7 @@ #include "toolchain/lex/tokenized_buffer.h" #include "toolchain/parse/context.h" #include "toolchain/parse/node_kind.h" +#include "toolchain/parse/typed_nodes.h" namespace Carbon::Parse { @@ -219,6 +220,16 @@ auto Tree::Print(llvm::raw_ostream& output, bool preorder) const -> void { output << " ]\n"; } +static auto TestExtract(const Tree* tree, NodeId node_id, NodeKind kind, + ErrorBuilder* trace) -> bool { + switch (kind) { +#define CARBON_PARSE_NODE_KIND(Name) \ + case NodeKind::Name: \ + return tree->VerifyExtractAs(node_id, trace).has_value(); +#include "toolchain/parse/node_kind.def" + } +} + auto Tree::Verify() const -> ErrorOr { llvm::SmallVector nodes; // Traverse the tree in postorder. @@ -235,6 +246,18 @@ auto Tree::Verify() const -> ErrorOr { return Error(llvm::formatv( "Node #{0} is a placeholder node that wasn't replaced.", n.index)); } + // Should extract successfully if node not marked as having an error. + // Without this code, a 10 mloc test case of lex & parse takes + // 4.129 s ± 0.041 s. With this additional verification, it takes + // 5.768 s ± 0.036 s. + if (!n_impl.has_error && !TestExtract(this, n, n_impl.kind, nullptr)) { + ErrorBuilder trace; + trace << llvm::formatv( + "NodeId #{0} couldn't be extracted as a {1}. Trace:\n", n, + n_impl.kind); + TestExtract(this, n, n_impl.kind, &trace); + return trace; + } int subtree_size = 1; if (n_impl.kind.has_bracket()) { diff --git a/toolchain/parse/tree.h b/toolchain/parse/tree.h index 8b8584294b00..811da401e8e0 100644 --- a/toolchain/parse/tree.h +++ b/toolchain/parse/tree.h @@ -7,6 +7,7 @@ #include +#include "common/check.h" #include "common/error.h" #include "common/ostream.h" #include "llvm/ADT/SmallVector.h" @@ -14,23 +15,13 @@ #include "llvm/ADT/iterator_range.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/node_ids.h" #include "toolchain/parse/node_kind.h" namespace Carbon::Parse { -// A lightweight handle representing a node in the tree. -// -// Objects of this type are small and cheap to copy and store. They don't -// contain any of the information about the node, and serve as a handle that -// can be used with the underlying tree to query for detailed information. -struct NodeId : public IdBase { - // An explicitly invalid instance. - static const NodeId Invalid; - - using IdBase::IdBase; -}; - -constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex); +// Defined in typed_nodes.h. Include that to call `Tree::ExtractFile()`. +struct File; // A tree of parsed tokens based on the language grammar. // @@ -120,6 +111,19 @@ class Tree : public Printable { auto node_subtree_size(NodeId n) const -> int32_t; + // Returns whether this node is a valid node of the specified type. + template + auto IsValid(NodeId node_id) const -> bool { + return node_kind(node_id) == T::Kind && !node_has_error(node_id); + } + + template + auto IsValid(IdT id) const -> bool { + using T = typename NodeForId::TypedNode; + CARBON_DCHECK(node_kind(id) == T::Kind); + return !node_has_error(id); + } + auto packaging_directive() const -> const std::optional& { return packaging_directive_; } @@ -168,14 +172,56 @@ class Tree : public Printable { // line-oriented shell tools from `grep` to `awk`. auto Print(llvm::raw_ostream& output, bool preorder) const -> void; + // The following `Extract*` function provide an alternative way of accessing + // the nodes of a tree. It is intended to be more convenient and type-safe, + // but slower and can't be used on nodes that are marked as having an error. + // It is appropriate for uses that are less performance sensitive, like + // diagnostics. Example usage: + // ``` + // auto file = tree->ExtractFile(); + // for (AnyDeclId decl_id : file.decls) { + // // `decl_id` is convertible to a `NodeId`. + // if (std::optional fn_decl = + // tree->ExtractAs(decl_id)) { + // // fn_decl->params is a `TuplePatternId` (which extends `NodeId`) + // // that is guaranteed to reference a `TuplePattern`. + // std::optional params = tree->Extract(fn_decl->params); + // // `params` has a value unless there was an error in that node. + // } else if (auto class_def = tree->ExtractAs(decl_id)) { + // // ... + // } + // } + // ``` + + // Extract a `File` object representing the parse tree for the whole file. + // #include "toolchain/parse/typed_nodes.h" to get the definition of `File` + // and the types representing its children nodes. + auto ExtractFile() const -> File; + + // Converts this node_id to a typed node of a specified type, if it is a valid + // node of that kind. + template + auto ExtractAs(NodeId node_id) const -> std::optional; + + // Converts to a typed node, if it is not an error. + template + auto Extract(IdT id) const + -> std::optional::TypedNode>; + // Verifies the parse tree structure. Checks invariants of the parse tree // structure and returns verification errors. // - // This is primarily intended to be used as a - // debugging aid. This routine doesn't directly CHECK so that it can be used - // within a debugger. + // This is fairly slow, and is primarily intended to be used as a debugging + // aid. This routine doesn't directly CHECK so that it can be used within a + // debugger. auto Verify() const -> ErrorOr; + // Like ExtractAs(), but malformed tree errors are not fatal. Should only be + // used by `Verify()`. + template + auto VerifyExtractAs(NodeId node_id, ErrorBuilder* trace) const + -> std::optional; + private: friend class Context; @@ -245,6 +291,20 @@ class Tree : public Printable { auto PrintNode(llvm::raw_ostream& output, NodeId n, int depth, bool preorder) const -> bool; + // Extract a node of type `T` from a sibling range. This is expected to + // consume the complete sibling range. Malformed tree errors are written + // to `*trace`, if `trace != nullptr`. + template + auto TryExtractNodeFromChildren( + llvm::iterator_range children, + ErrorBuilder* trace) const -> std::optional; + + // Extract a node of type `T` from a sibling range. This is expected to + // consume the complete sibling range. Malformed tree errors are fatal. + template + auto ExtractNodeFromChildren( + llvm::iterator_range children) const -> T; + // Depth-first postorder sequence of node implementation data. llvm::SmallVector node_impls_; @@ -270,7 +330,7 @@ class Tree : public Printable { class Tree::PostorderIterator : public llvm::iterator_facade_base, + int, const NodeId*, NodeId>, public Printable { public: PostorderIterator() = delete; @@ -322,7 +382,7 @@ class Tree::PostorderIterator class Tree::SiblingIterator : public llvm::iterator_facade_base, + const NodeId*, NodeId>, public Printable { public: explicit SiblingIterator() = delete; @@ -353,6 +413,51 @@ class Tree::SiblingIterator NodeId node_; }; +template +auto Tree::ExtractNodeFromChildren( + llvm::iterator_range children) const -> T { + auto result = TryExtractNodeFromChildren(children, nullptr); + if (!result.has_value()) { + // On error try again, this time capturing a trace. + ErrorBuilder trace; + TryExtractNodeFromChildren(children, &trace); + CARBON_FATAL() << "Malformed parse node:\n" << Error(trace).message(); + } + return *result; +} + +template +auto Tree::ExtractAs(NodeId node_id) const -> std::optional { + static_assert(HasKindMember, "Not a parse node type"); + if (!IsValid(node_id)) { + return std::nullopt; + } + + return ExtractNodeFromChildren(children(node_id)); +} + +template +auto Tree::VerifyExtractAs(NodeId node_id, ErrorBuilder* trace) const + -> std::optional { + static_assert(HasKindMember, "Not a parse node type"); + if (!IsValid(node_id)) { + return std::nullopt; + } + + return TryExtractNodeFromChildren(children(node_id), trace); +} + +template +auto Tree::Extract(IdT id) const + -> std::optional::TypedNode> { + if (!IsValid(id)) { + return std::nullopt; + } + + using T = typename NodeForId::TypedNode; + return ExtractNodeFromChildren(children(id)); +} + } // namespace Carbon::Parse #endif // CARBON_TOOLCHAIN_PARSE_TREE_H_ diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h new file mode 100644 index 000000000000..d7d242f9eca5 --- /dev/null +++ b/toolchain/parse/typed_nodes.h @@ -0,0 +1,909 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_PARSE_TYPED_NODES_H_ +#define CARBON_TOOLCHAIN_PARSE_TYPED_NODES_H_ + +#include "toolchain/parse/node_ids.h" +#include "toolchain/parse/node_kind.h" + +namespace Carbon::Parse { + +// Helpers for defining different kinds of parse nodes. +// ---------------------------------------------------- + +// A pair of a list item and its optional following comma. +template +struct ListItem { + Element value; + std::optional comma; +}; + +// A list of items, parameterized by the kind of the elements and comma. +template +using CommaSeparatedList = llvm::SmallVector>; + +// This class provides a shorthand for defining parse node kinds for leaf nodes. +template +struct LeafNode { + static constexpr auto Kind = KindT.Define(Category); +}; + +// ---------------------------------------------------------------------------- +// Each node kind (in node_kind.def) should have a corresponding type defined +// here which describes the expected child structure of that parse node. +// +// Each of these types should start with a `static constexpr Kind` member +// initialized by calling `Define` on the corresponding `NodeKind`, and passing +// in the `NodeCategory` of that kind. This will both associate the category +// with the node kind and create the necessary kind object for the typed node. +// +// This should be followed by field declarations that describe the child nodes, +// in order, that occur in the parse tree. The `Extract...` functions on the +// parse tree use struct reflection on these fields to guide the extraction of +// the child nodes from the tree into an object of this type with these fields +// for convenient access. +// +// The types of these fields are special and describe the specific child node +// structure of the parse node. Many of these types are defined in `node_ids.h`. +// +// Valid primitive types here are: +// - `NodeId` to match any single child node +// - `FooId` to require that child to have kind `NodeKind::Foo` +// - `AnyCatId` to require that child to have a kind in category `Cat` +// - `NodeIdOneOf` to require the child to have kind `NodeKind::A` or +// `NodeKind::B` +// - `NodeIdNot` to match any single child whose kind is not `NodeKind::A` +// +// There a few, restricted composite field types allowed that compose types in +// various ways, where all of the `T`s and `U`s below are themselves valid field +// types: +// - `llvm::SmallVector` to match any number of children matching `T` +// - `std::optional` to match 0 or 1 children matching `T` +// - `std::tuple` to match children matching `T...` +// - Any provided `Aggregate` type that is a simple aggregate type such as +// `struct Aggregate { T x; U y; }`, +// to match children with types `T` and `U`. +// ---------------------------------------------------------------------------- + +// Error nodes +// ----------- + +// An invalid parse. Used to balance the parse tree. This type is here only to +// ensure we have a type for each parse node kind. This node kind always has an +// error, so can never be extracted. +using InvalidParse = + LeafNode; + +// An invalid subtree. Always has an error so can never be extracted. +using InvalidParseStart = LeafNode; +struct InvalidParseSubtree { + static constexpr auto Kind = + NodeKind::InvalidParseSubtree.Define(NodeCategory::Decl); + + InvalidParseStartId start; + llvm::SmallVector> extra; +}; + +// A placeholder node to be replaced; it will never exist in a valid parse tree. +// Its token kind is not enforced even when valid. +using Placeholder = LeafNode; + +// File nodes +// ---------- + +// The start of the file. +using FileStart = LeafNode; + +// The end of the file. +using FileEnd = LeafNode; + +// General-purpose nodes +// --------------------- + +// An empty declaration, such as `;`. +using EmptyDecl = + LeafNode; + +// A name in a non-expression context, such as a declaration. +using IdentifierName = + LeafNode; + +// A name in an expression context. +using IdentifierNameExpr = + LeafNode; + +// The `self` value and `Self` type identifier keywords. Typically of the form +// `self: Self`. +using SelfValueName = LeafNode; +using SelfValueNameExpr = + LeafNode; +using SelfTypeNameExpr = + LeafNode; + +// The `base` value keyword, introduced by `base: B`. Typically referenced in +// an expression, as in `x.base` or `{.base = ...}`, but can also be used as a +// declared name, as in `{.base: partial B}`. +using BaseName = LeafNode; + +// A qualified name: `A.B`. +// +// TODO: This is not a declaration. Rename this parse node. +struct QualifiedDecl { + static constexpr auto Kind = + NodeKind::QualifiedDecl.Define(NodeCategory::NameComponent); + + // For now, this is either an IdentifierName or a QualifiedDecl. + AnyNameComponentId lhs; + + // TODO: This will eventually need to support more general expressions, for + // example `GenericType(type_args).ChildType(child_type_args).Name`. + IdentifierNameId rhs; +}; + +// Library, package, import +// ------------------------ + +// The `package` keyword in an expression. +using PackageExpr = LeafNode; + +// The name of a package or library for `package`, `import`, and `library`. +using PackageName = LeafNode; +using LibraryName = LeafNode; +using DefaultLibrary = LeafNode; + +using PackageIntroducer = LeafNode; +using PackageApi = LeafNode; +using PackageImpl = LeafNode; + +// `library` in `package` or `import`. +struct LibrarySpecifier { + static constexpr auto Kind = NodeKind::LibrarySpecifier.Define(); + + NodeIdOneOf name; +}; + +// First line of the file, such as: +// `package MyPackage library "MyLibrary" impl;` +struct PackageDirective { + static constexpr auto Kind = NodeKind::PackageDirective.Define(); + + PackageIntroducerId introducer; + std::optional name; + std::optional library; + NodeIdOneOf api_or_impl; +}; + +// `import TheirPackage library "TheirLibrary";` +using ImportIntroducer = LeafNode; +struct ImportDirective { + static constexpr auto Kind = NodeKind::ImportDirective.Define(); + + ImportIntroducerId introducer; + std::optional name; + std::optional library; +}; + +// `library` as directive. +using LibraryIntroducer = LeafNode; +struct LibraryDirective { + static constexpr auto Kind = NodeKind::LibraryDirective.Define(); + + LibraryIntroducerId introducer; + NodeIdOneOf library_name; + NodeIdOneOf api_or_impl; +}; + +// Namespace nodes +// --------------- + +using NamespaceStart = LeafNode; + +// A namespace: `namespace N;`. +struct Namespace { + static constexpr auto Kind = NodeKind::Namespace.Define(NodeCategory::Decl); + + NamespaceStartId introducer; + llvm::SmallVector modifiers; + NodeIdOneOf name; +}; + +// Pattern nodes +// ------------- + +// A pattern binding, such as `name: Type`. +struct BindingPattern { + static constexpr auto Kind = + NodeKind::BindingPattern.Define(NodeCategory::Pattern); + + NodeIdOneOf name; + AnyExprId type; +}; + +// `name:! Type` +struct GenericBindingPattern { + static constexpr auto Kind = + NodeKind::GenericBindingPattern.Define(NodeCategory::Pattern); + + NodeIdOneOf name; + AnyExprId type; +}; + +// An address-of binding: `addr self: Self*`. +struct Address { + static constexpr auto Kind = NodeKind::Address.Define(NodeCategory::Pattern); + + AnyPatternId inner; +}; + +// A template binding: `template T:! type`. +struct Template { + static constexpr auto Kind = NodeKind::Template.Define(NodeCategory::Pattern); + + // This is a GenericBindingPatternId in any valid program. + // TODO: Should the parser enforce that? + AnyPatternId inner; +}; + +using TuplePatternStart = LeafNode; +using PatternListComma = LeafNode; + +// A parameter list or tuple pattern: `(a: i32, b: i32)`. +struct TuplePattern { + static constexpr auto Kind = + NodeKind::TuplePattern.Define(NodeCategory::Pattern); + + TuplePatternStartId left_paren; + CommaSeparatedList params; +}; + +using ImplicitParamListStart = LeafNode; + +// An implicit parameter list: `[T:! type, self: Self]`. +struct ImplicitParamList { + static constexpr auto Kind = NodeKind::ImplicitParamList.Define(); + + ImplicitParamListStartId left_square; + CommaSeparatedList params; +}; + +// Function nodes +// -------------- + +using FunctionIntroducer = LeafNode; + +// A return type: `-> i32`. +struct ReturnType { + static constexpr auto Kind = NodeKind::ReturnType.Define(); + + AnyExprId type; +}; + +// A function signature: `fn F() -> i32`. +template +struct FunctionSignature { + static constexpr auto Kind = KindT.Define(NodeCategory::Decl); + + FunctionIntroducerId introducer; + llvm::SmallVector modifiers; + // For now, this is either an IdentifierName or a QualifiedDecl. + AnyNameComponentId name; + std::optional implicit_params; + TuplePatternId params; + std::optional return_type; +}; + +using FunctionDecl = FunctionSignature; +using FunctionDefinitionStart = + FunctionSignature; + +// A function definition: `fn F() -> i32 { ... }`. +struct FunctionDefinition { + static constexpr auto Kind = + NodeKind::FunctionDefinition.Define(NodeCategory::Decl); + + FunctionDefinitionStartId signature; + llvm::SmallVector body; +}; + +// `let` nodes +// ----------- + +using LetIntroducer = LeafNode; +using LetInitializer = LeafNode; + +// A `let` declaration: `let a: i32 = 5;`. +struct LetDecl { + static constexpr auto Kind = + NodeKind::LetDecl.Define(NodeCategory::Decl | NodeCategory::Statement); + + LetIntroducerId introducer; + llvm::SmallVector modifiers; + AnyPatternId pattern; + LetInitializerId equals; + AnyExprId initializer; +}; + +// `var` nodes +// ----------- + +using VariableIntroducer = LeafNode; +using ReturnedModifier = LeafNode; +using VariableInitializer = LeafNode; + +// A `var` declaration: `var a: i32;` or `var a: i32 = 5;`. +struct VariableDecl { + static constexpr auto Kind = NodeKind::VariableDecl.Define( + NodeCategory::Decl | NodeCategory::Statement); + + VariableIntroducerId introducer; + llvm::SmallVector modifiers; + std::optional returned; + AnyPatternId pattern; + + struct Initializer { + VariableInitializerId equals; + AnyExprId value; + }; + std::optional initializer; +}; + +// Statement nodes +// --------------- + +using CodeBlockStart = LeafNode; + +// A code block: `{ statement; statement; ... }`. +struct CodeBlock { + static constexpr auto Kind = NodeKind::CodeBlock.Define(); + + CodeBlockStartId left_brace; + llvm::SmallVector statements; +}; + +// An expression statement: `F(x);`. +struct ExprStatement { + static constexpr auto Kind = + NodeKind::ExprStatement.Define(NodeCategory::Statement); + + AnyExprId expr; +}; + +using BreakStatementStart = LeafNode; + +// A break statement: `break;`. +struct BreakStatement { + static constexpr auto Kind = + NodeKind::BreakStatement.Define(NodeCategory::Statement); + + BreakStatementStartId introducer; +}; + +using ContinueStatementStart = LeafNode; + +// A continue statement: `continue;`. +struct ContinueStatement { + static constexpr auto Kind = + NodeKind::ContinueStatement.Define(NodeCategory::Statement); + + ContinueStatementStartId introducer; +}; + +using ReturnStatementStart = LeafNode; +using ReturnVarModifier = LeafNode; + +// A return statement: `return;` or `return expr;` or `return var;`. +struct ReturnStatement { + static constexpr auto Kind = + NodeKind::ReturnStatement.Define(NodeCategory::Statement); + + ReturnStatementStartId introducer; + std::optional expr; + std::optional var; +}; + +using ForHeaderStart = LeafNode; + +// The `var ... in` portion of a `for` statement. +struct ForIn { + static constexpr auto Kind = NodeKind::ForIn.Define(); + + VariableIntroducerId introducer; + AnyPatternId pattern; +}; + +// The `for (var ... in ...)` portion of a `for` statement. +struct ForHeader { + static constexpr auto Kind = NodeKind::ForHeader.Define(); + + ForHeaderStartId introducer; + ForInId var; + AnyExprId range; +}; + +// A complete `for (...) { ... }` statement. +struct ForStatement { + static constexpr auto Kind = + NodeKind::ForStatement.Define(NodeCategory::Statement); + + ForHeaderId header; + CodeBlockId body; +}; + +using IfConditionStart = LeafNode; + +// The condition portion of an `if` statement: `(expr)`. +struct IfCondition { + static constexpr auto Kind = NodeKind::IfCondition.Define(); + + IfConditionStartId left_paren; + AnyExprId condition; +}; + +using IfStatementElse = LeafNode; + +// An `if` statement: `if (expr) { ... } else { ... }`. +struct IfStatement { + static constexpr auto Kind = + NodeKind::IfStatement.Define(NodeCategory::Statement); + + IfConditionId head; + CodeBlockId then; + + struct Else { + IfStatementElseId else_token; + NodeIdOneOf body; + }; + std::optional else_clause; +}; + +using WhileConditionStart = LeafNode; + +// The condition portion of a `while` statement: `(expr)`. +struct WhileCondition { + static constexpr auto Kind = NodeKind::WhileCondition.Define(); + + WhileConditionStartId left_paren; + AnyExprId condition; +}; + +// A `while` statement: `while (expr) { ... }`. +struct WhileStatement { + static constexpr auto Kind = + NodeKind::WhileStatement.Define(NodeCategory::Statement); + + WhileConditionId head; + CodeBlockId body; +}; + +// Expression nodes +// ---------------- + +using ArrayExprStart = LeafNode; + +// The start of an array type, `[i32;`. +// +// TODO: Consider flattening this into `ArrayExpr`. +struct ArrayExprSemi { + static constexpr auto Kind = NodeKind::ArrayExprSemi.Define(); + + ArrayExprStartId left_square; + AnyExprId type; +}; + +// An array type, such as `[i32; 3]` or `[i32;]`. +struct ArrayExpr { + static constexpr auto Kind = NodeKind::ArrayExpr.Define(NodeCategory::Expr); + + ArrayExprSemiId start; + std::optional bound; +}; + +// The opening portion of an indexing expression: `a[`. +// +// TODO: Consider flattening this into `IndexExpr`. +struct IndexExprStart { + static constexpr auto Kind = NodeKind::IndexExprStart.Define(); + + AnyExprId sequence; +}; + +// An indexing expression, such as `a[1]`. +struct IndexExpr { + static constexpr auto Kind = NodeKind::IndexExpr.Define(NodeCategory::Expr); + + IndexExprStartId start; + AnyExprId index; +}; + +using ExprOpenParen = LeafNode; + +// A parenthesized expression: `(a)`. +struct ParenExpr { + static constexpr auto Kind = NodeKind::ParenExpr.Define(NodeCategory::Expr); + + ExprOpenParenId left_paren; + AnyExprId expr; +}; + +using TupleLiteralComma = LeafNode; + +// A tuple literal: `()`, `(a, b, c)`, or `(a,)`. +struct TupleLiteral { + static constexpr auto Kind = + NodeKind::TupleLiteral.Define(NodeCategory::Expr); + + ExprOpenParenId left_paren; + CommaSeparatedList elements; +}; + +// The opening portion of a call expression: `F(`. +// +// TODO: Consider flattening this into `CallExpr`. +struct CallExprStart { + static constexpr auto Kind = NodeKind::CallExprStart.Define(); + + AnyExprId callee; +}; + +using CallExprComma = LeafNode; + +// A call expression: `F(a, b, c)`. +struct CallExpr { + static constexpr auto Kind = NodeKind::CallExpr.Define(NodeCategory::Expr); + + CallExprStartId start; + CommaSeparatedList arguments; +}; + +// A simple member access expression: `a.b`. +struct MemberAccessExpr { + static constexpr auto Kind = + NodeKind::MemberAccessExpr.Define(NodeCategory::Expr); + + AnyExprId lhs; + // TODO: Figure out which nodes can appear here + NodeId rhs; +}; + +// A simple indirect member access expression: `a->b`. +struct PointerMemberAccessExpr { + static constexpr auto Kind = + NodeKind::PointerMemberAccessExpr.Define(NodeCategory::Expr); + + AnyExprId lhs; + // TODO: Figure out which nodes can appear here + NodeId rhs; +}; + +// A prefix operator expression. +template +struct PrefixOperator { + static constexpr auto Kind = KindT.Define(NodeCategory::Expr); + + AnyExprId operand; +}; + +// An infix operator expression. +template +struct InfixOperator { + static constexpr auto Kind = KindT.Define(NodeCategory::Expr); + + AnyExprId lhs; + AnyExprId rhs; +}; + +// A postfix operator expression. +template +struct PostfixOperator { + static constexpr auto Kind = KindT.Define(NodeCategory::Expr); + + AnyExprId operand; +}; + +// Literals, operators, and modifiers + +#define CARBON_PARSE_NODE_KIND(...) +#define CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(Name, ...) \ + using Name = LeafNode; +#define CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Name, ...) \ + using Name##Modifier = \ + LeafNode; +#define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name, ...) \ + using PrefixOperator##Name = PrefixOperator; +#define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name, ...) \ + using InfixOperator##Name = InfixOperator; +#define CARBON_PARSE_NODE_KIND_POSTFIX_OPERATOR(Name, ...) \ + using PostfixOperator##Name = \ + PostfixOperator; +#include "toolchain/parse/node_kind.def" + +// The first operand of a short-circuiting infix operator: `a and` or `a or`. +// The complete operator expression will be an InfixOperator with this as the +// `lhs`. +// TODO: Make this be a template if we ever need to write generic code to cover +// both cases at once, say in check. +struct ShortCircuitOperandAnd { + static constexpr auto Kind = NodeKind::ShortCircuitOperandAnd.Define(); + + AnyExprId operand; +}; + +struct ShortCircuitOperandOr { + static constexpr auto Kind = NodeKind::ShortCircuitOperandOr.Define(); + + AnyExprId operand; +}; + +struct ShortCircuitOperatorAnd { + static constexpr auto Kind = + NodeKind::ShortCircuitOperatorAnd.Define(NodeCategory::Expr); + + ShortCircuitOperandAndId lhs; + AnyExprId rhs; +}; + +struct ShortCircuitOperatorOr { + static constexpr auto Kind = + NodeKind::ShortCircuitOperatorOr.Define(NodeCategory::Expr); + + ShortCircuitOperandOrId lhs; + AnyExprId rhs; +}; + +// The `if` portion of an `if` expression: `if expr`. +struct IfExprIf { + static constexpr auto Kind = NodeKind::IfExprIf.Define(); + + AnyExprId condition; +}; + +// The `then` portion of an `if` expression: `then expr`. +struct IfExprThen { + static constexpr auto Kind = NodeKind::IfExprThen.Define(); + + AnyExprId result; +}; + +// A full `if` expression: `if expr then expr else expr`. +struct IfExprElse { + static constexpr auto Kind = NodeKind::IfExprElse.Define(NodeCategory::Expr); + + IfExprIfId start; + IfExprThenId then; + AnyExprId else_result; +}; + +// Struct literals and struct type literals +// ---------------------------------------- + +// `{` +using StructLiteralOrStructTypeLiteralStart = + LeafNode; +// `,` +using StructComma = LeafNode; + +// `.a` +struct StructFieldDesignator { + static constexpr auto Kind = NodeKind::StructFieldDesignator.Define(); + + NodeIdOneOf name; +}; + +// `.a = 0` +struct StructFieldValue { + static constexpr auto Kind = NodeKind::StructFieldValue.Define(); + + StructFieldDesignatorId designator; + AnyExprId expr; +}; + +// `.a: i32` +struct StructFieldType { + static constexpr auto Kind = NodeKind::StructFieldType.Define(); + + StructFieldDesignatorId designator; + AnyExprId type_expr; +}; + +// Struct literals, such as `{.a = 0}`. +struct StructLiteral { + static constexpr auto Kind = + NodeKind::StructLiteral.Define(NodeCategory::Expr); + + StructLiteralOrStructTypeLiteralStartId introducer; + CommaSeparatedList fields; +}; + +// Struct type literals, such as `{.a: i32}`. +struct StructTypeLiteral { + static constexpr auto Kind = + NodeKind::StructTypeLiteral.Define(NodeCategory::Expr); + + StructLiteralOrStructTypeLiteralStartId introducer; + CommaSeparatedList fields; +}; + +// `class` declarations and definitions +// ------------------------------------ + +// `class` +using ClassIntroducer = LeafNode; + +// A class signature `class C` +template +struct ClassSignature { + static constexpr auto Kind = KindT.Define(Category); + + ClassIntroducerId introducer; + llvm::SmallVector modifiers; + AnyNameComponentId name; + std::optional implicit_params; + std::optional params; +}; + +// `class C;` +using ClassDecl = ClassSignature; +// `class C {` +using ClassDefinitionStart = + ClassSignature; + +// `class C { ... }` +struct ClassDefinition { + static constexpr auto Kind = + NodeKind::ClassDefinition.Define(NodeCategory::Decl); + + ClassDefinitionStartId signature; + llvm::SmallVector members; +}; + +// Base class declaration +// ---------------------- + +// `base` +using BaseIntroducer = LeafNode; +using BaseColon = LeafNode; +// `extend base: BaseClass;` +struct BaseDecl { + static constexpr auto Kind = NodeKind::BaseDecl.Define(NodeCategory::Decl); + + BaseIntroducerId introducer; + llvm::SmallVector modifiers; + BaseColonId colon; + AnyExprId base_class; +}; + +// Interface declarations and definitions +// -------------------------------------- + +// `interface` +using InterfaceIntroducer = LeafNode; + +// `interface I` +template +struct InterfaceSignature { + static constexpr auto Kind = KindT.Define(Category); + + InterfaceIntroducerId introducer; + llvm::SmallVector modifiers; + AnyNameComponentId name; + std::optional implicit_params; + std::optional params; +}; + +// `interface I;` +using InterfaceDecl = + InterfaceSignature; +// `interface I {` +using InterfaceDefinitionStart = + InterfaceSignature; + +// `interface I { ... }` +struct InterfaceDefinition { + static constexpr auto Kind = + NodeKind::InterfaceDefinition.Define(NodeCategory::Decl); + + InterfaceDefinitionStartId signature; + llvm::SmallVector members; +}; + +// `impl`...`as` declarations and definitions +// ------------------------------------------ + +// `impl` +using ImplIntroducer = LeafNode; +// `as` +using ImplAs = LeafNode; + +// `forall [...]` +struct ImplForall { + static constexpr auto Kind = NodeKind::ImplForall.Define(); + + ImplicitParamListId params; +}; + +// `impl T as I` +template +struct ImplSignature { + static constexpr auto Kind = KindT.Define(Category); + + ImplIntroducerId introducer; + llvm::SmallVector modifiers; + std::optional forall; + std::optional type_expr; + ImplAsId as; + AnyExprId interface; +}; + +// `impl T as I;` +using ImplDecl = ImplSignature; +// `impl T as I {` +using ImplDefinitionStart = + ImplSignature; + +// `impl T as I { ... }` +struct ImplDefinition { + static constexpr auto Kind = + NodeKind::ImplDefinition.Define(NodeCategory::Decl); + + ImplDefinitionStartId signature; + llvm::SmallVector members; +}; + +// Named constraint declarations and definitions +// --------------------------------------------- + +// `constraint` +using NamedConstraintIntroducer = LeafNode; + +// `constraint NC` +template +struct NamedConstraintSignature { + static constexpr auto Kind = KindT.Define(Category); + + NamedConstraintIntroducerId introducer; + llvm::SmallVector modifiers; + AnyNameComponentId name; + std::optional implicit_params; + std::optional params; +}; + +// `constraint NC;` +using NamedConstraintDecl = + NamedConstraintSignature; +// `constraint NC {` +using NamedConstraintDefinitionStart = + NamedConstraintSignature; + +// `constraint NC { ... }` +struct NamedConstraintDefinition { + static constexpr auto Kind = + NodeKind::NamedConstraintDefinition.Define(NodeCategory::Decl); + + NamedConstraintDefinitionStartId signature; + llvm::SmallVector members; +}; + +// --------------------------------------------------------------------------- + +// A complete source file. Note that there is no corresponding parse node for +// the file. The file is instead the complete contents of the parse tree. +struct File { + FileStartId start; + llvm::SmallVector decls; + FileEndId end; +}; + +// Define `Foo` as the node type for the ID type `FooId`. +#define CARBON_PARSE_NODE_KIND(KindName) \ + template <> \ + struct NodeForId { \ + using TypedNode = KindName; \ + }; +#include "toolchain/parse/node_kind.def" + +} // namespace Carbon::Parse + +#endif // CARBON_TOOLCHAIN_PARSE_TYPED_NODES_H_ diff --git a/toolchain/parse/typed_nodes_test.cpp b/toolchain/parse/typed_nodes_test.cpp new file mode 100644 index 000000000000..6094d0fb41c1 --- /dev/null +++ b/toolchain/parse/typed_nodes_test.cpp @@ -0,0 +1,152 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/parse/typed_nodes.h" + +#include +#include + +#include + +#include "toolchain/diagnostics/mocks.h" +#include "toolchain/lex/lex.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/tree.h" + +namespace Carbon::Parse { +namespace { + +// Check that each node kind defines a Kind member using the correct +// NodeKind enumerator. +#define CARBON_PARSE_NODE_KIND(Name) \ + static_assert(Name::Kind == NodeKind::Name, #Name); +#include "toolchain/parse/node_kind.def" + +class TypedNodeTest : public ::testing::Test { + protected: + auto GetSourceBuffer(llvm::StringRef t) -> SourceBuffer& { + CARBON_CHECK(fs_.addFile("test.carbon", /*ModificationTime=*/0, + llvm::MemoryBuffer::getMemBuffer(t))); + source_storage_.push_front(std::move( + *SourceBuffer::CreateFromFile(fs_, "test.carbon", consumer_))); + return source_storage_.front(); + } + + auto GetTokenizedBuffer(llvm::StringRef t) -> Lex::TokenizedBuffer& { + token_storage_.push_front( + Lex::Lex(value_stores_, GetSourceBuffer(t), consumer_)); + return token_storage_.front(); + } + + auto GetTree(llvm::StringRef t) -> Tree& { + tree_storage_.push_front(Tree::Parse(GetTokenizedBuffer(t), consumer_, + /*vlog_stream=*/nullptr)); + return tree_storage_.front(); + } + + SharedValueStores value_stores_; + llvm::vfs::InMemoryFileSystem fs_; + std::forward_list source_storage_; + std::forward_list token_storage_; + std::forward_list tree_storage_; + DiagnosticConsumer& consumer_ = ConsoleDiagnosticConsumer(); +}; + +TEST_F(TypedNodeTest, Empty) { + auto* tree = &GetTree(""); + auto file = tree->ExtractFile(); + + EXPECT_TRUE(tree->IsValid(file.start)); + EXPECT_TRUE(tree->ExtractAs(file.start).has_value()); + EXPECT_TRUE(tree->Extract(file.start).has_value()); + + EXPECT_TRUE(tree->IsValid(file.end)); + EXPECT_TRUE(tree->ExtractAs(file.end).has_value()); + EXPECT_TRUE(tree->Extract(file.end).has_value()); + + EXPECT_FALSE(tree->IsValid(file.start)); + EXPECT_FALSE(tree->ExtractAs(file.start).has_value()); +} + +TEST_F(TypedNodeTest, Function) { + auto* tree = &GetTree(R"carbon( + fn F() {} + virtual fn G() -> i32; + )carbon"); + auto file = tree->ExtractFile(); + + ASSERT_EQ(file.decls.size(), 2); + + auto f_fn = tree->ExtractAs(file.decls[0]); + ASSERT_TRUE(f_fn.has_value()); + auto f_sig = tree->Extract(f_fn->signature); + ASSERT_TRUE(f_sig.has_value()); + EXPECT_FALSE(f_sig->return_type.has_value()); + EXPECT_TRUE(f_sig->modifiers.empty()); + + auto g_fn = tree->ExtractAs(file.decls[1]); + ASSERT_TRUE(g_fn.has_value()); + EXPECT_TRUE(g_fn->return_type.has_value()); + EXPECT_FALSE(g_fn->modifiers.empty()); +} + +TEST_F(TypedNodeTest, ModifierOrder) { + auto* tree = &GetTree(R"carbon( + private abstract virtual default interface I; + )carbon"); + auto file = tree->ExtractFile(); + + ASSERT_EQ(file.decls.size(), 1); + + auto decl = tree->ExtractAs(file.decls[0]); + ASSERT_TRUE(decl.has_value()); + ASSERT_EQ(decl->modifiers.size(), 4); + // Note that the order here matches the source order, but is reversed from + // sibling iteration order. + ASSERT_TRUE(tree->ExtractAs(decl->modifiers[0]).has_value()); + ASSERT_TRUE( + tree->ExtractAs(decl->modifiers[1]).has_value()); + ASSERT_TRUE(tree->ExtractAs(decl->modifiers[2]).has_value()); + ASSERT_TRUE(tree->ExtractAs(decl->modifiers[3]).has_value()); +} + +TEST_F(TypedNodeTest, For) { + auto* tree = &GetTree(R"carbon( + fn F(arr: [i32; 5]) { + for (var v: i32 in arr) { + Print(v); + } + } + )carbon"); + auto file = tree->ExtractFile(); + + ASSERT_EQ(file.decls.size(), 1); + auto fn = tree->ExtractAs(file.decls[0]); + ASSERT_TRUE(fn.has_value()); + ASSERT_EQ(fn->body.size(), 1); + auto for_stmt = tree->ExtractAs(fn->body[0]); + ASSERT_TRUE(for_stmt.has_value()); + auto for_header = tree->Extract(for_stmt->header); + ASSERT_TRUE(for_header.has_value()); + auto for_var = tree->Extract(for_header->var); + ASSERT_TRUE(for_var.has_value()); + auto for_var_binding = tree->ExtractAs(for_var->pattern); + ASSERT_TRUE(for_var_binding.has_value()); + auto for_var_name = tree->ExtractAs(for_var_binding->name); + ASSERT_TRUE(for_var_name.has_value()); +} + +auto CategoryMatches(const NodeKind::Definition& def, NodeKind kind, + const char* name) { + EXPECT_EQ(def.category(), kind.category()) << name; +} + +TEST_F(TypedNodeTest, CategoryMatches) { +#define CARBON_PARSE_NODE_KIND(Name) \ + CategoryMatches(Name::Kind, NodeKind::Name, #Name); +#include "toolchain/parse/node_kind.def" +} + +} // namespace +} // namespace Carbon::Parse diff --git a/toolchain/sem_ir/inst.h b/toolchain/sem_ir/inst.h index 6f20f75a866c..e005f2985922 100644 --- a/toolchain/sem_ir/inst.h +++ b/toolchain/sem_ir/inst.h @@ -27,7 +27,7 @@ struct TypedInstArgsInfo { using Tuple = decltype(StructReflection::AsTuple(std::declval())); static constexpr int FirstArgField = - HasParseNode + HasTypeId; + HasParseNodeMember + HasTypeIdMember; static constexpr int NumArgs = std::tuple_size_v - FirstArgField; static_assert(NumArgs <= 2, @@ -73,10 +73,10 @@ class Inst : public Printable { type_id_(TypeId::Invalid), arg0_(InstId::InvalidIndex), arg1_(InstId::InvalidIndex) { - if constexpr (HasParseNode) { + if constexpr (HasParseNodeMember) { parse_node_ = typed_inst.parse_node; } - if constexpr (HasTypeId) { + if constexpr (HasTypeIdMember) { type_id_ = typed_inst.type_id; } if constexpr (Info::NumArgs > 0) { @@ -100,7 +100,7 @@ class Inst : public Printable { CARBON_CHECK(Is()) << "Casting inst of kind " << kind() << " to wrong kind " << TypedInst::Kind; auto build_with_type_id_and_args = [&](auto... type_id_and_args) { - if constexpr (HasParseNode) { + if constexpr (HasParseNodeMember) { return TypedInst{parse_node(), type_id_and_args...}; } else { return TypedInst{type_id_and_args...}; @@ -108,7 +108,7 @@ class Inst : public Printable { }; auto build_with_args = [&](auto... args) { - if constexpr (HasTypeId) { + if constexpr (HasTypeIdMember) { return build_with_type_id_and_args(type_id(), args...); } else { return build_with_type_id_and_args(args...); diff --git a/toolchain/sem_ir/inst_kind.cpp b/toolchain/sem_ir/inst_kind.cpp index 8cd8b774c056..56193b27f945 100644 --- a/toolchain/sem_ir/inst_kind.cpp +++ b/toolchain/sem_ir/inst_kind.cpp @@ -20,7 +20,7 @@ auto InstKind::ir_name() const -> llvm::StringLiteral { auto InstKind::value_kind() const -> InstValueKind { static constexpr InstValueKind Table[] = { #define CARBON_SEM_IR_INST_KIND(Name) \ - HasTypeId ? InstValueKind::Typed : InstValueKind::None, + HasTypeIdMember ? InstValueKind::Typed : InstValueKind::None, #include "toolchain/sem_ir/inst_kind.def" }; return Table[AsInt()]; diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index c56de856f68b..76d0b89c700c 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -616,17 +616,17 @@ struct VarStorage { NameId name_id; }; -// HasParseNode is true if T has a `Parse::NodeId parse_node` field. +// HasParseNodeMember is true if T has a `Parse::NodeId parse_node` field. template -inline constexpr bool HasParseNode = false; +inline constexpr bool HasParseNodeMember = false; template -inline constexpr bool HasParseNode = true; +inline constexpr bool HasParseNodeMember = true; -// HasTypeId is true if T has a `TypeId type_id` field. +// HasTypeIdMember is true if T has a `TypeId type_id` field. template -inline constexpr bool HasTypeId = false; +inline constexpr bool HasTypeIdMember = false; template -inline constexpr bool HasTypeId = true; +inline constexpr bool HasTypeIdMember = true; } // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/typed_insts_test.cpp b/toolchain/sem_ir/typed_insts_test.cpp index f1673431108b..df83c74dbd95 100644 --- a/toolchain/sem_ir/typed_insts_test.cpp +++ b/toolchain/sem_ir/typed_insts_test.cpp @@ -43,10 +43,10 @@ template auto CommonFieldOrder() -> void { Inst inst = MakeInstWithNumberedFields(TypedInst::Kind); auto typed = inst.As(); - if constexpr (HasParseNode) { + if constexpr (HasParseNodeMember) { EXPECT_EQ(typed.parse_node, Parse::NodeId(1)); } - if constexpr (HasTypeId) { + if constexpr (HasTypeIdMember) { EXPECT_EQ(typed.type_id, TypeId(2)); } } @@ -77,7 +77,8 @@ auto RoundTrip() -> void { auto typed1 = inst1.As(); Inst inst2 = typed1; - ExpectEqInsts(inst1, inst2, HasParseNode, HasTypeId); + ExpectEqInsts(inst1, inst2, HasParseNodeMember, + HasTypeIdMember); // If the typed instruction has no padding, we should get exactly the same // thing if we convert back from an instruction. @@ -129,8 +130,8 @@ auto StructLayout() -> void { if constexpr (std::has_unique_object_representations_v) { auto typed = MakeInstWithNumberedFields(TypedInst::Kind).template As(); - StructLayoutHelper(&typed, sizeof(typed), HasParseNode, - HasTypeId); + StructLayoutHelper(&typed, sizeof(typed), HasParseNodeMember, + HasTypeIdMember); } }