[flang] cleanup

Original-commit: flang-compiler/f18@99c4bcb60c
Reviewed-on: https://github.com/flang-compiler/f18/pull/183
Tree-same-pre-rewrite: false
This commit is contained in:
peter klausler 2018-08-29 12:26:22 -07:00
parent 3c850d05ca
commit 70febd9285
7 changed files with 187 additions and 128 deletions

View file

@ -104,7 +104,7 @@ template<typename... LAMBDAS> visitors(LAMBDAS... x)->visitors<LAMBDAS...>;
return false; \
} \
} \
template<typename A> constexpr bool T { class_trait_ns_##T::trait_value<A>() };
template<typename A> constexpr bool T{class_trait_ns_##T::trait_value<A>()};
// Define enum class NAME with the given enumerators, a static
// function EnumToString() that maps enumerators to std::string,
@ -129,56 +129,5 @@ template<typename A> struct ListItemCount {
static_cast<int>(e), #__VA_ARGS__); \
}
template<typename A> std::optional<A> GetIfNonNull(const A *p) {
if (p) {
return {*p};
}
return std::nullopt;
}
// If a variant holds a value of a particular type, return a copy in a
// std::optional<>.
template<typename A, typename VARIANT>
std::optional<A> GetIf(const VARIANT &u) {
return GetIfNonNull(std::get_if<A>(&u));
}
// Collapses a nested std::optional<std::optional<A>> to std::optional<A>
template<typename A>
std::optional<A> JoinOptionals(std::optional<std::optional<A>> &&x) {
if (x.has_value()) {
return std::move(*x);
}
return std::nullopt;
}
// Apply a function to optional argument(s), if are all present.
// N.B. This function uses a "functor" in the C++ sense -- a type with
// a member function operator() -- to implement a "functor" in the category
// theoretical sense.
template<typename A, typename B>
std::optional<A> MapOptional(std::function<A(B &&)> &f, std::optional<B> &&x) {
if (x.has_value()) {
return {f(std::move(*x))};
}
return std::nullopt;
}
template<typename A, typename B, typename C>
std::optional<A> MapOptional(std::function<A(B &&, C &&)> &f,
std::optional<B> &&x, std::optional<C> &&y) {
if (x.has_value() && y.has_value()) {
return {f(std::move(*x), std::move(*y))};
}
return std::nullopt;
}
// Move a value from one variant type to another. The types allowed in the
// source variant must all be allowed in the destination variant type.
template<typename TOV, typename FROMV> TOV MoveVariant(FROMV &&u) {
return std::visit(
[](auto &&x) -> TOV { return {std::move(x)}; }, std::move(u));
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_IDIOMS_H_

View file

@ -16,14 +16,48 @@
#define FORTRAN_COMMON_TEMPLATE_H_
#include <functional>
#include <optional>
#include <tuple>
#include <type_traits>
#include <variant>
// Template metaprogramming utilities
// Utility templates for metaprogramming and for composing the
// std::optional<>, std::tuple<>, and std::variant<> containers.
namespace Fortran::common {
// const A * -> std::optional<A>
template<typename A> std::optional<A> GetIfNonNull(const A *p) {
if (p) {
return {*p};
}
return std::nullopt;
}
// const std::variant<..., A, ...> -> std::optional<A>
// i.e., when a variant holds a value of a particular type, return a copy
// of that value in a std::optional<>.
template<typename A, typename VARIANT>
std::optional<A> GetIf(const VARIANT &u) {
return GetIfNonNull(std::get_if<A>(&u));
}
// std::optional<std::optional<A>> -> std::optional<A>
template<typename A>
std::optional<A> JoinOptional(std::optional<std::optional<A>> &&x) {
if (x.has_value()) {
return std::move(*x);
}
return std::nullopt;
}
// Move a value from one variant type to another. The types allowed in the
// source variant must all be allowed in the destination variant type.
template<typename TOV, typename FROMV> TOV MoveVariant(FROMV &&u) {
return std::visit(
[](auto &&x) -> TOV { return {std::move(x)}; }, std::move(u));
}
// SearchTypeList<PREDICATE, TYPES...> scans a list of types. The zero-based
// index of the first type T in the list for which PREDICATE<T>::value() is
// true is returned, or -1 if the predicate is false for every type in the list.
@ -57,16 +91,9 @@ template<typename A> struct MatchType {
template<typename A, typename... TYPES>
constexpr int TypeIndex{SearchTypeList<MatchType<A>::template Match, TYPES...>};
// SearchVariantType<PREDICATE> scans the types that constitute the alternatives
// of a std::variant instantiation. The zero-based index of the first type T
// among the alternatives for which PREDICATE<T>::value() is true is returned,
// or -1 if the predicate is false for every alternative of the union.
// N.B. It *is* possible to extract the types of the alternatives of a
// std::variant discriminated union instantiation and reuse them as a
// template parameter pack in another template instantiation. The trick is
// to match the std::variant type with a partial specialization. And it
// works with tuples, too, of course.
// OverMembers extracts the list of types that constitute the alternatives
// of a std::variant or elements of a std::tuple and passes that list as
// parameter types to a given variadic template.
template<template<typename...> class, typename> struct OverMembersHelper;
template<template<typename...> class T, typename... Ts>
struct OverMembersHelper<T, std::variant<Ts...>> {
@ -80,12 +107,16 @@ struct OverMembersHelper<T, std::tuple<Ts...>> {
template<template<typename...> class T, typename TorV>
using OverMembers = typename OverMembersHelper<T, TorV>::type;
// SearchMembers<PREDICATE> scans the types that constitute the alternatives
// of a std::variant instantiation or elements of a std::tuple.
// The zero-based index of the first type T among the alternatives for which
// PREDICATE<T>::value() is true is returned, or -1 when the predicate is false
// for every type in the set.
template<template<typename> class PREDICATE> struct SearchMembersHelper {
template<typename... Ts> struct Scanner {
static constexpr int value() { return SearchTypeList<PREDICATE, Ts...>; }
};
};
template<template<typename> class PREDICATE, typename TorV>
constexpr int SearchMembers{
OverMembers<SearchMembersHelper<PREDICATE>::template Scanner,
@ -115,22 +146,26 @@ template<typename... Ts> struct VariantToTupleHelper<std::variant<Ts...>> {
template<typename VARIANT>
using VariantToTuple = typename VariantToTupleHelper<VARIANT>::type;
template<typename A, typename B, typename... REST> struct AreTypesDistinctHelper {
template<typename A, typename B, typename... REST>
struct AreTypesDistinctHelper {
static constexpr bool value() {
if constexpr (std::is_same_v<A, B>) {
return false;
}
if constexpr (sizeof...(REST) > 0) {
return AreTypesDistinctHelper<A, REST...>::value() && AreTypesDistinctHelper<B, REST...>::value();
return AreTypesDistinctHelper<A, REST...>::value() &&
AreTypesDistinctHelper<B, REST...>::value();
}
return true;
}
};
template<typename... Ts> constexpr bool AreTypesDistinct{AreTypesDistinctHelper<Ts...>::value()};
template<typename... Ts>
constexpr bool AreTypesDistinct{AreTypesDistinctHelper<Ts...>::value()};
template<typename> struct TupleToVariantHelper;
template<typename... Ts> struct TupleToVariantHelper<std::tuple<Ts...>> {
static_assert(AreTypesDistinct<Ts...> || !"TupleToVariant: types are not pairwise distinct");
static_assert(AreTypesDistinct<Ts...> ||
!"TupleToVariant: types are not pairwise distinct");
using type = std::variant<Ts...>;
};
template<typename TUPLE>
@ -142,26 +177,72 @@ template<typename... VARIANTS> struct CombineVariantsHelper {
template<typename... VARIANTS>
using CombineVariants = typename CombineVariantsHelper<VARIANTS...>::type;
// SquashVariantOfVariants: given a std::variant whose alternatives are
// all std::variant instantiations, form a new union over their alternatives.
template<typename VARIANT>
using SquashVariantOfVariants = OverMembers<CombineVariants, VARIANT>;
// Given a type function, apply it to each of the types in a tuple or variant,
// and collect the results in another tuple or variant.
// Given a type function, MapTemplate applies it to each of the types
// in a tuple or variant, and collect the results in a given variadic
// template (typically a std::variant).
template<template<typename> class, template<typename...> class, typename...>
struct MapTemplateHelper;
template<template<typename> class F, template<typename...> class TorV,
template<template<typename> class F, template<typename...> class PACKAGE,
typename... Ts>
struct MapTemplateHelper<F, TorV, std::tuple<Ts...>> {
using type = TorV<F<Ts>...>;
struct MapTemplateHelper<F, PACKAGE, std::tuple<Ts...>> {
using type = PACKAGE<F<Ts>...>;
};
template<template<typename> class F, template<typename...> class TorV,
template<template<typename> class F, template<typename...> class PACKAGE,
typename... Ts>
struct MapTemplateHelper<F, TorV, std::variant<Ts...>> {
using type = TorV<F<Ts>...>;
struct MapTemplateHelper<F, PACKAGE, std::variant<Ts...>> {
using type = PACKAGE<F<Ts>...>;
};
template<template<typename> class F, template<typename...> class TorV,
typename TV>
using MapTemplate = typename MapTemplateHelper<F, TorV, TV>::type;
template<template<typename> class F, typename TorV,
template<typename...> class PACKAGE = std::variant>
using MapTemplate = typename MapTemplateHelper<F, PACKAGE, TorV>::type;
// std::tuple<std::optional<>...> -> std::optional<std::tuple<...>>
// i.e., inverts a tuple of optional values into an optional tuple that has
// a value only if all of the original elements were present.
template<typename... A, std::size_t... J>
std::optional<std::tuple<A...>> AllElementsPresentHelper(
std::tuple<std::optional<A>...> &&t, std::index_sequence<J...>) {
bool present[]{std::get<J>(t).has_value()...};
for (std::size_t j{0}; j < sizeof...(J); ++j) {
if (!present[j]) {
return std::nullopt;
}
}
return {std::make_tuple(*std::get<J>(t)...)};
}
template<typename... A>
std::optional<std::tuple<A...>> AllElementsPresent(
std::tuple<std::optional<A>...> &&t) {
return AllElementsPresentHelper(
std::move(t), std::index_sequence_for<A...>{});
}
// (std::optional<>...) -> std::optional<std::tuple<...>>
// i.e., given some number of optional values, return a optional tuple of
// those values that is present only of all of the values were so.
template<typename... A>
std::optional<std::tuple<A...>> AllPresent(std::optional<A> &&... x) {
return AllElementsPresent(std::make_tuple(std::move(x)...));
}
// (f(A...) -> R) -> std::optional<A>... -> std::optional<R>
// Apply a function to optional arguments if all are present.
// If the function returns std::optional, you will probably want to
// pass it through JoinOptional to "squash" it.
template<typename R, typename... A>
std::optional<R> MapOptional(
std::function<R(A &&...)> &&f, std::optional<A> &&... x) {
if (auto args{AllPresent(std::move(x)...)}) {
return std::make_optional(std::apply(std::move(f), std::move(*args)));
}
return std::nullopt;
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_TEMPLATE_H_

View file

@ -539,7 +539,7 @@ template<> struct Relational<SomeType> {
Relational(std::enable_if_t<!std::is_reference_v<A>, A> &&x)
: u{std::move(x)} {}
std::ostream &Dump(std::ostream &o) const;
common::MapTemplate<Relational, std::variant, RelationalTypes> u;
common::MapTemplate<Relational, RelationalTypes> u;
};
extern template struct Relational<Type<TypeCategory::Integer, 1>>;
@ -605,7 +605,7 @@ public:
template<typename A>
Expr(std::enable_if_t<!std::is_reference_v<A>, A> &&x) : u{std::move(x)} {}
common::MapTemplate<Expr, std::variant, CategoryTypes<CAT>> u;
common::MapTemplate<Expr, CategoryTypes<CAT>> u;
};
// BOZ literal constants need to be wide enough to hold an integer or real
@ -645,7 +645,7 @@ public:
}
using Others = std::variant<BOZLiteralConstant>;
using Categories = common::MapTemplate<Expr, std::variant, SomeCategory>;
using Categories = common::MapTemplate<Expr, SomeCategory>;
common::CombineVariants<Others, Categories> u;
};

View file

@ -22,6 +22,25 @@ using namespace Fortran::parser::literals;
namespace Fortran::evaluate {
using SameRealExprPair = SameKindExprs<TypeCategory::Real>;
static SameRealExprPair ConversionHelper(
Expr<SomeReal> &&x, Expr<SomeReal> &&y) {
return std::visit(
[&](auto &&rx, auto &&ry) -> SameRealExprPair {
using XTy = ResultType<decltype(rx)>;
using YTy = ResultType<decltype(ry)>;
if constexpr (std::is_same_v<XTy, YTy>) {
return {SameExprs<XTy>{std::move(rx), std::move(ry)}};
} else if constexpr (XTy::kind < YTy::kind) {
return {SameExprs<YTy>{ConvertTo(ry, std::move(rx)), std::move(ry)}};
} else {
return {SameExprs<XTy>{std::move(rx), ConvertTo(rx, std::move(ry))}};
}
},
std::move(x.u), std::move(y.u));
}
ConvertRealOperandsResult ConvertRealOperands(
parser::ContextualMessages &messages, Expr<SomeType> &&x,
Expr<SomeType> &&y) {
@ -31,24 +50,22 @@ ConvertRealOperandsResult ConvertRealOperands(
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
// Can happen in a CMPLX() constructor. Per F'2018,
// both integer operands are converted to default REAL.
return std::optional{std::make_pair(
ToCategoryExpr(ConvertToType<DefaultReal>(std::move(ix))),
ToCategoryExpr(ConvertToType<DefaultReal>(std::move(iy))))};
return {ConversionHelper(ConvertToType<DefaultReal>(std::move(ix)),
ConvertToType<DefaultReal>(std::move(iy)))};
},
[&](Expr<SomeInteger> &&ix,
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
auto rx{ConvertTo(ry, std::move(ix))};
return std::optional{std::make_pair(std::move(rx), std::move(ry))};
return {
ConversionHelper(ConvertTo(ry, std::move(ix)), std::move(ry))};
},
[&](Expr<SomeReal> &&rx,
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
auto ry{ConvertTo(rx, std::move(iy))};
return std::optional{std::make_pair(std::move(rx), std::move(ry))};
return {
ConversionHelper(std::move(rx), ConvertTo(rx, std::move(iy)))};
},
[&](Expr<SomeReal> &&rx,
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
ConvertToSameKind(rx, ry);
return std::optional{std::make_pair(std::move(rx), std::move(ry))};
return {ConversionHelper(std::move(rx), std::move(ry))};
},
[&](auto &&, auto &&) -> ConvertRealOperandsResult {
// TODO: allow BOZ here?
@ -66,8 +83,8 @@ ConvertRealOperandsResult ConvertRealOperands(
}};
using fType = ConvertRealOperandsResult(Expr<SomeType> &&, Expr<SomeType> &&);
std::function<fType> f{partial};
return common::JoinOptionals(
common::MapOptional(f, std::move(x), std::move(y)));
return common::JoinOptional(
common::MapOptional(std::move(f), std::move(x), std::move(y)));
}
template<template<typename> class OPR, TypeCategory CAT>

View file

@ -18,6 +18,7 @@
#include "expression.h"
#include "../common/idioms.h"
#include "../parser/message.h"
#include <array>
#include <optional>
#include <utility>
@ -93,18 +94,22 @@ Expr<SomeKind<C>> operator/(Expr<SomeKind<C>> &&x, Expr<SomeKind<C>> &&y) {
// Generalizers: these take expressions of more specific types and wrap
// them in more abstract containers.
template<TypeCategory CAT, int KIND>
Expr<SomeKind<CAT>> ToCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
return {std::move(x)};
}
template<typename A> Expr<SomeType> ToGenericExpr(A &&x) {
template<typename A> Expr<ResultType<A>> AsExpr(A &&x) {
return {std::move(x)};
}
template<TypeCategory CAT, int KIND>
Expr<SomeType> ToGenericExpr(Expr<Type<CAT, KIND>> &&x) {
return {ToCategoryExpr(std::move(x))};
Expr<SomeKind<CAT>> AsCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
return {std::move(x)};
}
template<typename A> Expr<SomeType> AsGenericExpr(A &&x) {
return {std::move(x)};
}
template<TypeCategory CAT, int KIND>
Expr<SomeType> AsGenericExpr(Expr<Type<CAT, KIND>> &&x) {
return {AsCategoryExpr(std::move(x))};
}
// Creation of conversion expressions can be done to either a known
@ -125,7 +130,7 @@ Expr<Type<TC, TK>> ConvertTo(
template<TypeCategory TC, int TK, TypeCategory FC, int FK>
Expr<Type<TC, TK>> ConvertTo(
const Expr<Type<TC, TK>> &, Expr<Type<FC, FK>> &&x) {
return ConvertToType<Type<TC, TK>>(ToCategoryExpr(std::move(x)));
return ConvertToType<Type<TC, TK>>(AsCategoryExpr(std::move(x)));
}
template<TypeCategory TC, TypeCategory FC>
@ -134,7 +139,7 @@ Expr<SomeKind<TC>> ConvertTo(
return std::visit(
[&](const auto &toKindExpr) {
using KindExpr = std::decay_t<decltype(toKindExpr)>;
return ToCategoryExpr(
return AsCategoryExpr(
ConvertToType<ResultType<KindExpr>>(std::move(from)));
},
to.u);
@ -143,14 +148,14 @@ Expr<SomeKind<TC>> ConvertTo(
template<TypeCategory TC, TypeCategory FC, int FK>
Expr<SomeKind<TC>> ConvertTo(
const Expr<SomeKind<TC>> &to, Expr<Type<FC, FK>> &&from) {
return ConvertTo(to, ToCategoryExpr(std::move(from)));
return ConvertTo(to, AsCategoryExpr(std::move(from)));
}
template<typename FT>
Expr<SomeType> ConvertTo(const Expr<SomeType> &to, Expr<FT> &&from) {
return std::visit(
[&](const auto &toCatExpr) {
return ToGenericExpr(ConvertTo(toCatExpr, std::move(from)));
return AsGenericExpr(ConvertTo(toCatExpr, std::move(from)));
},
to.u);
}
@ -175,9 +180,16 @@ void ConvertToSameKind(Expr<SomeKind<CAT>> &x, Expr<SomeKind<CAT>> &y) {
// Ensure that both operands of an intrinsic REAL operation (or CMPLX()
// constructor) are INTEGER or REAL, then convert them as necessary to the
// same kind of REAL.
// TODO pmk: need a better type that guarantees that both have same kind
template<int N = 2> struct SameExprsHelper {
template<typename A> using SameExprs = std::array<Expr<A>, N>;
};
template<typename A, int N = 2> using SameExprs = std::array<Expr<A>, N>;
template<TypeCategory CAT, int N = 2>
using SameKindExprs =
common::MapTemplate<SameExprsHelper<N>::template SameExprs,
CategoryTypes<CAT>>;
using ConvertRealOperandsResult =
std::optional<std::pair<Expr<SomeReal>, Expr<SomeReal>>>;
std::optional<SameKindExprs<TypeCategory::Real, 2>>;
ConvertRealOperandsResult ConvertRealOperands(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&);
ConvertRealOperandsResult ConvertRealOperands(parser::ContextualMessages &,

View file

@ -230,7 +230,7 @@ template<typename TYPES> struct SomeScalar {
return common::GetIf<Scalar<T>>(u);
}
common::MapTemplate<Scalar, std::variant, Types> u;
common::MapTemplate<Scalar, Types> u;
};
template<TypeCategory CATEGORY>

View file

@ -107,7 +107,7 @@ template<TypeCategory CAT, typename VALUE> struct ConstantHelper {
if (kind == Ty::kind) {
result = Expr<Ty>{evaluate::Constant<Ty>{std::move(value)}};
} else {
SetKindTraverser<J+1>(kind);
SetKindTraverser<J + 1>(kind);
}
}
}
@ -130,10 +130,9 @@ static std::optional<Expr<evaluate::SomeCharacter>> AnalyzeLiteral(
return std::move(helper.result);
}
template<typename A>
MaybeExpr PackageGeneric(std::optional<A> &&x) {
template<typename A> MaybeExpr PackageGeneric(std::optional<A> &&x) {
if (x.has_value()) {
return {evaluate::ToGenericExpr(std::move(*x))};
return {evaluate::AsGenericExpr(std::move(*x))};
}
return std::nullopt;
}
@ -154,12 +153,14 @@ MaybeExpr AnalyzeHelper(
std::optional<Expr<evaluate::SubscriptInteger>> lb, ub;
if (lbTree.has_value()) {
if (MaybeIntExpr lbExpr{AnalyzeHelper(ea, *lbTree)}) {
lb = evaluate::ConvertToType<evaluate::SubscriptInteger>(std::move(*lbExpr));
lb = evaluate::ConvertToType<evaluate::SubscriptInteger>(
std::move(*lbExpr));
}
}
if (ubTree.has_value()) {
if (MaybeIntExpr ubExpr{AnalyzeHelper(ea, *ubTree)}) {
ub = evaluate::ConvertToType<evaluate::SubscriptInteger>(std::move(*ubExpr));
ub = evaluate::ConvertToType<evaluate::SubscriptInteger>(
std::move(*ubExpr));
}
}
if (!lb.has_value() || !ub.has_value()) {
@ -169,7 +170,7 @@ MaybeExpr AnalyzeHelper(
evaluate::CopyableIndirection<evaluate::Substring> ind{std::move(substring)};
Expr<evaluate::DefaultCharacter> chExpr{std::move(ind)};
chExpr.Fold(ea.context());
return {evaluate::ToGenericExpr(chExpr)};
return {evaluate::AsGenericExpr(chExpr)};
}
// Common handling of parser::IntLiteralConstant and SignedIntLiteralConstant
@ -179,7 +180,8 @@ std::optional<Expr<evaluate::SomeInteger>> IntLiteralConstant(
auto kind{ea.Analyze(std::get<std::optional<parser::KindParam>>(x.t),
ea.defaultIntegerKind())};
auto value{std::get<0>(x.t)}; // std::(u)int64_t
ConstantHelper<TypeCategory::Integer, decltype(value)> helper{std::move(value)};
ConstantHelper<TypeCategory::Integer, decltype(value)> helper{
std::move(value)};
helper.SetKind(kind);
if (!helper.result.has_value()) {
ea.context().messages.Say("unsupported INTEGER(KIND=%ju)"_err_en_US,
@ -236,7 +238,8 @@ std::optional<Expr<evaluate::SomeReal>> ReadRealLiteral(
if (context.flushDenormalsToZero) {
value = value.FlushDenormalToZero();
}
return {evaluate::ToCategoryExpr(Expr<RealType>{evaluate::Constant<RealType>{value}})};
return {evaluate::AsCategoryExpr(
Expr<RealType>{evaluate::Constant<RealType>{value}})};
}
struct RealHelper {
@ -250,7 +253,7 @@ struct RealHelper {
if (kind == Ty::kind) {
result = ReadRealLiteral<Ty::kind>(literal, context);
} else {
SetKindTraverser<J+1>(kind);
SetKindTraverser<J + 1>(kind);
}
}
}
@ -597,22 +600,19 @@ ExpressionAnalyzer::KindParam ExpressionAnalyzer::Analyze(
kindParam->u);
}
// TODO pmk: need a way to represent a tuple of same-typed expressions, avoid CHECK here
std::optional<Expr<evaluate::SomeComplex>> ExpressionAnalyzer::ConstructComplex(
MaybeExpr &&real, MaybeExpr &&imaginary) {
if (auto converted{evaluate::ConvertRealOperands(
context_.messages, std::move(real), std::move(imaginary))}) {
return {std::visit(
[&](auto &&re) -> Expr<evaluate::SomeComplex> {
using realType = evaluate::ResultType<decltype(re)>;
auto *im{std::get_if<Expr<realType>>(&converted->second.u)};
CHECK(im != nullptr);
constexpr int kind{realType::kind};
using zType = evaluate::Type<TypeCategory::Complex, kind>;
return {Expr<evaluate::SomeComplex>{Expr<zType>{evaluate::ComplexConstructor<kind>{
std::move(re), std::move(*im)}}}};
[](auto &&pair) -> std::optional<Expr<evaluate::SomeComplex>> {
using realType = evaluate::ResultType<decltype(pair[0])>;
using zType = evaluate::SameKind<TypeCategory::Complex, realType>;
auto cmplx{evaluate::ComplexConstructor<zType::kind>{
std::move(pair[0]), std::move(pair[1])}};
return {evaluate::AsCategoryExpr(evaluate::AsExpr(std::move(cmplx)))};
},
std::move(converted->first.u))};
*converted)};
}
return std::nullopt;
}