Typed wrappers around parse tree nodes (#3534)

These are intended to allow the structure of a parse tree node to be
described more precisely in code, to support these use cases:

- Automated checking that the parse tree conforms to the expected
structure. (Added to `Tree::Verify`.)
- Easier reading and understanding of the structure of the parse tree by
toolchain developers. (See `parse/typed_nodes.h`.)
- Easier navigation of the parse tree, for example for tooling uses and
for use when forming diagnostics.

On this last point, an object representing the file may be inspecting
using `Tree::ExtractFile`, as in:
```
auto file = tree->ExtractFile();
for (AnyDeclId decl_id : file.decls) {
  // `decl_id` is convertible to a `NodeId`.
  if (std::optional<FunctionDecl> fn_decl =
      tree->ExtractAs<FunctionDecl>(decl_id)) {
    // fn_decl->params is a `TuplePatternId` (which extends `NodeId`)
    // that is guaranteed to reference a `TuplePattern`.
    std::optional<TuplePattern> 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<ClassDefinition>(decl_id)) {
    // ...
  }
}
```

The `Extract...` functions collect the child nodes into the typed parse
node's fields (internally using a `Tree::SiblingIterator`) for easy
access. However, this is not as fast as directly observing the tree
structure using the postorder strategy being used by the check stage.

These functions rely on using struct reflection on the typed parse node
definitions from `parse/typed_nodes.h` to get the expected structure of
child nodes and then populate them.

Note that validating these in `Tree::Verify` adds significant cost to
it, and is currently included in the parsing stage. Without this change,
a 10 mloc test case of lex & parse takes 4.129 s ± 0.041 s. With this
change, it takes 5.768 s ± 0.036 s.

This builds upon and completes #3393.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
josh11b
2023-12-22 22:14:11 +00:00
committed by GitHub
co-authored by Richard Smith Chandler Carruth
parent fe24ebc021
commit 2e97f27b8d
38 changed files with 1865 additions and 76 deletions
+23
View File
@@ -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<Name>(node_id, trace).has_value();
#include "toolchain/parse/node_kind.def"
}
}
auto Tree::Verify() const -> ErrorOr<Success> {
llvm::SmallVector<NodeId> nodes;
// Traverse the tree in postorder.
@@ -235,6 +246,18 @@ auto Tree::Verify() const -> ErrorOr<Success> {
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()) {