mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-02 17:52:58 +01:00
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>
178 lines
5.4 KiB
C++
178 lines
5.4 KiB
C++
// 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_COMMON_STRUCT_REFLECTION_H_
|
|
#define CARBON_COMMON_STRUCT_REFLECTION_H_
|
|
|
|
// Reflection support for simple struct types.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// ```
|
|
// struct A { int x; std::string y; };
|
|
//
|
|
// A a;
|
|
// std::tuple<int, std::string> t = StructReflection::AsTuple(a);
|
|
// ```
|
|
//
|
|
// Limitations:
|
|
//
|
|
// - 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 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.
|
|
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
|
|
namespace Carbon::StructReflection {
|
|
|
|
namespace Internal {
|
|
|
|
// A type that can be converted to any field type within type T.
|
|
template <typename T>
|
|
struct AnyField {
|
|
template <typename FieldT>
|
|
// NOLINTNEXTLINE(google-explicit-constructor)
|
|
operator FieldT&() const;
|
|
|
|
template <typename FieldT>
|
|
// NOLINTNEXTLINE(google-explicit-constructor)
|
|
operator FieldT&&() const;
|
|
|
|
// Don't allow conversion to T itself. This ensures we don't match against a
|
|
// copy or move constructor.
|
|
operator T&() const = delete;
|
|
operator T&&() const = delete;
|
|
};
|
|
|
|
// The detection mechanism below intentionally misses field initializers.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
|
|
|
// Detector for whether we can list-initialize T from the given list of fields.
|
|
template <typename T, typename... Fields>
|
|
constexpr auto CanListInitialize(decltype(T{Fields()...})* /*unused*/) -> bool {
|
|
return true;
|
|
}
|
|
template <typename T, typename... Fields>
|
|
constexpr auto CanListInitialize(...) -> bool {
|
|
return false;
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
// Simple detector to find the number of data fields in a struct. This proceeds
|
|
// in two passes:
|
|
//
|
|
// 1) Add AnyField<T>s until we can initialize T from our list of initializers.
|
|
// 2) Add more AnyField<T>s until we can't initialize any more.
|
|
template <typename T, bool AnyWorkedSoFar = false, typename... Fields>
|
|
constexpr auto CountFields() -> int {
|
|
if constexpr (CanListInitialize<T, Fields...>(0)) {
|
|
return CountFields<T, true, Fields..., AnyField<T>>();
|
|
} else if constexpr (AnyWorkedSoFar) {
|
|
// 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) {
|
|
// If we go too far without finding a working initializer, something
|
|
// probably went wrong with our calculation. Bail out before we recurse too
|
|
// deeply.
|
|
static_assert(sizeof...(Fields) <= 32,
|
|
"Internal error, could not count fields in struct");
|
|
} else {
|
|
return CountFields<T, false, Fields..., AnyField<T>>();
|
|
}
|
|
}
|
|
|
|
// Utility to access fields by index.
|
|
template <int NumFields>
|
|
struct FieldAccessor;
|
|
|
|
template <>
|
|
struct FieldAccessor<0> {
|
|
template <typename T>
|
|
static auto Get(T& /*value*/) -> auto {
|
|
return std::tuple<>();
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<1> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0] = value;
|
|
return std::tuple<decltype(field0)>(field0);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<2> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1] = value;
|
|
return std::tuple<decltype(field0), decltype(field1)>(field0, field1);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<3> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2)>(
|
|
field0, field1, field2);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<4> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3)>(field0, field1, field2, field3);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<5> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4)>(
|
|
field0, field1, field2, field3, field4);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct FieldAccessor<6> {
|
|
template <typename T>
|
|
static auto Get(T& value) -> auto {
|
|
auto& [field0, field1, field2, field3, field4, field5] = value;
|
|
return std::tuple<decltype(field0), decltype(field1), decltype(field2),
|
|
decltype(field3), decltype(field4), decltype(field5)>(
|
|
field0, field1, field2, field3, field4, field5);
|
|
}
|
|
};
|
|
|
|
} // namespace Internal
|
|
|
|
// Get the fields of the struct `T` as a tuple.
|
|
template <typename T>
|
|
auto AsTuple(T value) -> auto {
|
|
// We use aggregate initialization to detect the number of fields.
|
|
static_assert(std::is_aggregate_v<T>, "Only aggregates are supported");
|
|
return Internal::FieldAccessor<Internal::CountFields<T>()>::Get(value);
|
|
}
|
|
|
|
} // namespace Carbon::StructReflection
|
|
|
|
#endif // CARBON_COMMON_STRUCT_REFLECTION_H_
|