// 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_EXPLORER_AST_VALUE_TRANSFORM_H_ #define CARBON_EXPLORER_AST_VALUE_TRANSFORM_H_ #include "common/error.h" #include "explorer/ast/expression_category.h" #include "explorer/ast/value.h" namespace Carbon { // Constructs a T instance by direct-list-initialization from the given // components (which must have been produced by Decompose). template auto ConstructFromComponents(Args&&... args) -> decltype(T{std::declval()...}) { return T{std::forward(args)...}; } // Overload of the above to accommodate the case where T is an aggregate and // has a CRTP base class, in which case the initializer list must start with // an empty initializer for the base class. template auto ConstructFromComponents(Args&&... args) -> decltype(T{{}, std::declval()...}) { return T{{}, std::forward(args)...}; } template constexpr bool IsConstructibleFromComponentsImpl = false; template constexpr bool IsConstructibleFromComponentsImpl< T, decltype(ConstructFromComponents(std::declval()...)), Args...> = true; // A no-op visitor used to implement `IsRecursivelyTransformable`. The // `operator()` function returns `true_type` if it's called with arguments that // can be used to direct-list-initialize `T`, and `false_type` otherwise. template struct IsRecursivelyTransformableVisitor { template auto operator()(Args&&... args) -> std::integral_constant< bool, IsConstructibleFromComponentsImpl>; }; // A type trait that indicates whether `T` is transformable. A transformable // type provides a function // // template void Decompose(F f) const; // // that takes a callable `f` and passes it an argument list that can be passed // to the constructor of `T` to create an equivalent value. template constexpr bool IsRecursivelyTransformable = false; template // NOLINTNEXTLINE(misc-definitions-in-headers) constexpr bool IsRecursivelyTransformable< T, decltype(std::declval().Decompose( IsRecursivelyTransformableVisitor{}))> = true; // Unwrapper for the case where there's nothing to unwrap. class NoOpUnwrapper { public: template auto UnwrapOr(T&& value, const U&) -> T { return std::forward(value); } template auto Wrap(T&& value) -> T&& { return std::forward(value); } constexpr bool failed() const { return false; } }; // Helper to temporarily unwrap the ErrorOr around a value, and then put it // back when we're done with the overall computation. class ErrorUnwrapper { public: // Unwrap the `ErrorOr` from the given value, or collect the error and return // the given fallback value on failure. template auto UnwrapOr(ErrorOr value, const U& fallback) -> T { if (!value.ok()) { status_ = std::move(value).error(); return fallback; } return std::move(*value); } template auto UnwrapOr(T&& value, const U&) -> T { return std::forward(value); } // Wrap the given value into `ErrorOr`, returning our collected error if any, // or the given value if we succeeded. template auto Wrap(T&& value) -> ErrorOr { if (!status_.ok()) { Error error = std::move(status_).error(); status_ = Success(); return error; } return std::forward(value); } bool failed() const { return !status_.ok(); } private: ErrorOr status_ = Success(); }; // Base class for transforms of visitable data types. template class TransformBase { public: explicit TransformBase(Nonnull arena) : arena_(arena) {} // Transform the given value, and produce either the transformed value or an // error. template auto Transform(const T& v) -> decltype(auto) { return unwrapper_.Wrap(TransformOrOriginal(v)); } protected: // Transform the given value, or return the original if transformation fails. template auto TransformOrOriginal(const T& v) -> decltype(std::declval().UnwrapOr( std::declval()(v), v)) { // If we've already failed, don't do any more transformations. if (unwrapper_.failed()) { return v; } return unwrapper_.UnwrapOr(static_cast(*this)(v), v); } // Transformable values are recursively transformed by default. template , void*> = nullptr> auto operator()(const T& value) -> T { return value.Decompose([&](const auto&... elements) { return [&](auto&&... transformed_elements) { if (unwrapper_.failed()) { return value; } return ConstructFromComponents( decltype(transformed_elements)(transformed_elements)...); }(TransformOrOriginal(elements)...); }); } // Transformable pointers are recursively transformed and reallocated by // default. template , void*> = nullptr> auto operator()(Nonnull value) -> auto { return value->Decompose([&](const auto&... elements) { return [&](auto&&... transformed_elements) -> decltype(AllocateTrait::New( arena_, decltype(transformed_elements)(transformed_elements)...)) { if (unwrapper_.failed()) { return value; } return AllocateTrait::New( arena_, decltype(transformed_elements)(transformed_elements)...); }(TransformOrOriginal(elements)...); }); } // Fundamental types like `int` are assumed to not need transformation. template auto operator()(const T& v) -> std::enable_if_t, T> { return v; } auto operator()(const std::string& str) -> const std::string& { return str; } auto operator()(llvm::StringRef str) -> llvm::StringRef { return str; } // Transform `optional` by transforming the `T` if it's present. template auto operator()(const std::optional& v) -> std::optional { if (!v) { return std::nullopt; } return TransformOrOriginal(*v); } // Transform `pair` by transforming T and U. template auto operator()(const std::pair& pair) -> std::pair { return std::pair{TransformOrOriginal(pair.first), TransformOrOriginal(pair.second)}; } // Transform `vector` by transforming its elements. template auto operator()(const std::vector& vec) -> std::vector { std::vector result; result.reserve(vec.size()); for (auto& value : vec) { result.push_back(TransformOrOriginal(value)); } return result; } // Transform `map` by transforming its keys and values. template auto operator()(const std::map& map) -> std::map { std::map result; for (auto& [key, value] : map) { result.insert({TransformOrOriginal(key), TransformOrOriginal(value)}); } return result; } // Transform `llvm::StringMap` by transforming its keys and values. template auto operator()(const llvm::StringMap& map) -> llvm::StringMap { llvm::StringMap result; for (const auto& it : map) { result.insert( {TransformOrOriginal(it.first()), TransformOrOriginal(it.second)}); } return result; } private: Nonnull arena_; // Unwrapper for results. Used to remove an ErrorOr<...> wrapper temporarily // during recursive transformations and re-apply it when we're done. ResultUnwrapper unwrapper_; }; // Base class for transforms of `Value`s. template class ValueTransform : public TransformBase { public: using TransformBase::TransformBase; using TransformBase::operator(); // Leave references to AST nodes alone by default. // The 'int = 0' parameter avoids this function hiding the `operator()(const // T*)` in the base class. We can remove this once we start using a compiler // that implements P1787R6. template auto operator()(Nonnull node, int /*unused*/ = 0) -> std::enable_if_t, Nonnull> { return node; } auto operator()(Address addr) -> Address { return addr; } auto operator()(ExpressionCategory cat) -> ExpressionCategory { return cat; } auto operator()(ValueNodeView value_node) -> ValueNodeView { return value_node; } // For a type that provides a `Visit` function to visit the most-derived // object, visit and transform that most-derived object. template auto TransformDerived(Nonnull value) -> R { return value->template Visit([&](const auto* derived_value) { using DerivedType = std::remove_pointer_t; static_assert(IsRecursivelyTransformable); return this->TransformOrOriginal(derived_value); }); } // For values, dispatch on the value kind and recursively transform. auto operator()(Nonnull value) -> Nonnull { return TransformDerived>(value); } // Provide a more precise type from transforming a `Witness`. auto operator()(Nonnull value) -> Nonnull { return llvm::cast( this->TransformOrOriginal(llvm::cast(value))); } // For elements, dispatch on the element kind and recursively transform. auto operator()(Nonnull elem) -> Nonnull { return TransformDerived>(elem); } // Preserve vtable during transformation. auto operator()(Nonnull vtable) -> Nonnull { return vtable; } // Preserve class value ptr during transformation. auto operator()(Nonnull value_ptr) -> Nonnull { return value_ptr; } // Preserve constraint kind for intrinsic constraints. auto operator()(IntrinsicConstraint::Kind kind) -> IntrinsicConstraint::Kind { return kind; } }; } // namespace Carbon #endif // CARBON_EXPLORER_AST_VALUE_TRANSFORM_H_