[flang] cleanup
authorpeter klausler <pklausler@nvidia.com>
Wed, 29 Aug 2018 19:26:22 +0000 (12:26 -0700)
committerpeter klausler <pklausler@nvidia.com>
Wed, 12 Sep 2018 23:29:07 +0000 (16:29 -0700)
Original-commit: flang-compiler/f18@99c4bcb60c66b591b24143be194ba85ff692977d
Reviewed-on: https://github.com/flang-compiler/f18/pull/183
Tree-same-pre-rewrite: false

flang/lib/common/idioms.h
flang/lib/common/template.h
flang/lib/evaluate/expression.h
flang/lib/evaluate/tools.cc
flang/lib/evaluate/tools.h
flang/lib/evaluate/type.h
flang/lib/semantics/expression.cc

index 552ddd6..a80ca3a 100644 (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_
index 8a36fe5..986aea5 100644 (file)
 #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_
index 31998f4..85e3efb 100644 (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;
 };
 
index 06355e4..7c56c38 100644 (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>
index 147e6b3..27b4470 100644 (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<typename A> Expr<ResultType<A>> AsExpr(A &&x) {
+  return {std::move(x)};
+}
+
 template<TypeCategory CAT, int KIND>
-Expr<SomeKind<CAT>> ToCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
+Expr<SomeKind<CAT>> AsCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
   return {std::move(x)};
 }
 
-template<typename A> Expr<SomeType> ToGenericExpr(A &&x) {
+template<typename A> Expr<SomeType> AsGenericExpr(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<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 &,
index 5fb1866..0538d77 100644 (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>
index 666adfa..acaaf0e 100644 (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;
 }