1 //===-- lib/Evaluate/fold-implementation.h --------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
10 #define FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
11 
12 #include "character.h"
13 #include "host.h"
14 #include "int-power.h"
15 #include "flang/Common/indirection.h"
16 #include "flang/Common/template.h"
17 #include "flang/Common/unwrap.h"
18 #include "flang/Evaluate/characteristics.h"
19 #include "flang/Evaluate/common.h"
20 #include "flang/Evaluate/constant.h"
21 #include "flang/Evaluate/expression.h"
22 #include "flang/Evaluate/fold.h"
23 #include "flang/Evaluate/formatting.h"
24 #include "flang/Evaluate/intrinsics-library.h"
25 #include "flang/Evaluate/intrinsics.h"
26 #include "flang/Evaluate/shape.h"
27 #include "flang/Evaluate/tools.h"
28 #include "flang/Evaluate/traverse.h"
29 #include "flang/Evaluate/type.h"
30 #include "flang/Parser/message.h"
31 #include "flang/Semantics/scope.h"
32 #include "flang/Semantics/symbol.h"
33 #include "flang/Semantics/tools.h"
34 #include <algorithm>
35 #include <cmath>
36 #include <complex>
37 #include <cstdio>
38 #include <optional>
39 #include <type_traits>
40 #include <variant>
41 
42 // Some environments, viz. clang on Darwin, allow the macro HUGE
43 // to leak out of <math.h> even when it is never directly included.
44 #undef HUGE
45 
46 namespace Fortran::evaluate {
47 
48 // Utilities
49 template <typename T> class Folder {
50 public:
Folder(FoldingContext & c)51   explicit Folder(FoldingContext &c) : context_{c} {}
52   std::optional<Constant<T>> GetNamedConstant(const Symbol &);
53   std::optional<Constant<T>> ApplySubscripts(const Constant<T> &array,
54       const std::vector<Constant<SubscriptInteger>> &subscripts);
55   std::optional<Constant<T>> ApplyComponent(Constant<SomeDerived> &&,
56       const Symbol &component,
57       const std::vector<Constant<SubscriptInteger>> * = nullptr);
58   std::optional<Constant<T>> GetConstantComponent(
59       Component &, const std::vector<Constant<SubscriptInteger>> * = nullptr);
60   std::optional<Constant<T>> Folding(ArrayRef &);
61   std::optional<Constant<T>> Folding(DataRef &);
62   Expr<T> Folding(Designator<T> &&);
63   Constant<T> *Folding(std::optional<ActualArgument> &);
64 
65   Expr<T> CSHIFT(FunctionRef<T> &&);
66   Expr<T> EOSHIFT(FunctionRef<T> &&);
67   Expr<T> PACK(FunctionRef<T> &&);
68   Expr<T> RESHAPE(FunctionRef<T> &&);
69   Expr<T> SPREAD(FunctionRef<T> &&);
70   Expr<T> TRANSPOSE(FunctionRef<T> &&);
71   Expr<T> UNPACK(FunctionRef<T> &&);
72 
73   Expr<T> TRANSFER(FunctionRef<T> &&);
74 
75 private:
76   FoldingContext &context_;
77 };
78 
79 std::optional<Constant<SubscriptInteger>> GetConstantSubscript(
80     FoldingContext &, Subscript &, const NamedEntity &, int dim);
81 
82 // Helper to use host runtime on scalars for folding.
83 template <typename TR, typename... TA>
84 std::optional<std::function<Scalar<TR>(FoldingContext &, Scalar<TA>...)>>
GetHostRuntimeWrapper(const std::string & name)85 GetHostRuntimeWrapper(const std::string &name) {
86   std::vector<DynamicType> argTypes{TA{}.GetType()...};
87   if (auto hostWrapper{GetHostRuntimeWrapper(name, TR{}.GetType(), argTypes)}) {
88     return [hostWrapper](
89                FoldingContext &context, Scalar<TA>... args) -> Scalar<TR> {
90       std::vector<Expr<SomeType>> genericArgs{
91           AsGenericExpr(Constant<TA>{args})...};
92       return GetScalarConstantValue<TR>(
93           (*hostWrapper)(context, std::move(genericArgs)))
94           .value();
95     };
96   }
97   return std::nullopt;
98 }
99 
100 // FoldOperation() rewrites expression tree nodes.
101 // If there is any possibility that the rewritten node will
102 // not have the same representation type, the result of
103 // FoldOperation() will be packaged in an Expr<> of the same
104 // specific type.
105 
106 // no-op base case
107 template <typename A>
FoldOperation(FoldingContext &,A && x)108 common::IfNoLvalue<Expr<ResultType<A>>, A> FoldOperation(
109     FoldingContext &, A &&x) {
110   static_assert(!std::is_same_v<A, Expr<ResultType<A>>>,
111       "call Fold() instead for Expr<>");
112   return Expr<ResultType<A>>{std::move(x)};
113 }
114 
115 Component FoldOperation(FoldingContext &, Component &&);
116 NamedEntity FoldOperation(FoldingContext &, NamedEntity &&);
117 Triplet FoldOperation(FoldingContext &, Triplet &&);
118 Subscript FoldOperation(FoldingContext &, Subscript &&);
119 ArrayRef FoldOperation(FoldingContext &, ArrayRef &&);
120 CoarrayRef FoldOperation(FoldingContext &, CoarrayRef &&);
121 DataRef FoldOperation(FoldingContext &, DataRef &&);
122 Substring FoldOperation(FoldingContext &, Substring &&);
123 ComplexPart FoldOperation(FoldingContext &, ComplexPart &&);
124 template <typename T>
125 Expr<T> FoldOperation(FoldingContext &, FunctionRef<T> &&);
126 template <typename T>
FoldOperation(FoldingContext & context,Designator<T> && designator)127 Expr<T> FoldOperation(FoldingContext &context, Designator<T> &&designator) {
128   return Folder<T>{context}.Folding(std::move(designator));
129 }
130 Expr<TypeParamInquiry::Result> FoldOperation(
131     FoldingContext &, TypeParamInquiry &&);
132 Expr<ImpliedDoIndex::Result> FoldOperation(
133     FoldingContext &context, ImpliedDoIndex &&);
134 template <typename T>
135 Expr<T> FoldOperation(FoldingContext &, ArrayConstructor<T> &&);
136 Expr<SomeDerived> FoldOperation(FoldingContext &, StructureConstructor &&);
137 
138 template <typename T>
GetNamedConstant(const Symbol & symbol0)139 std::optional<Constant<T>> Folder<T>::GetNamedConstant(const Symbol &symbol0) {
140   const Symbol &symbol{ResolveAssociations(symbol0)};
141   if (IsNamedConstant(symbol)) {
142     if (const auto *object{
143             symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
144       if (const auto *constant{UnwrapConstantValue<T>(object->init())}) {
145         return *constant;
146       }
147     }
148   }
149   return std::nullopt;
150 }
151 
152 template <typename T>
Folding(ArrayRef & aRef)153 std::optional<Constant<T>> Folder<T>::Folding(ArrayRef &aRef) {
154   std::vector<Constant<SubscriptInteger>> subscripts;
155   int dim{0};
156   for (Subscript &ss : aRef.subscript()) {
157     if (auto constant{GetConstantSubscript(context_, ss, aRef.base(), dim++)}) {
158       subscripts.emplace_back(std::move(*constant));
159     } else {
160       return std::nullopt;
161     }
162   }
163   if (Component * component{aRef.base().UnwrapComponent()}) {
164     return GetConstantComponent(*component, &subscripts);
165   } else if (std::optional<Constant<T>> array{
166                  GetNamedConstant(aRef.base().GetLastSymbol())}) {
167     return ApplySubscripts(*array, subscripts);
168   } else {
169     return std::nullopt;
170   }
171 }
172 
173 template <typename T>
Folding(DataRef & ref)174 std::optional<Constant<T>> Folder<T>::Folding(DataRef &ref) {
175   return common::visit(
176       common::visitors{
177           [this](SymbolRef &sym) { return GetNamedConstant(*sym); },
178           [this](Component &comp) {
179             comp = FoldOperation(context_, std::move(comp));
180             return GetConstantComponent(comp);
181           },
182           [this](ArrayRef &aRef) {
183             aRef = FoldOperation(context_, std::move(aRef));
184             return Folding(aRef);
185           },
186           [](CoarrayRef &) { return std::optional<Constant<T>>{}; },
187       },
188       ref.u);
189 }
190 
191 // TODO: This would be more natural as a member function of Constant<T>.
192 template <typename T>
ApplySubscripts(const Constant<T> & array,const std::vector<Constant<SubscriptInteger>> & subscripts)193 std::optional<Constant<T>> Folder<T>::ApplySubscripts(const Constant<T> &array,
194     const std::vector<Constant<SubscriptInteger>> &subscripts) {
195   const auto &shape{array.shape()};
196   const auto &lbounds{array.lbounds()};
197   int rank{GetRank(shape)};
198   CHECK(rank == static_cast<int>(subscripts.size()));
199   std::size_t elements{1};
200   ConstantSubscripts resultShape;
201   ConstantSubscripts ssLB;
202   for (const auto &ss : subscripts) {
203     CHECK(ss.Rank() <= 1);
204     if (ss.Rank() == 1) {
205       resultShape.push_back(static_cast<ConstantSubscript>(ss.size()));
206       elements *= ss.size();
207       ssLB.push_back(ss.lbounds().front());
208     }
209   }
210   ConstantSubscripts ssAt(rank, 0), at(rank, 0), tmp(1, 0);
211   std::vector<Scalar<T>> values;
212   while (elements-- > 0) {
213     bool increment{true};
214     int k{0};
215     for (int j{0}; j < rank; ++j) {
216       if (subscripts[j].Rank() == 0) {
217         at[j] = subscripts[j].GetScalarValue().value().ToInt64();
218       } else {
219         CHECK(k < GetRank(resultShape));
220         tmp[0] = ssLB.at(k) + ssAt.at(k);
221         at[j] = subscripts[j].At(tmp).ToInt64();
222         if (increment) {
223           if (++ssAt[k] == resultShape[k]) {
224             ssAt[k] = 0;
225           } else {
226             increment = false;
227           }
228         }
229         ++k;
230       }
231       if (at[j] < lbounds[j] || at[j] >= lbounds[j] + shape[j]) {
232         context_.messages().Say(
233             "Subscript value (%jd) is out of range on dimension %d in reference to a constant array value"_err_en_US,
234             at[j], j + 1);
235         return std::nullopt;
236       }
237     }
238     values.emplace_back(array.At(at));
239     CHECK(!increment || elements == 0);
240     CHECK(k == GetRank(resultShape));
241   }
242   if constexpr (T::category == TypeCategory::Character) {
243     return Constant<T>{array.LEN(), std::move(values), std::move(resultShape)};
244   } else if constexpr (std::is_same_v<T, SomeDerived>) {
245     return Constant<T>{array.result().derivedTypeSpec(), std::move(values),
246         std::move(resultShape)};
247   } else {
248     return Constant<T>{std::move(values), std::move(resultShape)};
249   }
250 }
251 
252 template <typename T>
ApplyComponent(Constant<SomeDerived> && structures,const Symbol & component,const std::vector<Constant<SubscriptInteger>> * subscripts)253 std::optional<Constant<T>> Folder<T>::ApplyComponent(
254     Constant<SomeDerived> &&structures, const Symbol &component,
255     const std::vector<Constant<SubscriptInteger>> *subscripts) {
256   if (auto scalar{structures.GetScalarValue()}) {
257     if (std::optional<Expr<SomeType>> expr{scalar->Find(component)}) {
258       if (const Constant<T> *value{UnwrapConstantValue<T>(expr.value())}) {
259         if (!subscripts) {
260           return std::move(*value);
261         } else {
262           return ApplySubscripts(*value, *subscripts);
263         }
264       }
265     }
266   } else {
267     // A(:)%scalar_component & A(:)%array_component(subscripts)
268     std::unique_ptr<ArrayConstructor<T>> array;
269     if (structures.empty()) {
270       return std::nullopt;
271     }
272     ConstantSubscripts at{structures.lbounds()};
273     do {
274       StructureConstructor scalar{structures.At(at)};
275       if (std::optional<Expr<SomeType>> expr{scalar.Find(component)}) {
276         if (const Constant<T> *value{UnwrapConstantValue<T>(expr.value())}) {
277           if (!array.get()) {
278             // This technique ensures that character length or derived type
279             // information is propagated to the array constructor.
280             auto *typedExpr{UnwrapExpr<Expr<T>>(expr.value())};
281             CHECK(typedExpr);
282             array = std::make_unique<ArrayConstructor<T>>(*typedExpr);
283           }
284           if (subscripts) {
285             if (auto element{ApplySubscripts(*value, *subscripts)}) {
286               CHECK(element->Rank() == 0);
287               array->Push(Expr<T>{std::move(*element)});
288             } else {
289               return std::nullopt;
290             }
291           } else {
292             CHECK(value->Rank() == 0);
293             array->Push(Expr<T>{*value});
294           }
295         } else {
296           return std::nullopt;
297         }
298       }
299     } while (structures.IncrementSubscripts(at));
300     // Fold the ArrayConstructor<> into a Constant<>.
301     CHECK(array);
302     Expr<T> result{Fold(context_, Expr<T>{std::move(*array)})};
303     if (auto *constant{UnwrapConstantValue<T>(result)}) {
304       return constant->Reshape(common::Clone(structures.shape()));
305     }
306   }
307   return std::nullopt;
308 }
309 
310 template <typename T>
GetConstantComponent(Component & component,const std::vector<Constant<SubscriptInteger>> * subscripts)311 std::optional<Constant<T>> Folder<T>::GetConstantComponent(Component &component,
312     const std::vector<Constant<SubscriptInteger>> *subscripts) {
313   if (std::optional<Constant<SomeDerived>> structures{common::visit(
314           common::visitors{
315               [&](const Symbol &symbol) {
316                 return Folder<SomeDerived>{context_}.GetNamedConstant(symbol);
317               },
318               [&](ArrayRef &aRef) {
319                 return Folder<SomeDerived>{context_}.Folding(aRef);
320               },
321               [&](Component &base) {
322                 return Folder<SomeDerived>{context_}.GetConstantComponent(base);
323               },
324               [&](CoarrayRef &) {
325                 return std::optional<Constant<SomeDerived>>{};
326               },
327           },
328           component.base().u)}) {
329     return ApplyComponent(
330         std::move(*structures), component.GetLastSymbol(), subscripts);
331   } else {
332     return std::nullopt;
333   }
334 }
335 
Folding(Designator<T> && designator)336 template <typename T> Expr<T> Folder<T>::Folding(Designator<T> &&designator) {
337   if constexpr (T::category == TypeCategory::Character) {
338     if (auto *substring{common::Unwrap<Substring>(designator.u)}) {
339       if (std::optional<Expr<SomeCharacter>> folded{
340               substring->Fold(context_)}) {
341         if (const auto *specific{std::get_if<Expr<T>>(&folded->u)}) {
342           return std::move(*specific);
343         }
344       }
345       // We used to fold zero-length substrings into zero-length
346       // constants here, but that led to problems in variable
347       // definition contexts.
348     }
349   } else if constexpr (T::category == TypeCategory::Real) {
350     if (auto *zPart{std::get_if<ComplexPart>(&designator.u)}) {
351       *zPart = FoldOperation(context_, std::move(*zPart));
352       using ComplexT = Type<TypeCategory::Complex, T::kind>;
353       if (auto zConst{Folder<ComplexT>{context_}.Folding(zPart->complex())}) {
354         return Fold(context_,
355             Expr<T>{ComplexComponent<T::kind>{
356                 zPart->part() == ComplexPart::Part::IM,
357                 Expr<ComplexT>{std::move(*zConst)}}});
358       } else {
359         return Expr<T>{Designator<T>{std::move(*zPart)}};
360       }
361     }
362   }
363   return common::visit(
364       common::visitors{
365           [&](SymbolRef &&symbol) {
366             if (auto constant{GetNamedConstant(*symbol)}) {
367               return Expr<T>{std::move(*constant)};
368             }
369             return Expr<T>{std::move(designator)};
370           },
371           [&](ArrayRef &&aRef) {
372             aRef = FoldOperation(context_, std::move(aRef));
373             if (auto c{Folding(aRef)}) {
374               return Expr<T>{std::move(*c)};
375             } else {
376               return Expr<T>{Designator<T>{std::move(aRef)}};
377             }
378           },
379           [&](Component &&component) {
380             component = FoldOperation(context_, std::move(component));
381             if (auto c{GetConstantComponent(component)}) {
382               return Expr<T>{std::move(*c)};
383             } else {
384               return Expr<T>{Designator<T>{std::move(component)}};
385             }
386           },
387           [&](auto &&x) {
388             return Expr<T>{
389                 Designator<T>{FoldOperation(context_, std::move(x))}};
390           },
391       },
392       std::move(designator.u));
393 }
394 
395 // Apply type conversion and re-folding if necessary.
396 // This is where BOZ arguments are converted.
397 template <typename T>
Folding(std::optional<ActualArgument> & arg)398 Constant<T> *Folder<T>::Folding(std::optional<ActualArgument> &arg) {
399   if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) {
400     if (!UnwrapExpr<Expr<T>>(*expr)) {
401       if (auto converted{ConvertToType(T::GetType(), std::move(*expr))}) {
402         *expr = Fold(context_, std::move(*converted));
403       }
404     }
405     return UnwrapConstantValue<T>(*expr);
406   }
407   return nullptr;
408 }
409 
410 template <typename... A, std::size_t... I>
GetConstantArgumentsHelper(FoldingContext & context,ActualArguments & arguments,std::index_sequence<I...>)411 std::optional<std::tuple<const Constant<A> *...>> GetConstantArgumentsHelper(
412     FoldingContext &context, ActualArguments &arguments,
413     std::index_sequence<I...>) {
414   static_assert(
415       (... && IsSpecificIntrinsicType<A>)); // TODO derived types for MERGE?
416   static_assert(sizeof...(A) > 0);
417   std::tuple<const Constant<A> *...> args{
418       Folder<A>{context}.Folding(arguments.at(I))...};
419   if ((... && (std::get<I>(args)))) {
420     return args;
421   } else {
422     return std::nullopt;
423   }
424 }
425 
426 template <typename... A>
GetConstantArguments(FoldingContext & context,ActualArguments & args)427 std::optional<std::tuple<const Constant<A> *...>> GetConstantArguments(
428     FoldingContext &context, ActualArguments &args) {
429   return GetConstantArgumentsHelper<A...>(
430       context, args, std::index_sequence_for<A...>{});
431 }
432 
433 template <typename... A, std::size_t... I>
GetScalarConstantArgumentsHelper(FoldingContext & context,ActualArguments & args,std::index_sequence<I...>)434 std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArgumentsHelper(
435     FoldingContext &context, ActualArguments &args, std::index_sequence<I...>) {
436   if (auto constArgs{GetConstantArguments<A...>(context, args)}) {
437     return std::tuple<Scalar<A>...>{
438         std::get<I>(*constArgs)->GetScalarValue().value()...};
439   } else {
440     return std::nullopt;
441   }
442 }
443 
444 template <typename... A>
GetScalarConstantArguments(FoldingContext & context,ActualArguments & args)445 std::optional<std::tuple<Scalar<A>...>> GetScalarConstantArguments(
446     FoldingContext &context, ActualArguments &args) {
447   return GetScalarConstantArgumentsHelper<A...>(
448       context, args, std::index_sequence_for<A...>{});
449 }
450 
451 // helpers to fold intrinsic function references
452 // Define callable types used in a common utility that
453 // takes care of array and cast/conversion aspects for elemental intrinsics
454 
455 template <typename TR, typename... TArgs>
456 using ScalarFunc = std::function<Scalar<TR>(const Scalar<TArgs> &...)>;
457 template <typename TR, typename... TArgs>
458 using ScalarFuncWithContext =
459     std::function<Scalar<TR>(FoldingContext &, const Scalar<TArgs> &...)>;
460 
461 template <template <typename, typename...> typename WrapperType, typename TR,
462     typename... TA, std::size_t... I>
FoldElementalIntrinsicHelper(FoldingContext & context,FunctionRef<TR> && funcRef,WrapperType<TR,TA...> func,std::index_sequence<I...>)463 Expr<TR> FoldElementalIntrinsicHelper(FoldingContext &context,
464     FunctionRef<TR> &&funcRef, WrapperType<TR, TA...> func,
465     std::index_sequence<I...>) {
466   if (std::optional<std::tuple<const Constant<TA> *...>> args{
467           GetConstantArguments<TA...>(context, funcRef.arguments())}) {
468     // Compute the shape of the result based on shapes of arguments
469     ConstantSubscripts shape;
470     int rank{0};
471     const ConstantSubscripts *shapes[]{&std::get<I>(*args)->shape()...};
472     const int ranks[]{std::get<I>(*args)->Rank()...};
473     for (unsigned int i{0}; i < sizeof...(TA); ++i) {
474       if (ranks[i] > 0) {
475         if (rank == 0) {
476           rank = ranks[i];
477           shape = *shapes[i];
478         } else {
479           if (shape != *shapes[i]) {
480             // TODO: Rank compatibility was already checked but it seems to be
481             // the first place where the actual shapes are checked to be the
482             // same. Shouldn't this be checked elsewhere so that this is also
483             // checked for non constexpr call to elemental intrinsics function?
484             context.messages().Say(
485                 "Arguments in elemental intrinsic function are not conformable"_err_en_US);
486             return Expr<TR>{std::move(funcRef)};
487           }
488         }
489       }
490     }
491     CHECK(rank == GetRank(shape));
492 
493     // Compute all the scalar values of the results
494     std::vector<Scalar<TR>> results;
495     if (TotalElementCount(shape) > 0) {
496       ConstantBounds bounds{shape};
497       ConstantSubscripts resultIndex(rank, 1);
498       ConstantSubscripts argIndex[]{std::get<I>(*args)->lbounds()...};
499       do {
500         if constexpr (std::is_same_v<WrapperType<TR, TA...>,
501                           ScalarFuncWithContext<TR, TA...>>) {
502           results.emplace_back(
503               func(context, std::get<I>(*args)->At(argIndex[I])...));
504         } else if constexpr (std::is_same_v<WrapperType<TR, TA...>,
505                                  ScalarFunc<TR, TA...>>) {
506           results.emplace_back(func(std::get<I>(*args)->At(argIndex[I])...));
507         }
508         (std::get<I>(*args)->IncrementSubscripts(argIndex[I]), ...);
509       } while (bounds.IncrementSubscripts(resultIndex));
510     }
511     // Build and return constant result
512     if constexpr (TR::category == TypeCategory::Character) {
513       auto len{static_cast<ConstantSubscript>(
514           results.empty() ? 0 : results[0].length())};
515       return Expr<TR>{Constant<TR>{len, std::move(results), std::move(shape)}};
516     } else {
517       return Expr<TR>{Constant<TR>{std::move(results), std::move(shape)}};
518     }
519   }
520   return Expr<TR>{std::move(funcRef)};
521 }
522 
523 template <typename TR, typename... TA>
FoldElementalIntrinsic(FoldingContext & context,FunctionRef<TR> && funcRef,ScalarFunc<TR,TA...> func)524 Expr<TR> FoldElementalIntrinsic(FoldingContext &context,
525     FunctionRef<TR> &&funcRef, ScalarFunc<TR, TA...> func) {
526   return FoldElementalIntrinsicHelper<ScalarFunc, TR, TA...>(
527       context, std::move(funcRef), func, std::index_sequence_for<TA...>{});
528 }
529 template <typename TR, typename... TA>
FoldElementalIntrinsic(FoldingContext & context,FunctionRef<TR> && funcRef,ScalarFuncWithContext<TR,TA...> func)530 Expr<TR> FoldElementalIntrinsic(FoldingContext &context,
531     FunctionRef<TR> &&funcRef, ScalarFuncWithContext<TR, TA...> func) {
532   return FoldElementalIntrinsicHelper<ScalarFuncWithContext, TR, TA...>(
533       context, std::move(funcRef), func, std::index_sequence_for<TA...>{});
534 }
535 
536 std::optional<std::int64_t> GetInt64Arg(const std::optional<ActualArgument> &);
537 std::optional<std::int64_t> GetInt64ArgOr(
538     const std::optional<ActualArgument> &, std::int64_t defaultValue);
539 
540 template <typename A, typename B>
GetIntegerVector(const B & x)541 std::optional<std::vector<A>> GetIntegerVector(const B &x) {
542   static_assert(std::is_integral_v<A>);
543   if (const auto *someInteger{UnwrapExpr<Expr<SomeInteger>>(x)}) {
544     return common::visit(
545         [](const auto &typedExpr) -> std::optional<std::vector<A>> {
546           using T = ResultType<decltype(typedExpr)>;
547           if (const auto *constant{UnwrapConstantValue<T>(typedExpr)}) {
548             if (constant->Rank() == 1) {
549               std::vector<A> result;
550               for (const auto &value : constant->values()) {
551                 result.push_back(static_cast<A>(value.ToInt64()));
552               }
553               return result;
554             }
555           }
556           return std::nullopt;
557         },
558         someInteger->u);
559   }
560   return std::nullopt;
561 }
562 
563 // Transform an intrinsic function reference that contains user errors
564 // into an intrinsic with the same characteristic but the "invalid" name.
565 // This to prevent generating warnings over and over if the expression
566 // gets re-folded.
MakeInvalidIntrinsic(FunctionRef<T> && funcRef)567 template <typename T> Expr<T> MakeInvalidIntrinsic(FunctionRef<T> &&funcRef) {
568   SpecificIntrinsic invalid{std::get<SpecificIntrinsic>(funcRef.proc().u)};
569   invalid.name = IntrinsicProcTable::InvalidName;
570   return Expr<T>{FunctionRef<T>{ProcedureDesignator{std::move(invalid)},
571       ActualArguments{std::move(funcRef.arguments())}}};
572 }
573 
CSHIFT(FunctionRef<T> && funcRef)574 template <typename T> Expr<T> Folder<T>::CSHIFT(FunctionRef<T> &&funcRef) {
575   auto args{funcRef.arguments()};
576   CHECK(args.size() == 3);
577   const auto *array{UnwrapConstantValue<T>(args[0])};
578   const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])};
579   auto dim{GetInt64ArgOr(args[2], 1)};
580   if (!array || !shiftExpr || !dim) {
581     return Expr<T>{std::move(funcRef)};
582   }
583   auto convertedShift{Fold(context_,
584       ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))};
585   const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)};
586   if (!shift) {
587     return Expr<T>{std::move(funcRef)};
588   }
589   // Arguments are constant
590   if (*dim < 1 || *dim > array->Rank()) {
591     context_.messages().Say("Invalid 'dim=' argument (%jd) in CSHIFT"_err_en_US,
592         static_cast<std::intmax_t>(*dim));
593   } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) {
594     // message already emitted from intrinsic look-up
595   } else {
596     int rank{array->Rank()};
597     int zbDim{static_cast<int>(*dim) - 1};
598     bool ok{true};
599     if (shift->Rank() > 0) {
600       int k{0};
601       for (int j{0}; j < rank; ++j) {
602         if (j != zbDim) {
603           if (array->shape()[j] != shift->shape()[k]) {
604             context_.messages().Say(
605                 "Invalid 'shift=' argument in CSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
606                 k + 1, static_cast<std::intmax_t>(shift->shape()[k]),
607                 static_cast<std::intmax_t>(array->shape()[j]));
608             ok = false;
609           }
610           ++k;
611         }
612       }
613     }
614     if (ok) {
615       std::vector<Scalar<T>> resultElements;
616       ConstantSubscripts arrayLB{array->lbounds()};
617       ConstantSubscripts arrayAt{arrayLB};
618       ConstantSubscript &dimIndex{arrayAt[zbDim]};
619       ConstantSubscript dimLB{dimIndex}; // initial value
620       ConstantSubscript dimExtent{array->shape()[zbDim]};
621       ConstantSubscripts shiftLB{shift->lbounds()};
622       for (auto n{GetSize(array->shape())}; n > 0; --n) {
623         ConstantSubscript origDimIndex{dimIndex};
624         ConstantSubscripts shiftAt;
625         if (shift->Rank() > 0) {
626           int k{0};
627           for (int j{0}; j < rank; ++j) {
628             if (j != zbDim) {
629               shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]);
630             }
631           }
632         }
633         ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()};
634         dimIndex = dimLB + ((dimIndex - dimLB + shiftCount) % dimExtent);
635         if (dimIndex < dimLB) {
636           dimIndex += dimExtent;
637         } else if (dimIndex >= dimLB + dimExtent) {
638           dimIndex -= dimExtent;
639         }
640         resultElements.push_back(array->At(arrayAt));
641         dimIndex = origDimIndex;
642         array->IncrementSubscripts(arrayAt);
643       }
644       return Expr<T>{PackageConstant<T>(
645           std::move(resultElements), *array, array->shape())};
646     }
647   }
648   // Invalid, prevent re-folding
649   return MakeInvalidIntrinsic(std::move(funcRef));
650 }
651 
EOSHIFT(FunctionRef<T> && funcRef)652 template <typename T> Expr<T> Folder<T>::EOSHIFT(FunctionRef<T> &&funcRef) {
653   auto args{funcRef.arguments()};
654   CHECK(args.size() == 4);
655   const auto *array{UnwrapConstantValue<T>(args[0])};
656   const auto *shiftExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])};
657   auto dim{GetInt64ArgOr(args[3], 1)};
658   if (!array || !shiftExpr || !dim) {
659     return Expr<T>{std::move(funcRef)};
660   }
661   // Apply type conversions to the shift= and boundary= arguments.
662   auto convertedShift{Fold(context_,
663       ConvertToType<SubscriptInteger>(Expr<SomeInteger>{*shiftExpr}))};
664   const auto *shift{UnwrapConstantValue<SubscriptInteger>(convertedShift)};
665   if (!shift) {
666     return Expr<T>{std::move(funcRef)};
667   }
668   const Constant<T> *boundary{nullptr};
669   std::optional<Expr<SomeType>> convertedBoundary;
670   if (const auto *boundaryExpr{UnwrapExpr<Expr<SomeType>>(args[2])}) {
671     convertedBoundary = Fold(context_,
672         ConvertToType(array->GetType(), Expr<SomeType>{*boundaryExpr}));
673     boundary = UnwrapExpr<Constant<T>>(convertedBoundary);
674     if (!boundary) {
675       return Expr<T>{std::move(funcRef)};
676     }
677   }
678   // Arguments are constant
679   if (*dim < 1 || *dim > array->Rank()) {
680     context_.messages().Say(
681         "Invalid 'dim=' argument (%jd) in EOSHIFT"_err_en_US,
682         static_cast<std::intmax_t>(*dim));
683   } else if (shift->Rank() > 0 && shift->Rank() != array->Rank() - 1) {
684     // message already emitted from intrinsic look-up
685   } else if (boundary && boundary->Rank() > 0 &&
686       boundary->Rank() != array->Rank() - 1) {
687     // ditto
688   } else {
689     int rank{array->Rank()};
690     int zbDim{static_cast<int>(*dim) - 1};
691     bool ok{true};
692     if (shift->Rank() > 0) {
693       int k{0};
694       for (int j{0}; j < rank; ++j) {
695         if (j != zbDim) {
696           if (array->shape()[j] != shift->shape()[k]) {
697             context_.messages().Say(
698                 "Invalid 'shift=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
699                 k + 1, static_cast<std::intmax_t>(shift->shape()[k]),
700                 static_cast<std::intmax_t>(array->shape()[j]));
701             ok = false;
702           }
703           ++k;
704         }
705       }
706     }
707     if (boundary && boundary->Rank() > 0) {
708       int k{0};
709       for (int j{0}; j < rank; ++j) {
710         if (j != zbDim) {
711           if (array->shape()[j] != boundary->shape()[k]) {
712             context_.messages().Say(
713                 "Invalid 'boundary=' argument in EOSHIFT: extent on dimension %d is %jd but must be %jd"_err_en_US,
714                 k + 1, static_cast<std::intmax_t>(boundary->shape()[k]),
715                 static_cast<std::intmax_t>(array->shape()[j]));
716             ok = false;
717           }
718           ++k;
719         }
720       }
721     }
722     if (ok) {
723       std::vector<Scalar<T>> resultElements;
724       ConstantSubscripts arrayLB{array->lbounds()};
725       ConstantSubscripts arrayAt{arrayLB};
726       ConstantSubscript &dimIndex{arrayAt[zbDim]};
727       ConstantSubscript dimLB{dimIndex}; // initial value
728       ConstantSubscript dimExtent{array->shape()[zbDim]};
729       ConstantSubscripts shiftLB{shift->lbounds()};
730       ConstantSubscripts boundaryLB;
731       if (boundary) {
732         boundaryLB = boundary->lbounds();
733       }
734       for (auto n{GetSize(array->shape())}; n > 0; --n) {
735         ConstantSubscript origDimIndex{dimIndex};
736         ConstantSubscripts shiftAt;
737         if (shift->Rank() > 0) {
738           int k{0};
739           for (int j{0}; j < rank; ++j) {
740             if (j != zbDim) {
741               shiftAt.emplace_back(shiftLB[k++] + arrayAt[j] - arrayLB[j]);
742             }
743           }
744         }
745         ConstantSubscript shiftCount{shift->At(shiftAt).ToInt64()};
746         dimIndex += shiftCount;
747         if (dimIndex >= dimLB && dimIndex < dimLB + dimExtent) {
748           resultElements.push_back(array->At(arrayAt));
749         } else if (boundary) {
750           ConstantSubscripts boundaryAt;
751           if (boundary->Rank() > 0) {
752             for (int j{0}; j < rank; ++j) {
753               int k{0};
754               if (j != zbDim) {
755                 boundaryAt.emplace_back(
756                     boundaryLB[k++] + arrayAt[j] - arrayLB[j]);
757               }
758             }
759           }
760           resultElements.push_back(boundary->At(boundaryAt));
761         } else if constexpr (T::category == TypeCategory::Integer ||
762             T::category == TypeCategory::Real ||
763             T::category == TypeCategory::Complex ||
764             T::category == TypeCategory::Logical) {
765           resultElements.emplace_back();
766         } else if constexpr (T::category == TypeCategory::Character) {
767           auto len{static_cast<std::size_t>(array->LEN())};
768           typename Scalar<T>::value_type space{' '};
769           resultElements.emplace_back(len, space);
770         } else {
771           DIE("no derived type boundary");
772         }
773         dimIndex = origDimIndex;
774         array->IncrementSubscripts(arrayAt);
775       }
776       return Expr<T>{PackageConstant<T>(
777           std::move(resultElements), *array, array->shape())};
778     }
779   }
780   // Invalid, prevent re-folding
781   return MakeInvalidIntrinsic(std::move(funcRef));
782 }
783 
PACK(FunctionRef<T> && funcRef)784 template <typename T> Expr<T> Folder<T>::PACK(FunctionRef<T> &&funcRef) {
785   auto args{funcRef.arguments()};
786   CHECK(args.size() == 3);
787   const auto *array{UnwrapConstantValue<T>(args[0])};
788   const auto *vector{UnwrapConstantValue<T>(args[2])};
789   auto convertedMask{Fold(context_,
790       ConvertToType<LogicalResult>(
791           Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))};
792   const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)};
793   if (!array || !mask || (args[2] && !vector)) {
794     return Expr<T>{std::move(funcRef)};
795   }
796   // Arguments are constant.
797   ConstantSubscript arrayElements{GetSize(array->shape())};
798   ConstantSubscript truths{0};
799   ConstantSubscripts maskAt{mask->lbounds()};
800   if (mask->Rank() == 0) {
801     if (mask->At(maskAt).IsTrue()) {
802       truths = arrayElements;
803     }
804   } else if (array->shape() != mask->shape()) {
805     // Error already emitted from intrinsic processing
806     return MakeInvalidIntrinsic(std::move(funcRef));
807   } else {
808     for (ConstantSubscript j{0}; j < arrayElements;
809          ++j, mask->IncrementSubscripts(maskAt)) {
810       if (mask->At(maskAt).IsTrue()) {
811         ++truths;
812       }
813     }
814   }
815   std::vector<Scalar<T>> resultElements;
816   ConstantSubscripts arrayAt{array->lbounds()};
817   ConstantSubscript resultSize{truths};
818   if (vector) {
819     resultSize = vector->shape().at(0);
820     if (resultSize < truths) {
821       context_.messages().Say(
822           "Invalid 'vector=' argument in PACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US,
823           static_cast<std::intmax_t>(truths),
824           static_cast<std::intmax_t>(resultSize));
825       return MakeInvalidIntrinsic(std::move(funcRef));
826     }
827   }
828   for (ConstantSubscript j{0}; j < truths;) {
829     if (mask->At(maskAt).IsTrue()) {
830       resultElements.push_back(array->At(arrayAt));
831       ++j;
832     }
833     array->IncrementSubscripts(arrayAt);
834     mask->IncrementSubscripts(maskAt);
835   }
836   if (vector) {
837     ConstantSubscripts vectorAt{vector->lbounds()};
838     vectorAt.at(0) += truths;
839     for (ConstantSubscript j{truths}; j < resultSize; ++j) {
840       resultElements.push_back(vector->At(vectorAt));
841       ++vectorAt[0];
842     }
843   }
844   return Expr<T>{PackageConstant<T>(std::move(resultElements), *array,
845       ConstantSubscripts{static_cast<ConstantSubscript>(resultSize)})};
846 }
847 
RESHAPE(FunctionRef<T> && funcRef)848 template <typename T> Expr<T> Folder<T>::RESHAPE(FunctionRef<T> &&funcRef) {
849   auto args{funcRef.arguments()};
850   CHECK(args.size() == 4);
851   const auto *source{UnwrapConstantValue<T>(args[0])};
852   const auto *pad{UnwrapConstantValue<T>(args[2])};
853   std::optional<std::vector<ConstantSubscript>> shape{
854       GetIntegerVector<ConstantSubscript>(args[1])};
855   std::optional<std::vector<int>> order{GetIntegerVector<int>(args[3])};
856   if (!source || !shape || (args[2] && !pad) || (args[3] && !order)) {
857     return Expr<T>{std::move(funcRef)}; // Non-constant arguments
858   } else if (shape.value().size() > common::maxRank) {
859     context_.messages().Say(
860         "Size of 'shape=' argument must not be greater than %d"_err_en_US,
861         common::maxRank);
862   } else if (HasNegativeExtent(shape.value())) {
863     context_.messages().Say(
864         "'shape=' argument must not have a negative extent"_err_en_US);
865   } else {
866     int rank{GetRank(shape.value())};
867     std::size_t resultElements{TotalElementCount(shape.value())};
868     std::optional<std::vector<int>> dimOrder;
869     if (order) {
870       dimOrder = ValidateDimensionOrder(rank, *order);
871     }
872     std::vector<int> *dimOrderPtr{dimOrder ? &dimOrder.value() : nullptr};
873     if (order && !dimOrder) {
874       context_.messages().Say("Invalid 'order=' argument in RESHAPE"_err_en_US);
875     } else if (resultElements > source->size() && (!pad || pad->empty())) {
876       context_.messages().Say(
877           "Too few elements in 'source=' argument and 'pad=' "
878           "argument is not present or has null size"_err_en_US);
879     } else {
880       Constant<T> result{!source->empty() || !pad
881               ? source->Reshape(std::move(shape.value()))
882               : pad->Reshape(std::move(shape.value()))};
883       ConstantSubscripts subscripts{result.lbounds()};
884       auto copied{result.CopyFrom(*source,
885           std::min(source->size(), resultElements), subscripts, dimOrderPtr)};
886       if (copied < resultElements) {
887         CHECK(pad);
888         copied += result.CopyFrom(
889             *pad, resultElements - copied, subscripts, dimOrderPtr);
890       }
891       CHECK(copied == resultElements);
892       return Expr<T>{std::move(result)};
893     }
894   }
895   // Invalid, prevent re-folding
896   return MakeInvalidIntrinsic(std::move(funcRef));
897 }
898 
SPREAD(FunctionRef<T> && funcRef)899 template <typename T> Expr<T> Folder<T>::SPREAD(FunctionRef<T> &&funcRef) {
900   auto args{funcRef.arguments()};
901   CHECK(args.size() == 3);
902   const Constant<T> *source{UnwrapConstantValue<T>(args[0])};
903   auto dim{GetInt64Arg(args[1])};
904   auto ncopies{GetInt64Arg(args[2])};
905   if (!source || !dim) {
906     return Expr<T>{std::move(funcRef)};
907   }
908   int sourceRank{source->Rank()};
909   if (sourceRank >= common::maxRank) {
910     context_.messages().Say(
911         "SOURCE= argument to SPREAD has rank %d but must have rank less than %d"_err_en_US,
912         sourceRank, common::maxRank);
913   } else if (*dim < 1 || *dim > sourceRank + 1) {
914     context_.messages().Say(
915         "DIM=%d argument to SPREAD must be between 1 and %d"_err_en_US, *dim,
916         sourceRank + 1);
917   } else if (!ncopies) {
918     return Expr<T>{std::move(funcRef)};
919   } else {
920     if (*ncopies < 0) {
921       ncopies = 0;
922     }
923     // TODO: Consider moving this implementation (after the user error
924     // checks), along with other transformational intrinsics, into
925     // constant.h (or a new header) so that the transformationals
926     // are available for all Constant<>s without needing to be packaged
927     // as references to intrinsic functions for folding.
928     ConstantSubscripts shape{source->shape()};
929     shape.insert(shape.begin() + *dim - 1, *ncopies);
930     Constant<T> spread{source->Reshape(std::move(shape))};
931     std::vector<int> dimOrder;
932     for (int j{0}; j < sourceRank; ++j) {
933       dimOrder.push_back(j < *dim - 1 ? j : j + 1);
934     }
935     dimOrder.push_back(*dim - 1);
936     ConstantSubscripts at{spread.lbounds()}; // all 1
937     spread.CopyFrom(*source, TotalElementCount(spread.shape()), at, &dimOrder);
938     return Expr<T>{std::move(spread)};
939   }
940   // Invalid, prevent re-folding
941   return MakeInvalidIntrinsic(std::move(funcRef));
942 }
943 
TRANSPOSE(FunctionRef<T> && funcRef)944 template <typename T> Expr<T> Folder<T>::TRANSPOSE(FunctionRef<T> &&funcRef) {
945   auto args{funcRef.arguments()};
946   CHECK(args.size() == 1);
947   const auto *matrix{UnwrapConstantValue<T>(args[0])};
948   if (!matrix) {
949     return Expr<T>{std::move(funcRef)};
950   }
951   // Argument is constant.  Traverse its elements in transposed order.
952   std::vector<Scalar<T>> resultElements;
953   ConstantSubscripts at(2);
954   for (ConstantSubscript j{0}; j < matrix->shape()[0]; ++j) {
955     at[0] = matrix->lbounds()[0] + j;
956     for (ConstantSubscript k{0}; k < matrix->shape()[1]; ++k) {
957       at[1] = matrix->lbounds()[1] + k;
958       resultElements.push_back(matrix->At(at));
959     }
960   }
961   at = matrix->shape();
962   std::swap(at[0], at[1]);
963   return Expr<T>{PackageConstant<T>(std::move(resultElements), *matrix, at)};
964 }
965 
UNPACK(FunctionRef<T> && funcRef)966 template <typename T> Expr<T> Folder<T>::UNPACK(FunctionRef<T> &&funcRef) {
967   auto args{funcRef.arguments()};
968   CHECK(args.size() == 3);
969   const auto *vector{UnwrapConstantValue<T>(args[0])};
970   auto convertedMask{Fold(context_,
971       ConvertToType<LogicalResult>(
972           Expr<SomeLogical>{DEREF(UnwrapExpr<Expr<SomeLogical>>(args[1]))}))};
973   const auto *mask{UnwrapConstantValue<LogicalResult>(convertedMask)};
974   const auto *field{UnwrapConstantValue<T>(args[2])};
975   if (!vector || !mask || !field) {
976     return Expr<T>{std::move(funcRef)};
977   }
978   // Arguments are constant.
979   if (field->Rank() > 0 && field->shape() != mask->shape()) {
980     // Error already emitted from intrinsic processing
981     return MakeInvalidIntrinsic(std::move(funcRef));
982   }
983   ConstantSubscript maskElements{GetSize(mask->shape())};
984   ConstantSubscript truths{0};
985   ConstantSubscripts maskAt{mask->lbounds()};
986   for (ConstantSubscript j{0}; j < maskElements;
987        ++j, mask->IncrementSubscripts(maskAt)) {
988     if (mask->At(maskAt).IsTrue()) {
989       ++truths;
990     }
991   }
992   if (truths > GetSize(vector->shape())) {
993     context_.messages().Say(
994         "Invalid 'vector=' argument in UNPACK: the 'mask=' argument has %jd true elements, but the vector has only %jd elements"_err_en_US,
995         static_cast<std::intmax_t>(truths),
996         static_cast<std::intmax_t>(GetSize(vector->shape())));
997     return MakeInvalidIntrinsic(std::move(funcRef));
998   }
999   std::vector<Scalar<T>> resultElements;
1000   ConstantSubscripts vectorAt{vector->lbounds()};
1001   ConstantSubscripts fieldAt{field->lbounds()};
1002   for (ConstantSubscript j{0}; j < maskElements; ++j) {
1003     if (mask->At(maskAt).IsTrue()) {
1004       resultElements.push_back(vector->At(vectorAt));
1005       vector->IncrementSubscripts(vectorAt);
1006     } else {
1007       resultElements.push_back(field->At(fieldAt));
1008     }
1009     mask->IncrementSubscripts(maskAt);
1010     field->IncrementSubscripts(fieldAt);
1011   }
1012   return Expr<T>{
1013       PackageConstant<T>(std::move(resultElements), *vector, mask->shape())};
1014 }
1015 
1016 std::optional<Expr<SomeType>> FoldTransfer(
1017     FoldingContext &, const ActualArguments &);
1018 
TRANSFER(FunctionRef<T> && funcRef)1019 template <typename T> Expr<T> Folder<T>::TRANSFER(FunctionRef<T> &&funcRef) {
1020   if (auto folded{FoldTransfer(context_, funcRef.arguments())}) {
1021     return DEREF(UnwrapExpr<Expr<T>>(*folded));
1022   } else {
1023     return Expr<T>{std::move(funcRef)};
1024   }
1025 }
1026 
1027 template <typename T>
FoldMINorMAX(FoldingContext & context,FunctionRef<T> && funcRef,Ordering order)1028 Expr<T> FoldMINorMAX(
1029     FoldingContext &context, FunctionRef<T> &&funcRef, Ordering order) {
1030   static_assert(T::category == TypeCategory::Integer ||
1031       T::category == TypeCategory::Real ||
1032       T::category == TypeCategory::Character);
1033   std::vector<Constant<T> *> constantArgs;
1034   // Call Folding on all arguments, even if some are not constant,
1035   // to make operand promotion explicit.
1036   for (auto &arg : funcRef.arguments()) {
1037     if (auto *cst{Folder<T>{context}.Folding(arg)}) {
1038       constantArgs.push_back(cst);
1039     }
1040   }
1041   if (constantArgs.size() != funcRef.arguments().size()) {
1042     return Expr<T>(std::move(funcRef));
1043   }
1044   CHECK(!constantArgs.empty());
1045   Expr<T> result{std::move(*constantArgs[0])};
1046   for (std::size_t i{1}; i < constantArgs.size(); ++i) {
1047     Extremum<T> extremum{order, result, Expr<T>{std::move(*constantArgs[i])}};
1048     result = FoldOperation(context, std::move(extremum));
1049   }
1050   return result;
1051 }
1052 
1053 // For AMAX0, AMIN0, AMAX1, AMIN1, DMAX1, DMIN1, MAX0, MIN0, MAX1, and MIN1
1054 // a special care has to be taken to insert the conversion on the result
1055 // of the MIN/MAX. This is made slightly more complex by the extension
1056 // supported by f18 that arguments may have different kinds. This implies
1057 // that the created MIN/MAX result type cannot be deduced from the standard but
1058 // has to be deduced from the arguments.
1059 // e.g. AMAX0(int8, int4) is rewritten to REAL(MAX(int8, INT(int4, 8)))).
1060 template <typename T>
RewriteSpecificMINorMAX(FoldingContext & context,FunctionRef<T> && funcRef)1061 Expr<T> RewriteSpecificMINorMAX(
1062     FoldingContext &context, FunctionRef<T> &&funcRef) {
1063   ActualArguments &args{funcRef.arguments()};
1064   auto &intrinsic{DEREF(std::get_if<SpecificIntrinsic>(&funcRef.proc().u))};
1065   // Rewrite MAX1(args) to INT(MAX(args)) and fold. Same logic for MIN1.
1066   // Find result type for max/min based on the arguments.
1067   DynamicType resultType{args[0].value().GetType().value()};
1068   auto *resultTypeArg{&args[0]};
1069   for (auto j{args.size() - 1}; j > 0; --j) {
1070     DynamicType type{args[j].value().GetType().value()};
1071     if (type.category() == resultType.category()) {
1072       if (type.kind() > resultType.kind()) {
1073         resultTypeArg = &args[j];
1074         resultType = type;
1075       }
1076     } else if (resultType.category() == TypeCategory::Integer) {
1077       // Handle mixed real/integer arguments: all the previous arguments were
1078       // integers and this one is real. The type of the MAX/MIN result will
1079       // be the one of the real argument.
1080       resultTypeArg = &args[j];
1081       resultType = type;
1082     }
1083   }
1084   intrinsic.name =
1085       intrinsic.name.find("max") != std::string::npos ? "max"s : "min"s;
1086   intrinsic.characteristics.value().functionResult.value().SetType(resultType);
1087   auto insertConversion{[&](const auto &x) -> Expr<T> {
1088     using TR = ResultType<decltype(x)>;
1089     FunctionRef<TR> maxRef{std::move(funcRef.proc()), std::move(args)};
1090     return Fold(context, ConvertToType<T>(AsCategoryExpr(std::move(maxRef))));
1091   }};
1092   if (auto *sx{UnwrapExpr<Expr<SomeReal>>(*resultTypeArg)}) {
1093     return common::visit(insertConversion, sx->u);
1094   }
1095   auto &sx{DEREF(UnwrapExpr<Expr<SomeInteger>>(*resultTypeArg))};
1096   return common::visit(insertConversion, sx.u);
1097 }
1098 
1099 // FoldIntrinsicFunction()
1100 template <int KIND>
1101 Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
1102     FoldingContext &context, FunctionRef<Type<TypeCategory::Integer, KIND>> &&);
1103 template <int KIND>
1104 Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
1105     FoldingContext &context, FunctionRef<Type<TypeCategory::Real, KIND>> &&);
1106 template <int KIND>
1107 Expr<Type<TypeCategory::Complex, KIND>> FoldIntrinsicFunction(
1108     FoldingContext &context, FunctionRef<Type<TypeCategory::Complex, KIND>> &&);
1109 template <int KIND>
1110 Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction(
1111     FoldingContext &context, FunctionRef<Type<TypeCategory::Logical, KIND>> &&);
1112 
1113 template <typename T>
FoldOperation(FoldingContext & context,FunctionRef<T> && funcRef)1114 Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
1115   ActualArguments &args{funcRef.arguments()};
1116   for (std::optional<ActualArgument> &arg : args) {
1117     if (auto *expr{UnwrapExpr<Expr<SomeType>>(arg)}) {
1118       *expr = Fold(context, std::move(*expr));
1119     }
1120   }
1121   if (auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)}) {
1122     const std::string name{intrinsic->name};
1123     if (name == "cshift") {
1124       return Folder<T>{context}.CSHIFT(std::move(funcRef));
1125     } else if (name == "eoshift") {
1126       return Folder<T>{context}.EOSHIFT(std::move(funcRef));
1127     } else if (name == "pack") {
1128       return Folder<T>{context}.PACK(std::move(funcRef));
1129     } else if (name == "reshape") {
1130       return Folder<T>{context}.RESHAPE(std::move(funcRef));
1131     } else if (name == "spread") {
1132       return Folder<T>{context}.SPREAD(std::move(funcRef));
1133     } else if (name == "transfer") {
1134       return Folder<T>{context}.TRANSFER(std::move(funcRef));
1135     } else if (name == "transpose") {
1136       return Folder<T>{context}.TRANSPOSE(std::move(funcRef));
1137     } else if (name == "unpack") {
1138       return Folder<T>{context}.UNPACK(std::move(funcRef));
1139     }
1140     // TODO: extends_type_of, same_type_as
1141     if constexpr (!std::is_same_v<T, SomeDerived>) {
1142       return FoldIntrinsicFunction(context, std::move(funcRef));
1143     }
1144   }
1145   return Expr<T>{std::move(funcRef)};
1146 }
1147 
1148 template <typename T>
FoldMerge(FoldingContext & context,FunctionRef<T> && funcRef)1149 Expr<T> FoldMerge(FoldingContext &context, FunctionRef<T> &&funcRef) {
1150   return FoldElementalIntrinsic<T, T, T, LogicalResult>(context,
1151       std::move(funcRef),
1152       ScalarFunc<T, T, T, LogicalResult>(
1153           [](const Scalar<T> &ifTrue, const Scalar<T> &ifFalse,
1154               const Scalar<LogicalResult> &predicate) -> Scalar<T> {
1155             return predicate.IsTrue() ? ifTrue : ifFalse;
1156           }));
1157 }
1158 
1159 Expr<ImpliedDoIndex::Result> FoldOperation(FoldingContext &, ImpliedDoIndex &&);
1160 
1161 // Array constructor folding
1162 template <typename T> class ArrayConstructorFolder {
1163 public:
ArrayConstructorFolder(FoldingContext & c)1164   explicit ArrayConstructorFolder(FoldingContext &c) : context_{c} {}
1165 
FoldArray(ArrayConstructor<T> && array)1166   Expr<T> FoldArray(ArrayConstructor<T> &&array) {
1167     // Calls FoldArray(const ArrayConstructorValues<T> &) below
1168     if (FoldArray(array)) {
1169       auto n{static_cast<ConstantSubscript>(elements_.size())};
1170       if constexpr (std::is_same_v<T, SomeDerived>) {
1171         return Expr<T>{Constant<T>{array.GetType().GetDerivedTypeSpec(),
1172             std::move(elements_), ConstantSubscripts{n}}};
1173       } else if constexpr (T::category == TypeCategory::Character) {
1174         auto length{Fold(context_, common::Clone(array.LEN()))};
1175         if (std::optional<ConstantSubscript> lengthValue{ToInt64(length)}) {
1176           return Expr<T>{Constant<T>{
1177               *lengthValue, std::move(elements_), ConstantSubscripts{n}}};
1178         }
1179       } else {
1180         return Expr<T>{
1181             Constant<T>{std::move(elements_), ConstantSubscripts{n}}};
1182       }
1183     }
1184     return Expr<T>{std::move(array)};
1185   }
1186 
1187 private:
FoldArray(const Expr<T> & expr)1188   bool FoldArray(const Expr<T> &expr) {
1189     Expr<T> folded{Fold(context_, common::Clone(expr))};
1190     if (const auto *c{UnwrapConstantValue<T>(folded)}) {
1191       // Copy elements in Fortran array element order
1192       if (!c->empty()) {
1193         ConstantSubscripts index{c->lbounds()};
1194         do {
1195           elements_.emplace_back(c->At(index));
1196         } while (c->IncrementSubscripts(index));
1197       }
1198       return true;
1199     } else {
1200       return false;
1201     }
1202   }
FoldArray(const common::CopyableIndirection<Expr<T>> & expr)1203   bool FoldArray(const common::CopyableIndirection<Expr<T>> &expr) {
1204     return FoldArray(expr.value());
1205   }
FoldArray(const ImpliedDo<T> & iDo)1206   bool FoldArray(const ImpliedDo<T> &iDo) {
1207     Expr<SubscriptInteger> lower{
1208         Fold(context_, Expr<SubscriptInteger>{iDo.lower()})};
1209     Expr<SubscriptInteger> upper{
1210         Fold(context_, Expr<SubscriptInteger>{iDo.upper()})};
1211     Expr<SubscriptInteger> stride{
1212         Fold(context_, Expr<SubscriptInteger>{iDo.stride()})};
1213     std::optional<ConstantSubscript> start{ToInt64(lower)}, end{ToInt64(upper)},
1214         step{ToInt64(stride)};
1215     if (start && end && step && *step != 0) {
1216       bool result{true};
1217       ConstantSubscript &j{context_.StartImpliedDo(iDo.name(), *start)};
1218       if (*step > 0) {
1219         for (; j <= *end; j += *step) {
1220           result &= FoldArray(iDo.values());
1221         }
1222       } else {
1223         for (; j >= *end; j += *step) {
1224           result &= FoldArray(iDo.values());
1225         }
1226       }
1227       context_.EndImpliedDo(iDo.name());
1228       return result;
1229     } else {
1230       return false;
1231     }
1232   }
FoldArray(const ArrayConstructorValue<T> & x)1233   bool FoldArray(const ArrayConstructorValue<T> &x) {
1234     return common::visit([&](const auto &y) { return FoldArray(y); }, x.u);
1235   }
FoldArray(const ArrayConstructorValues<T> & xs)1236   bool FoldArray(const ArrayConstructorValues<T> &xs) {
1237     for (const auto &x : xs) {
1238       if (!FoldArray(x)) {
1239         return false;
1240       }
1241     }
1242     return true;
1243   }
1244 
1245   FoldingContext &context_;
1246   std::vector<Scalar<T>> elements_;
1247 };
1248 
1249 template <typename T>
FoldOperation(FoldingContext & context,ArrayConstructor<T> && array)1250 Expr<T> FoldOperation(FoldingContext &context, ArrayConstructor<T> &&array) {
1251   return ArrayConstructorFolder<T>{context}.FoldArray(std::move(array));
1252 }
1253 
1254 // Array operation elemental application: When all operands to an operation
1255 // are constant arrays, array constructors without any implied DO loops,
1256 // &/or expanded scalars, pull the operation "into" the array result by
1257 // applying it in an elementwise fashion.  For example, [A,1]+[B,2]
1258 // is rewritten into [A+B,1+2] and then partially folded to [A+B,3].
1259 
1260 // If possible, restructures an array expression into an array constructor
1261 // that comprises a "flat" ArrayConstructorValues with no implied DO loops.
1262 template <typename T>
ArrayConstructorIsFlat(const ArrayConstructorValues<T> & values)1263 bool ArrayConstructorIsFlat(const ArrayConstructorValues<T> &values) {
1264   for (const ArrayConstructorValue<T> &x : values) {
1265     if (!std::holds_alternative<Expr<T>>(x.u)) {
1266       return false;
1267     }
1268   }
1269   return true;
1270 }
1271 
1272 template <typename T>
AsFlatArrayConstructor(const Expr<T> & expr)1273 std::optional<Expr<T>> AsFlatArrayConstructor(const Expr<T> &expr) {
1274   if (const auto *c{UnwrapConstantValue<T>(expr)}) {
1275     ArrayConstructor<T> result{expr};
1276     if (!c->empty()) {
1277       ConstantSubscripts at{c->lbounds()};
1278       do {
1279         result.Push(Expr<T>{Constant<T>{c->At(at)}});
1280       } while (c->IncrementSubscripts(at));
1281     }
1282     return std::make_optional<Expr<T>>(std::move(result));
1283   } else if (const auto *a{UnwrapExpr<ArrayConstructor<T>>(expr)}) {
1284     if (ArrayConstructorIsFlat(*a)) {
1285       return std::make_optional<Expr<T>>(expr);
1286     }
1287   } else if (const auto *p{UnwrapExpr<Parentheses<T>>(expr)}) {
1288     return AsFlatArrayConstructor(Expr<T>{p->left()});
1289   }
1290   return std::nullopt;
1291 }
1292 
1293 template <TypeCategory CAT>
1294 std::enable_if_t<CAT != TypeCategory::Derived,
1295     std::optional<Expr<SomeKind<CAT>>>>
AsFlatArrayConstructor(const Expr<SomeKind<CAT>> & expr)1296 AsFlatArrayConstructor(const Expr<SomeKind<CAT>> &expr) {
1297   return common::visit(
1298       [&](const auto &kindExpr) -> std::optional<Expr<SomeKind<CAT>>> {
1299         if (auto flattened{AsFlatArrayConstructor(kindExpr)}) {
1300           return Expr<SomeKind<CAT>>{std::move(*flattened)};
1301         } else {
1302           return std::nullopt;
1303         }
1304       },
1305       expr.u);
1306 }
1307 
1308 // FromArrayConstructor is a subroutine for MapOperation() below.
1309 // Given a flat ArrayConstructor<T> and a shape, it wraps the array
1310 // into an Expr<T>, folds it, and returns the resulting wrapped
1311 // array constructor or constant array value.
1312 template <typename T>
FromArrayConstructor(FoldingContext & context,ArrayConstructor<T> && values,std::optional<ConstantSubscripts> && shape)1313 Expr<T> FromArrayConstructor(FoldingContext &context,
1314     ArrayConstructor<T> &&values, std::optional<ConstantSubscripts> &&shape) {
1315   Expr<T> result{Fold(context, Expr<T>{std::move(values)})};
1316   if (shape) {
1317     if (auto *constant{UnwrapConstantValue<T>(result)}) {
1318       return Expr<T>{constant->Reshape(std::move(*shape))};
1319     }
1320   }
1321   return result;
1322 }
1323 
1324 // MapOperation is a utility for various specializations of ApplyElementwise()
1325 // that follow.  Given one or two flat ArrayConstructor<OPERAND> (wrapped in an
1326 // Expr<OPERAND>) for some specific operand type(s), apply a given function f
1327 // to each of their corresponding elements to produce a flat
1328 // ArrayConstructor<RESULT> (wrapped in an Expr<RESULT>).
1329 // Preserves shape.
1330 
1331 // Unary case
1332 template <typename RESULT, typename OPERAND>
MapOperation(FoldingContext & context,std::function<Expr<RESULT> (Expr<OPERAND> &&)> && f,const Shape & shape,Expr<OPERAND> && values)1333 Expr<RESULT> MapOperation(FoldingContext &context,
1334     std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f, const Shape &shape,
1335     Expr<OPERAND> &&values) {
1336   ArrayConstructor<RESULT> result{values};
1337   if constexpr (common::HasMember<OPERAND, AllIntrinsicCategoryTypes>) {
1338     common::visit(
1339         [&](auto &&kindExpr) {
1340           using kindType = ResultType<decltype(kindExpr)>;
1341           auto &aConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1342           for (auto &acValue : aConst) {
1343             auto &scalar{std::get<Expr<kindType>>(acValue.u)};
1344             result.Push(Fold(context, f(Expr<OPERAND>{std::move(scalar)})));
1345           }
1346         },
1347         std::move(values.u));
1348   } else {
1349     auto &aConst{std::get<ArrayConstructor<OPERAND>>(values.u)};
1350     for (auto &acValue : aConst) {
1351       auto &scalar{std::get<Expr<OPERAND>>(acValue.u)};
1352       result.Push(Fold(context, f(std::move(scalar))));
1353     }
1354   }
1355   return FromArrayConstructor(
1356       context, std::move(result), AsConstantExtents(context, shape));
1357 }
1358 
1359 template <typename RESULT, typename A>
ArrayConstructorFromMold(const A & prototype,std::optional<Expr<SubscriptInteger>> && length)1360 ArrayConstructor<RESULT> ArrayConstructorFromMold(
1361     const A &prototype, std::optional<Expr<SubscriptInteger>> &&length) {
1362   if constexpr (RESULT::category == TypeCategory::Character) {
1363     return ArrayConstructor<RESULT>{
1364         std::move(length.value()), ArrayConstructorValues<RESULT>{}};
1365   } else {
1366     return ArrayConstructor<RESULT>{prototype};
1367   }
1368 }
1369 
1370 // array * array case
1371 template <typename RESULT, typename LEFT, typename RIGHT>
MapOperation(FoldingContext & context,std::function<Expr<RESULT> (Expr<LEFT> &&,Expr<RIGHT> &&)> && f,const Shape & shape,std::optional<Expr<SubscriptInteger>> && length,Expr<LEFT> && leftValues,Expr<RIGHT> && rightValues)1372 Expr<RESULT> MapOperation(FoldingContext &context,
1373     std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1374     const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1375     Expr<LEFT> &&leftValues, Expr<RIGHT> &&rightValues) {
1376   auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))};
1377   auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)};
1378   if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) {
1379     common::visit(
1380         [&](auto &&kindExpr) {
1381           using kindType = ResultType<decltype(kindExpr)>;
1382 
1383           auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1384           auto rightIter{rightArrConst.begin()};
1385           for (auto &leftValue : leftArrConst) {
1386             CHECK(rightIter != rightArrConst.end());
1387             auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1388             auto &rightScalar{std::get<Expr<kindType>>(rightIter->u)};
1389             result.Push(Fold(context,
1390                 f(std::move(leftScalar), Expr<RIGHT>{std::move(rightScalar)})));
1391             ++rightIter;
1392           }
1393         },
1394         std::move(rightValues.u));
1395   } else {
1396     auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)};
1397     auto rightIter{rightArrConst.begin()};
1398     for (auto &leftValue : leftArrConst) {
1399       CHECK(rightIter != rightArrConst.end());
1400       auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1401       auto &rightScalar{std::get<Expr<RIGHT>>(rightIter->u)};
1402       result.Push(
1403           Fold(context, f(std::move(leftScalar), std::move(rightScalar))));
1404       ++rightIter;
1405     }
1406   }
1407   return FromArrayConstructor(
1408       context, std::move(result), AsConstantExtents(context, shape));
1409 }
1410 
1411 // array * scalar case
1412 template <typename RESULT, typename LEFT, typename RIGHT>
MapOperation(FoldingContext & context,std::function<Expr<RESULT> (Expr<LEFT> &&,Expr<RIGHT> &&)> && f,const Shape & shape,std::optional<Expr<SubscriptInteger>> && length,Expr<LEFT> && leftValues,const Expr<RIGHT> & rightScalar)1413 Expr<RESULT> MapOperation(FoldingContext &context,
1414     std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1415     const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1416     Expr<LEFT> &&leftValues, const Expr<RIGHT> &rightScalar) {
1417   auto result{ArrayConstructorFromMold<RESULT>(leftValues, std::move(length))};
1418   auto &leftArrConst{std::get<ArrayConstructor<LEFT>>(leftValues.u)};
1419   for (auto &leftValue : leftArrConst) {
1420     auto &leftScalar{std::get<Expr<LEFT>>(leftValue.u)};
1421     result.Push(
1422         Fold(context, f(std::move(leftScalar), Expr<RIGHT>{rightScalar})));
1423   }
1424   return FromArrayConstructor(
1425       context, std::move(result), AsConstantExtents(context, shape));
1426 }
1427 
1428 // scalar * array case
1429 template <typename RESULT, typename LEFT, typename RIGHT>
MapOperation(FoldingContext & context,std::function<Expr<RESULT> (Expr<LEFT> &&,Expr<RIGHT> &&)> && f,const Shape & shape,std::optional<Expr<SubscriptInteger>> && length,const Expr<LEFT> & leftScalar,Expr<RIGHT> && rightValues)1430 Expr<RESULT> MapOperation(FoldingContext &context,
1431     std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f,
1432     const Shape &shape, std::optional<Expr<SubscriptInteger>> &&length,
1433     const Expr<LEFT> &leftScalar, Expr<RIGHT> &&rightValues) {
1434   auto result{ArrayConstructorFromMold<RESULT>(leftScalar, std::move(length))};
1435   if constexpr (common::HasMember<RIGHT, AllIntrinsicCategoryTypes>) {
1436     common::visit(
1437         [&](auto &&kindExpr) {
1438           using kindType = ResultType<decltype(kindExpr)>;
1439           auto &rightArrConst{std::get<ArrayConstructor<kindType>>(kindExpr.u)};
1440           for (auto &rightValue : rightArrConst) {
1441             auto &rightScalar{std::get<Expr<kindType>>(rightValue.u)};
1442             result.Push(Fold(context,
1443                 f(Expr<LEFT>{leftScalar},
1444                     Expr<RIGHT>{std::move(rightScalar)})));
1445           }
1446         },
1447         std::move(rightValues.u));
1448   } else {
1449     auto &rightArrConst{std::get<ArrayConstructor<RIGHT>>(rightValues.u)};
1450     for (auto &rightValue : rightArrConst) {
1451       auto &rightScalar{std::get<Expr<RIGHT>>(rightValue.u)};
1452       result.Push(
1453           Fold(context, f(Expr<LEFT>{leftScalar}, std::move(rightScalar))));
1454     }
1455   }
1456   return FromArrayConstructor(
1457       context, std::move(result), AsConstantExtents(context, shape));
1458 }
1459 
1460 template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
ComputeResultLength(Operation<DERIVED,RESULT,LEFT,RIGHT> & operation)1461 std::optional<Expr<SubscriptInteger>> ComputeResultLength(
1462     Operation<DERIVED, RESULT, LEFT, RIGHT> &operation) {
1463   if constexpr (RESULT::category == TypeCategory::Character) {
1464     return Expr<RESULT>{operation.derived()}.LEN();
1465   }
1466   return std::nullopt;
1467 }
1468 
1469 // ApplyElementwise() recursively folds the operand expression(s) of an
1470 // operation, then attempts to apply the operation to the (corresponding)
1471 // scalar element(s) of those operands.  Returns std::nullopt for scalars
1472 // or unlinearizable operands.
1473 template <typename DERIVED, typename RESULT, typename OPERAND>
1474 auto ApplyElementwise(FoldingContext &context,
1475     Operation<DERIVED, RESULT, OPERAND> &operation,
1476     std::function<Expr<RESULT>(Expr<OPERAND> &&)> &&f)
1477     -> std::optional<Expr<RESULT>> {
1478   auto &expr{operation.left()};
1479   expr = Fold(context, std::move(expr));
1480   if (expr.Rank() > 0) {
1481     if (std::optional<Shape> shape{GetShape(context, expr)}) {
1482       if (auto values{AsFlatArrayConstructor(expr)}) {
1483         return MapOperation(context, std::move(f), *shape, std::move(*values));
1484       }
1485     }
1486   }
1487   return std::nullopt;
1488 }
1489 
1490 template <typename DERIVED, typename RESULT, typename OPERAND>
1491 auto ApplyElementwise(
1492     FoldingContext &context, Operation<DERIVED, RESULT, OPERAND> &operation)
1493     -> std::optional<Expr<RESULT>> {
1494   return ApplyElementwise(context, operation,
1495       std::function<Expr<RESULT>(Expr<OPERAND> &&)>{
1496           [](Expr<OPERAND> &&operand) {
1497             return Expr<RESULT>{DERIVED{std::move(operand)}};
1498           }});
1499 }
1500 
1501 template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
1502 auto ApplyElementwise(FoldingContext &context,
1503     Operation<DERIVED, RESULT, LEFT, RIGHT> &operation,
1504     std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)> &&f)
1505     -> std::optional<Expr<RESULT>> {
1506   auto resultLength{ComputeResultLength(operation)};
1507   auto &leftExpr{operation.left()};
1508   leftExpr = Fold(context, std::move(leftExpr));
1509   auto &rightExpr{operation.right()};
1510   rightExpr = Fold(context, std::move(rightExpr));
1511   if (leftExpr.Rank() > 0) {
1512     if (std::optional<Shape> leftShape{GetShape(context, leftExpr)}) {
1513       if (auto left{AsFlatArrayConstructor(leftExpr)}) {
1514         if (rightExpr.Rank() > 0) {
1515           if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) {
1516             if (auto right{AsFlatArrayConstructor(rightExpr)}) {
1517               if (CheckConformance(context.messages(), *leftShape, *rightShape,
1518                       CheckConformanceFlags::EitherScalarExpandable)
1519                       .value_or(false /*fail if not known now to conform*/)) {
1520                 return MapOperation(context, std::move(f), *leftShape,
1521                     std::move(resultLength), std::move(*left),
1522                     std::move(*right));
1523               } else {
1524                 return std::nullopt;
1525               }
1526               return MapOperation(context, std::move(f), *leftShape,
1527                   std::move(resultLength), std::move(*left), std::move(*right));
1528             }
1529           }
1530         } else if (IsExpandableScalar(rightExpr)) {
1531           return MapOperation(context, std::move(f), *leftShape,
1532               std::move(resultLength), std::move(*left), rightExpr);
1533         }
1534       }
1535     }
1536   } else if (rightExpr.Rank() > 0 && IsExpandableScalar(leftExpr)) {
1537     if (std::optional<Shape> shape{GetShape(context, rightExpr)}) {
1538       if (auto right{AsFlatArrayConstructor(rightExpr)}) {
1539         return MapOperation(context, std::move(f), *shape,
1540             std::move(resultLength), leftExpr, std::move(*right));
1541       }
1542     }
1543   }
1544   return std::nullopt;
1545 }
1546 
1547 template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
1548 auto ApplyElementwise(
1549     FoldingContext &context, Operation<DERIVED, RESULT, LEFT, RIGHT> &operation)
1550     -> std::optional<Expr<RESULT>> {
1551   return ApplyElementwise(context, operation,
1552       std::function<Expr<RESULT>(Expr<LEFT> &&, Expr<RIGHT> &&)>{
1553           [](Expr<LEFT> &&left, Expr<RIGHT> &&right) {
1554             return Expr<RESULT>{DERIVED{std::move(left), std::move(right)}};
1555           }});
1556 }
1557 
1558 // Unary operations
1559 
1560 template <typename TO, typename FROM>
ConvertString(FROM && s)1561 common::IfNoLvalue<std::optional<TO>, FROM> ConvertString(FROM &&s) {
1562   if constexpr (std::is_same_v<TO, FROM>) {
1563     return std::make_optional<TO>(std::move(s));
1564   } else {
1565     // Fortran character conversion is well defined between distinct kinds
1566     // only when the actual characters are valid 7-bit ASCII.
1567     TO str;
1568     for (auto iter{s.cbegin()}; iter != s.cend(); ++iter) {
1569       if (static_cast<std::uint64_t>(*iter) > 127) {
1570         return std::nullopt;
1571       }
1572       str.push_back(*iter);
1573     }
1574     return std::make_optional<TO>(std::move(str));
1575   }
1576 }
1577 
1578 template <typename TO, TypeCategory FROMCAT>
FoldOperation(FoldingContext & context,Convert<TO,FROMCAT> && convert)1579 Expr<TO> FoldOperation(
1580     FoldingContext &context, Convert<TO, FROMCAT> &&convert) {
1581   if (auto array{ApplyElementwise(context, convert)}) {
1582     return *array;
1583   }
1584   struct {
1585     FoldingContext &context;
1586     Convert<TO, FROMCAT> &convert;
1587   } msvcWorkaround{context, convert};
1588   return common::visit(
1589       [&msvcWorkaround](auto &kindExpr) -> Expr<TO> {
1590         using Operand = ResultType<decltype(kindExpr)>;
1591         // This variable is a workaround for msvc which emits an error when
1592         // using the FROMCAT template parameter below.
1593         TypeCategory constexpr FromCat{FROMCAT};
1594         static_assert(FromCat == Operand::category);
1595         auto &convert{msvcWorkaround.convert};
1596         char buffer[64];
1597         if (auto value{GetScalarConstantValue<Operand>(kindExpr)}) {
1598           FoldingContext &ctx{msvcWorkaround.context};
1599           if constexpr (TO::category == TypeCategory::Integer) {
1600             if constexpr (FromCat == TypeCategory::Integer) {
1601               auto converted{Scalar<TO>::ConvertSigned(*value)};
1602               if (converted.overflow) {
1603                 ctx.messages().Say(
1604                     "INTEGER(%d) to INTEGER(%d) conversion overflowed"_warn_en_US,
1605                     Operand::kind, TO::kind);
1606               }
1607               return ScalarConstantToExpr(std::move(converted.value));
1608             } else if constexpr (FromCat == TypeCategory::Real) {
1609               auto converted{value->template ToInteger<Scalar<TO>>()};
1610               if (converted.flags.test(RealFlag::InvalidArgument)) {
1611                 ctx.messages().Say(
1612                     "REAL(%d) to INTEGER(%d) conversion: invalid argument"_warn_en_US,
1613                     Operand::kind, TO::kind);
1614               } else if (converted.flags.test(RealFlag::Overflow)) {
1615                 ctx.messages().Say(
1616                     "REAL(%d) to INTEGER(%d) conversion overflowed"_warn_en_US,
1617                     Operand::kind, TO::kind);
1618               }
1619               return ScalarConstantToExpr(std::move(converted.value));
1620             }
1621           } else if constexpr (TO::category == TypeCategory::Real) {
1622             if constexpr (FromCat == TypeCategory::Integer) {
1623               auto converted{Scalar<TO>::FromInteger(*value)};
1624               if (!converted.flags.empty()) {
1625                 std::snprintf(buffer, sizeof buffer,
1626                     "INTEGER(%d) to REAL(%d) conversion", Operand::kind,
1627                     TO::kind);
1628                 RealFlagWarnings(ctx, converted.flags, buffer);
1629               }
1630               return ScalarConstantToExpr(std::move(converted.value));
1631             } else if constexpr (FromCat == TypeCategory::Real) {
1632               auto converted{Scalar<TO>::Convert(*value)};
1633               if (!converted.flags.empty()) {
1634                 std::snprintf(buffer, sizeof buffer,
1635                     "REAL(%d) to REAL(%d) conversion", Operand::kind, TO::kind);
1636                 RealFlagWarnings(ctx, converted.flags, buffer);
1637               }
1638               if (ctx.targetCharacteristics().areSubnormalsFlushedToZero()) {
1639                 converted.value = converted.value.FlushSubnormalToZero();
1640               }
1641               return ScalarConstantToExpr(std::move(converted.value));
1642             }
1643           } else if constexpr (TO::category == TypeCategory::Complex) {
1644             if constexpr (FromCat == TypeCategory::Complex) {
1645               return FoldOperation(ctx,
1646                   ComplexConstructor<TO::kind>{
1647                       AsExpr(Convert<typename TO::Part>{AsCategoryExpr(
1648                           Constant<typename Operand::Part>{value->REAL()})}),
1649                       AsExpr(Convert<typename TO::Part>{AsCategoryExpr(
1650                           Constant<typename Operand::Part>{value->AIMAG()})})});
1651             }
1652           } else if constexpr (TO::category == TypeCategory::Character &&
1653               FromCat == TypeCategory::Character) {
1654             if (auto converted{ConvertString<Scalar<TO>>(std::move(*value))}) {
1655               return ScalarConstantToExpr(std::move(*converted));
1656             }
1657           } else if constexpr (TO::category == TypeCategory::Logical &&
1658               FromCat == TypeCategory::Logical) {
1659             return Expr<TO>{value->IsTrue()};
1660           }
1661         } else if constexpr (TO::category == FromCat &&
1662             FromCat != TypeCategory::Character) {
1663           // Conversion of non-constant in same type category
1664           if constexpr (std::is_same_v<Operand, TO>) {
1665             return std::move(kindExpr); // remove needless conversion
1666           } else if constexpr (TO::category == TypeCategory::Logical ||
1667               TO::category == TypeCategory::Integer) {
1668             if (auto *innerConv{
1669                     std::get_if<Convert<Operand, TO::category>>(&kindExpr.u)}) {
1670               // Conversion of conversion of same category & kind
1671               if (auto *x{std::get_if<Expr<TO>>(&innerConv->left().u)}) {
1672                 if constexpr (TO::category == TypeCategory::Logical ||
1673                     TO::kind <= Operand::kind) {
1674                   return std::move(*x); // no-op Logical or Integer
1675                                         // widening/narrowing conversion pair
1676                 } else if constexpr (std::is_same_v<TO,
1677                                          DescriptorInquiry::Result>) {
1678                   if (std::holds_alternative<DescriptorInquiry>(x->u) ||
1679                       std::holds_alternative<TypeParamInquiry>(x->u)) {
1680                     // int(int(size(...),kind=k),kind=8) -> size(...)
1681                     return std::move(*x);
1682                   }
1683                 }
1684               }
1685             }
1686           }
1687         }
1688         return Expr<TO>{std::move(convert)};
1689       },
1690       convert.left().u);
1691 }
1692 
1693 template <typename T>
FoldOperation(FoldingContext & context,Parentheses<T> && x)1694 Expr<T> FoldOperation(FoldingContext &context, Parentheses<T> &&x) {
1695   auto &operand{x.left()};
1696   operand = Fold(context, std::move(operand));
1697   if (auto value{GetScalarConstantValue<T>(operand)}) {
1698     // Preserve parentheses, even around constants.
1699     return Expr<T>{Parentheses<T>{Expr<T>{Constant<T>{*value}}}};
1700   } else if (std::holds_alternative<Parentheses<T>>(operand.u)) {
1701     // ((x)) -> (x)
1702     return std::move(operand);
1703   } else {
1704     return Expr<T>{Parentheses<T>{std::move(operand)}};
1705   }
1706 }
1707 
1708 template <typename T>
FoldOperation(FoldingContext & context,Negate<T> && x)1709 Expr<T> FoldOperation(FoldingContext &context, Negate<T> &&x) {
1710   if (auto array{ApplyElementwise(context, x)}) {
1711     return *array;
1712   }
1713   auto &operand{x.left()};
1714   if (auto *nn{std::get_if<Negate<T>>(&x.left().u)}) {
1715     return std::move(nn->left()); // -(-x) -> x
1716   } else if (auto value{GetScalarConstantValue<T>(operand)}) {
1717     if constexpr (T::category == TypeCategory::Integer) {
1718       auto negated{value->Negate()};
1719       if (negated.overflow) {
1720         context.messages().Say(
1721             "INTEGER(%d) negation overflowed"_warn_en_US, T::kind);
1722       }
1723       return Expr<T>{Constant<T>{std::move(negated.value)}};
1724     } else {
1725       // REAL & COMPLEX negation: no exceptions possible
1726       return Expr<T>{Constant<T>{value->Negate()}};
1727     }
1728   }
1729   return Expr<T>{std::move(x)};
1730 }
1731 
1732 // Binary (dyadic) operations
1733 
1734 template <typename LEFT, typename RIGHT>
OperandsAreConstants(const Expr<LEFT> & x,const Expr<RIGHT> & y)1735 std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants(
1736     const Expr<LEFT> &x, const Expr<RIGHT> &y) {
1737   if (auto xvalue{GetScalarConstantValue<LEFT>(x)}) {
1738     if (auto yvalue{GetScalarConstantValue<RIGHT>(y)}) {
1739       return {std::make_pair(*xvalue, *yvalue)};
1740     }
1741   }
1742   return std::nullopt;
1743 }
1744 
1745 template <typename DERIVED, typename RESULT, typename LEFT, typename RIGHT>
OperandsAreConstants(const Operation<DERIVED,RESULT,LEFT,RIGHT> & operation)1746 std::optional<std::pair<Scalar<LEFT>, Scalar<RIGHT>>> OperandsAreConstants(
1747     const Operation<DERIVED, RESULT, LEFT, RIGHT> &operation) {
1748   return OperandsAreConstants(operation.left(), operation.right());
1749 }
1750 
1751 template <typename T>
FoldOperation(FoldingContext & context,Add<T> && x)1752 Expr<T> FoldOperation(FoldingContext &context, Add<T> &&x) {
1753   if (auto array{ApplyElementwise(context, x)}) {
1754     return *array;
1755   }
1756   if (auto folded{OperandsAreConstants(x)}) {
1757     if constexpr (T::category == TypeCategory::Integer) {
1758       auto sum{folded->first.AddSigned(folded->second)};
1759       if (sum.overflow) {
1760         context.messages().Say(
1761             "INTEGER(%d) addition overflowed"_warn_en_US, T::kind);
1762       }
1763       return Expr<T>{Constant<T>{sum.value}};
1764     } else {
1765       auto sum{folded->first.Add(
1766           folded->second, context.targetCharacteristics().roundingMode())};
1767       RealFlagWarnings(context, sum.flags, "addition");
1768       if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
1769         sum.value = sum.value.FlushSubnormalToZero();
1770       }
1771       return Expr<T>{Constant<T>{sum.value}};
1772     }
1773   }
1774   return Expr<T>{std::move(x)};
1775 }
1776 
1777 template <typename T>
FoldOperation(FoldingContext & context,Subtract<T> && x)1778 Expr<T> FoldOperation(FoldingContext &context, Subtract<T> &&x) {
1779   if (auto array{ApplyElementwise(context, x)}) {
1780     return *array;
1781   }
1782   if (auto folded{OperandsAreConstants(x)}) {
1783     if constexpr (T::category == TypeCategory::Integer) {
1784       auto difference{folded->first.SubtractSigned(folded->second)};
1785       if (difference.overflow) {
1786         context.messages().Say(
1787             "INTEGER(%d) subtraction overflowed"_warn_en_US, T::kind);
1788       }
1789       return Expr<T>{Constant<T>{difference.value}};
1790     } else {
1791       auto difference{folded->first.Subtract(
1792           folded->second, context.targetCharacteristics().roundingMode())};
1793       RealFlagWarnings(context, difference.flags, "subtraction");
1794       if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
1795         difference.value = difference.value.FlushSubnormalToZero();
1796       }
1797       return Expr<T>{Constant<T>{difference.value}};
1798     }
1799   }
1800   return Expr<T>{std::move(x)};
1801 }
1802 
1803 template <typename T>
FoldOperation(FoldingContext & context,Multiply<T> && x)1804 Expr<T> FoldOperation(FoldingContext &context, Multiply<T> &&x) {
1805   if (auto array{ApplyElementwise(context, x)}) {
1806     return *array;
1807   }
1808   if (auto folded{OperandsAreConstants(x)}) {
1809     if constexpr (T::category == TypeCategory::Integer) {
1810       auto product{folded->first.MultiplySigned(folded->second)};
1811       if (product.SignedMultiplicationOverflowed()) {
1812         context.messages().Say(
1813             "INTEGER(%d) multiplication overflowed"_warn_en_US, T::kind);
1814       }
1815       return Expr<T>{Constant<T>{product.lower}};
1816     } else {
1817       auto product{folded->first.Multiply(
1818           folded->second, context.targetCharacteristics().roundingMode())};
1819       RealFlagWarnings(context, product.flags, "multiplication");
1820       if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
1821         product.value = product.value.FlushSubnormalToZero();
1822       }
1823       return Expr<T>{Constant<T>{product.value}};
1824     }
1825   } else if constexpr (T::category == TypeCategory::Integer) {
1826     if (auto c{GetScalarConstantValue<T>(x.right())}) {
1827       x.right() = std::move(x.left());
1828       x.left() = Expr<T>{std::move(*c)};
1829     }
1830     if (auto c{GetScalarConstantValue<T>(x.left())}) {
1831       if (c->IsZero()) {
1832         return std::move(x.left());
1833       } else if (c->CompareSigned(Scalar<T>{1}) == Ordering::Equal) {
1834         return std::move(x.right());
1835       } else if (c->CompareSigned(Scalar<T>{-1}) == Ordering::Equal) {
1836         return Expr<T>{Negate<T>{std::move(x.right())}};
1837       }
1838     }
1839   }
1840   return Expr<T>{std::move(x)};
1841 }
1842 
1843 template <typename T>
FoldOperation(FoldingContext & context,Divide<T> && x)1844 Expr<T> FoldOperation(FoldingContext &context, Divide<T> &&x) {
1845   if (auto array{ApplyElementwise(context, x)}) {
1846     return *array;
1847   }
1848   if (auto folded{OperandsAreConstants(x)}) {
1849     if constexpr (T::category == TypeCategory::Integer) {
1850       auto quotAndRem{folded->first.DivideSigned(folded->second)};
1851       if (quotAndRem.divisionByZero) {
1852         context.messages().Say(
1853             "INTEGER(%d) division by zero"_warn_en_US, T::kind);
1854         return Expr<T>{std::move(x)};
1855       }
1856       if (quotAndRem.overflow) {
1857         context.messages().Say(
1858             "INTEGER(%d) division overflowed"_warn_en_US, T::kind);
1859       }
1860       return Expr<T>{Constant<T>{quotAndRem.quotient}};
1861     } else {
1862       auto quotient{folded->first.Divide(
1863           folded->second, context.targetCharacteristics().roundingMode())};
1864       // Don't warn about -1./0., 0./0., or 1./0. from a module file
1865       // they are interpreted as canonical Fortran representations of -Inf,
1866       // NaN, and Inf respectively.
1867       bool isCanonicalNaNOrInf{false};
1868       if constexpr (T::category == TypeCategory::Real) {
1869         if (folded->second.IsZero() && context.inModuleFile()) {
1870           using IntType = typename T::Scalar::Word;
1871           auto intNumerator{folded->first.template ToInteger<IntType>()};
1872           isCanonicalNaNOrInf = intNumerator.flags == RealFlags{} &&
1873               intNumerator.value >= IntType{-1} &&
1874               intNumerator.value <= IntType{1};
1875         }
1876       }
1877       if (!isCanonicalNaNOrInf) {
1878         RealFlagWarnings(context, quotient.flags, "division");
1879       }
1880       if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
1881         quotient.value = quotient.value.FlushSubnormalToZero();
1882       }
1883       return Expr<T>{Constant<T>{quotient.value}};
1884     }
1885   }
1886   return Expr<T>{std::move(x)};
1887 }
1888 
1889 template <typename T>
FoldOperation(FoldingContext & context,Power<T> && x)1890 Expr<T> FoldOperation(FoldingContext &context, Power<T> &&x) {
1891   if (auto array{ApplyElementwise(context, x)}) {
1892     return *array;
1893   }
1894   if (auto folded{OperandsAreConstants(x)}) {
1895     if constexpr (T::category == TypeCategory::Integer) {
1896       auto power{folded->first.Power(folded->second)};
1897       if (power.divisionByZero) {
1898         context.messages().Say(
1899             "INTEGER(%d) zero to negative power"_warn_en_US, T::kind);
1900       } else if (power.overflow) {
1901         context.messages().Say(
1902             "INTEGER(%d) power overflowed"_warn_en_US, T::kind);
1903       } else if (power.zeroToZero) {
1904         context.messages().Say(
1905             "INTEGER(%d) 0**0 is not defined"_warn_en_US, T::kind);
1906       }
1907       return Expr<T>{Constant<T>{power.power}};
1908     } else {
1909       if (auto callable{GetHostRuntimeWrapper<T, T, T>("pow")}) {
1910         return Expr<T>{
1911             Constant<T>{(*callable)(context, folded->first, folded->second)}};
1912       } else {
1913         context.messages().Say(
1914             "Power for %s cannot be folded on host"_warn_en_US,
1915             T{}.AsFortran());
1916       }
1917     }
1918   }
1919   return Expr<T>{std::move(x)};
1920 }
1921 
1922 template <typename T>
FoldOperation(FoldingContext & context,RealToIntPower<T> && x)1923 Expr<T> FoldOperation(FoldingContext &context, RealToIntPower<T> &&x) {
1924   if (auto array{ApplyElementwise(context, x)}) {
1925     return *array;
1926   }
1927   return common::visit(
1928       [&](auto &y) -> Expr<T> {
1929         if (auto folded{OperandsAreConstants(x.left(), y)}) {
1930           auto power{evaluate::IntPower(folded->first, folded->second)};
1931           RealFlagWarnings(context, power.flags, "power with INTEGER exponent");
1932           if (context.targetCharacteristics().areSubnormalsFlushedToZero()) {
1933             power.value = power.value.FlushSubnormalToZero();
1934           }
1935           return Expr<T>{Constant<T>{power.value}};
1936         } else {
1937           return Expr<T>{std::move(x)};
1938         }
1939       },
1940       x.right().u);
1941 }
1942 
1943 template <typename T>
FoldOperation(FoldingContext & context,Extremum<T> && x)1944 Expr<T> FoldOperation(FoldingContext &context, Extremum<T> &&x) {
1945   if (auto array{ApplyElementwise(context, x,
1946           std::function<Expr<T>(Expr<T> &&, Expr<T> &&)>{[=](Expr<T> &&l,
1947                                                              Expr<T> &&r) {
1948             return Expr<T>{Extremum<T>{x.ordering, std::move(l), std::move(r)}};
1949           }})}) {
1950     return *array;
1951   }
1952   if (auto folded{OperandsAreConstants(x)}) {
1953     if constexpr (T::category == TypeCategory::Integer) {
1954       if (folded->first.CompareSigned(folded->second) == x.ordering) {
1955         return Expr<T>{Constant<T>{folded->first}};
1956       }
1957     } else if constexpr (T::category == TypeCategory::Real) {
1958       if (folded->first.IsNotANumber() ||
1959           (folded->first.Compare(folded->second) == Relation::Less) ==
1960               (x.ordering == Ordering::Less)) {
1961         return Expr<T>{Constant<T>{folded->first}};
1962       }
1963     } else {
1964       static_assert(T::category == TypeCategory::Character);
1965       // Result of MIN and MAX on character has the length of
1966       // the longest argument.
1967       auto maxLen{std::max(folded->first.length(), folded->second.length())};
1968       bool isFirst{x.ordering == Compare(folded->first, folded->second)};
1969       auto res{isFirst ? std::move(folded->first) : std::move(folded->second)};
1970       res = res.length() == maxLen
1971           ? std::move(res)
1972           : CharacterUtils<T::kind>::Resize(res, maxLen);
1973       return Expr<T>{Constant<T>{std::move(res)}};
1974     }
1975     return Expr<T>{Constant<T>{folded->second}};
1976   }
1977   return Expr<T>{std::move(x)};
1978 }
1979 
1980 template <int KIND>
ToReal(FoldingContext & context,Expr<SomeType> && expr)1981 Expr<Type<TypeCategory::Real, KIND>> ToReal(
1982     FoldingContext &context, Expr<SomeType> &&expr) {
1983   using Result = Type<TypeCategory::Real, KIND>;
1984   std::optional<Expr<Result>> result;
1985   common::visit(
1986       [&](auto &&x) {
1987         using From = std::decay_t<decltype(x)>;
1988         if constexpr (std::is_same_v<From, BOZLiteralConstant>) {
1989           // Move the bits without any integer->real conversion
1990           From original{x};
1991           result = ConvertToType<Result>(std::move(x));
1992           const auto *constant{UnwrapExpr<Constant<Result>>(*result)};
1993           CHECK(constant);
1994           Scalar<Result> real{constant->GetScalarValue().value()};
1995           From converted{From::ConvertUnsigned(real.RawBits()).value};
1996           if (original != converted) { // C1601
1997             context.messages().Say(
1998                 "Nonzero bits truncated from BOZ literal constant in REAL intrinsic"_warn_en_US);
1999           }
2000         } else if constexpr (IsNumericCategoryExpr<From>()) {
2001           result = Fold(context, ConvertToType<Result>(std::move(x)));
2002         } else {
2003           common::die("ToReal: bad argument expression");
2004         }
2005       },
2006       std::move(expr.u));
2007   return result.value();
2008 }
2009 
2010 // REAL(z) and AIMAG(z)
2011 template <int KIND>
FoldOperation(FoldingContext & context,ComplexComponent<KIND> && x)2012 Expr<Type<TypeCategory::Real, KIND>> FoldOperation(
2013     FoldingContext &context, ComplexComponent<KIND> &&x) {
2014   using Operand = Type<TypeCategory::Complex, KIND>;
2015   using Result = Type<TypeCategory::Real, KIND>;
2016   if (auto array{ApplyElementwise(context, x,
2017           std::function<Expr<Result>(Expr<Operand> &&)>{
2018               [=](Expr<Operand> &&operand) {
2019                 return Expr<Result>{ComplexComponent<KIND>{
2020                     x.isImaginaryPart, std::move(operand)}};
2021               }})}) {
2022     return *array;
2023   }
2024   auto &operand{x.left()};
2025   if (auto value{GetScalarConstantValue<Operand>(operand)}) {
2026     if (x.isImaginaryPart) {
2027       return Expr<Result>{Constant<Result>{value->AIMAG()}};
2028     } else {
2029       return Expr<Result>{Constant<Result>{value->REAL()}};
2030     }
2031   }
2032   return Expr<Result>{std::move(x)};
2033 }
2034 
2035 template <typename T>
Rewrite(FoldingContext & context,Expr<T> && expr)2036 Expr<T> ExpressionBase<T>::Rewrite(FoldingContext &context, Expr<T> &&expr) {
2037   return common::visit(
2038       [&](auto &&x) -> Expr<T> {
2039         if constexpr (IsSpecificIntrinsicType<T>) {
2040           return FoldOperation(context, std::move(x));
2041         } else if constexpr (std::is_same_v<T, SomeDerived>) {
2042           return FoldOperation(context, std::move(x));
2043         } else if constexpr (common::HasMember<decltype(x),
2044                                  TypelessExpression>) {
2045           return std::move(expr);
2046         } else {
2047           return Expr<T>{Fold(context, std::move(x))};
2048         }
2049       },
2050       std::move(expr.u));
2051 }
2052 
2053 FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase, )
2054 } // namespace Fortran::evaluate
2055 #endif // FORTRAN_EVALUATE_FOLD_IMPLEMENTATION_H_
2056