1 //===-- lib/Evaluate/fold-integer.cpp -------------------------------------===//
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 #include "fold-implementation.h"
10 #include "fold-reduction.h"
11 #include "flang/Evaluate/check-expression.h"
12 
13 namespace Fortran::evaluate {
14 
15 // Given a collection of ConstantSubscripts values, package them as a Constant.
16 // Return scalar value if asScalar == true and shape-dim array otherwise.
17 template <typename T>
18 Expr<T> PackageConstantBounds(
19     const ConstantSubscripts &&bounds, bool asScalar = false) {
20   if (asScalar) {
21     return Expr<T>{Constant<T>{bounds.at(0)}};
22   } else {
23     // As rank-dim array
24     const int rank{GetRank(bounds)};
25     std::vector<Scalar<T>> packed(rank);
26     std::transform(bounds.begin(), bounds.end(), packed.begin(),
27         [](ConstantSubscript x) { return Scalar<T>(x); });
28     return Expr<T>{Constant<T>{std::move(packed), ConstantSubscripts{rank}}};
29   }
30 }
31 
32 // Class to retrieve the constant bound of an expression which is an
33 // array that devolves to a type of Constant<T>
34 class GetConstantArrayBoundHelper {
35 public:
36   template <typename T>
37   static Expr<T> GetLbound(
38       const Expr<SomeType> &array, std::optional<int> dim) {
39     return PackageConstantBounds<T>(
40         GetConstantArrayBoundHelper(dim, /*getLbound=*/true).Get(array),
41         dim.has_value());
42   }
43 
44   template <typename T>
45   static Expr<T> GetUbound(
46       const Expr<SomeType> &array, std::optional<int> dim) {
47     return PackageConstantBounds<T>(
48         GetConstantArrayBoundHelper(dim, /*getLbound=*/false).Get(array),
49         dim.has_value());
50   }
51 
52 private:
53   GetConstantArrayBoundHelper(
54       std::optional<ConstantSubscript> dim, bool getLbound)
55       : dim_{dim}, getLbound_{getLbound} {}
56 
57   template <typename T> ConstantSubscripts Get(const T &) {
58     // The method is needed for template expansion, but we should never get
59     // here in practice.
60     CHECK(false);
61     return {0};
62   }
63 
64   template <typename T> ConstantSubscripts Get(const Constant<T> &x) {
65     if (getLbound_) {
66       // Return the lower bound
67       if (dim_) {
68         return {x.lbounds().at(*dim_)};
69       } else {
70         return x.lbounds();
71       }
72     } else {
73       // Return the upper bound
74       if (arrayFromParenthesesExpr) {
75         // Underlying array comes from (x) expression - return shapes
76         if (dim_) {
77           return {x.shape().at(*dim_)};
78         } else {
79           return x.shape();
80         }
81       } else {
82         return x.ComputeUbounds(dim_);
83       }
84     }
85   }
86 
87   template <typename T> ConstantSubscripts Get(const Parentheses<T> &x) {
88     // Cause of temp variable inside parentheses - return [1, ... 1] for lower
89     // bounds and shape for upper bounds
90     if (getLbound_) {
91       return ConstantSubscripts(x.Rank(), ConstantSubscript{1});
92     } else {
93       // Indicate that underlying array comes from parentheses expression.
94       // Continue to unwrap expression until we hit a constant
95       arrayFromParenthesesExpr = true;
96       return Get(x.left());
97     }
98   }
99 
100   template <typename T> ConstantSubscripts Get(const Expr<T> &x) {
101     // recurse through Expr<T>'a until we hit a constant
102     return common::visit([&](const auto &inner) { return Get(inner); },
103         //      [&](const auto &) { return 0; },
104         x.u);
105   }
106 
107   const std::optional<ConstantSubscript> dim_;
108   const bool getLbound_;
109   bool arrayFromParenthesesExpr{false};
110 };
111 
112 template <int KIND>
113 Expr<Type<TypeCategory::Integer, KIND>> LBOUND(FoldingContext &context,
114     FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) {
115   using T = Type<TypeCategory::Integer, KIND>;
116   ActualArguments &args{funcRef.arguments()};
117   if (const auto *array{UnwrapExpr<Expr<SomeType>>(args[0])}) {
118     if (int rank{array->Rank()}; rank > 0) {
119       std::optional<int> dim;
120       if (funcRef.Rank() == 0) {
121         // Optional DIM= argument is present: result is scalar.
122         if (auto dim64{GetInt64Arg(args[1])}) {
123           if (*dim64 < 1 || *dim64 > rank) {
124             context.messages().Say("DIM=%jd dimension is out of range for "
125                                    "rank-%d array"_err_en_US,
126                 *dim64, rank);
127             return MakeInvalidIntrinsic<T>(std::move(funcRef));
128           } else {
129             dim = *dim64 - 1; // 1-based to 0-based
130           }
131         } else {
132           // DIM= is present but not constant
133           return Expr<T>{std::move(funcRef)};
134         }
135       }
136       bool lowerBoundsAreOne{true};
137       if (auto named{ExtractNamedEntity(*array)}) {
138         const Symbol &symbol{named->GetLastSymbol()};
139         if (symbol.Rank() == rank) {
140           lowerBoundsAreOne = false;
141           if (dim) {
142             if (auto lb{GetLBOUND(context, *named, *dim)}) {
143               return Fold(context, ConvertToType<T>(std::move(*lb)));
144             }
145           } else if (auto extents{
146                          AsExtentArrayExpr(GetLBOUNDs(context, *named))}) {
147             return Fold(context,
148                 ConvertToType<T>(Expr<ExtentType>{std::move(*extents)}));
149           }
150         } else {
151           lowerBoundsAreOne = symbol.Rank() == 0; // LBOUND(array%component)
152         }
153       }
154       if (IsActuallyConstant(*array)) {
155         return GetConstantArrayBoundHelper::GetLbound<T>(*array, dim);
156       }
157       if (lowerBoundsAreOne) {
158         ConstantSubscripts ones(rank, ConstantSubscript{1});
159         return PackageConstantBounds<T>(std::move(ones), dim.has_value());
160       }
161     }
162   }
163   return Expr<T>{std::move(funcRef)};
164 }
165 
166 template <int KIND>
167 Expr<Type<TypeCategory::Integer, KIND>> UBOUND(FoldingContext &context,
168     FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) {
169   using T = Type<TypeCategory::Integer, KIND>;
170   ActualArguments &args{funcRef.arguments()};
171   if (auto *array{UnwrapExpr<Expr<SomeType>>(args[0])}) {
172     if (int rank{array->Rank()}; rank > 0) {
173       std::optional<int> dim;
174       if (funcRef.Rank() == 0) {
175         // Optional DIM= argument is present: result is scalar.
176         if (auto dim64{GetInt64Arg(args[1])}) {
177           if (*dim64 < 1 || *dim64 > rank) {
178             context.messages().Say("DIM=%jd dimension is out of range for "
179                                    "rank-%d array"_err_en_US,
180                 *dim64, rank);
181             return MakeInvalidIntrinsic<T>(std::move(funcRef));
182           } else {
183             dim = *dim64 - 1; // 1-based to 0-based
184           }
185         } else {
186           // DIM= is present but not constant
187           return Expr<T>{std::move(funcRef)};
188         }
189       }
190       bool takeBoundsFromShape{true};
191       if (auto named{ExtractNamedEntity(*array)}) {
192         const Symbol &symbol{named->GetLastSymbol()};
193         if (symbol.Rank() == rank) {
194           takeBoundsFromShape = false;
195           if (dim) {
196             if (semantics::IsAssumedSizeArray(symbol) && *dim == rank - 1) {
197               context.messages().Say("DIM=%jd dimension is out of range for "
198                                      "rank-%d assumed-size array"_err_en_US,
199                   rank, rank);
200               return MakeInvalidIntrinsic<T>(std::move(funcRef));
201             } else if (auto ub{GetUBOUND(context, *named, *dim)}) {
202               return Fold(context, ConvertToType<T>(std::move(*ub)));
203             }
204           } else {
205             Shape ubounds{GetUBOUNDs(context, *named)};
206             if (semantics::IsAssumedSizeArray(symbol)) {
207               CHECK(!ubounds.back());
208               ubounds.back() = ExtentExpr{-1};
209             }
210             if (auto extents{AsExtentArrayExpr(ubounds)}) {
211               return Fold(context,
212                   ConvertToType<T>(Expr<ExtentType>{std::move(*extents)}));
213             }
214           }
215         } else {
216           takeBoundsFromShape = symbol.Rank() == 0; // UBOUND(array%component)
217         }
218       }
219       if (IsActuallyConstant(*array)) {
220         return GetConstantArrayBoundHelper::GetUbound<T>(*array, dim);
221       }
222       if (takeBoundsFromShape) {
223         if (auto shape{GetContextFreeShape(context, *array)}) {
224           if (dim) {
225             if (auto &dimSize{shape->at(*dim)}) {
226               return Fold(context,
227                   ConvertToType<T>(Expr<ExtentType>{std::move(*dimSize)}));
228             }
229           } else if (auto shapeExpr{AsExtentArrayExpr(*shape)}) {
230             return Fold(context, ConvertToType<T>(std::move(*shapeExpr)));
231           }
232         }
233       }
234     }
235   }
236   return Expr<T>{std::move(funcRef)};
237 }
238 
239 // COUNT()
240 template <typename T>
241 static Expr<T> FoldCount(FoldingContext &context, FunctionRef<T> &&ref) {
242   static_assert(T::category == TypeCategory::Integer);
243   ActualArguments &arg{ref.arguments()};
244   if (const Constant<LogicalResult> *mask{arg.empty()
245               ? nullptr
246               : Folder<LogicalResult>{context}.Folding(arg[0])}) {
247     std::optional<int> dim;
248     if (CheckReductionDIM(dim, context, arg, 1, mask->Rank())) {
249       auto accumulator{[&](Scalar<T> &element, const ConstantSubscripts &at) {
250         if (mask->At(at).IsTrue()) {
251           element = element.AddSigned(Scalar<T>{1}).value;
252         }
253       }};
254       return Expr<T>{DoReduction<T>(*mask, dim, Scalar<T>{}, accumulator)};
255     }
256   }
257   return Expr<T>{std::move(ref)};
258 }
259 
260 // FINDLOC(), MAXLOC(), & MINLOC()
261 enum class WhichLocation { Findloc, Maxloc, Minloc };
262 template <WhichLocation WHICH> class LocationHelper {
263 public:
264   LocationHelper(
265       DynamicType &&type, ActualArguments &arg, FoldingContext &context)
266       : type_{type}, arg_{arg}, context_{context} {}
267   using Result = std::optional<Constant<SubscriptInteger>>;
268   using Types = std::conditional_t<WHICH == WhichLocation::Findloc,
269       AllIntrinsicTypes, RelationalTypes>;
270 
271   template <typename T> Result Test() const {
272     if (T::category != type_.category() || T::kind != type_.kind()) {
273       return std::nullopt;
274     }
275     CHECK(arg_.size() == (WHICH == WhichLocation::Findloc ? 6 : 5));
276     Folder<T> folder{context_};
277     Constant<T> *array{folder.Folding(arg_[0])};
278     if (!array) {
279       return std::nullopt;
280     }
281     std::optional<Constant<T>> value;
282     if constexpr (WHICH == WhichLocation::Findloc) {
283       if (const Constant<T> *p{folder.Folding(arg_[1])}) {
284         value.emplace(*p);
285       } else {
286         return std::nullopt;
287       }
288     }
289     std::optional<int> dim;
290     Constant<LogicalResult> *mask{
291         GetReductionMASK(arg_[maskArg], array->shape(), context_)};
292     if ((!mask && arg_[maskArg]) ||
293         !CheckReductionDIM(dim, context_, arg_, dimArg, array->Rank())) {
294       return std::nullopt;
295     }
296     bool back{false};
297     if (arg_[backArg]) {
298       const auto *backConst{
299           Folder<LogicalResult>{context_}.Folding(arg_[backArg])};
300       if (backConst) {
301         back = backConst->GetScalarValue().value().IsTrue();
302       } else {
303         return std::nullopt;
304       }
305     }
306     const RelationalOperator relation{WHICH == WhichLocation::Findloc
307             ? RelationalOperator::EQ
308             : WHICH == WhichLocation::Maxloc
309             ? (back ? RelationalOperator::GE : RelationalOperator::GT)
310             : back ? RelationalOperator::LE
311                    : RelationalOperator::LT};
312     // Use lower bounds of 1 exclusively.
313     array->SetLowerBoundsToOne();
314     ConstantSubscripts at{array->lbounds()}, maskAt, resultIndices, resultShape;
315     if (mask) {
316       if (auto scalarMask{mask->GetScalarValue()}) {
317         // Convert into array in case of scalar MASK= (for
318         // MAXLOC/MINLOC/FINDLOC mask should be be conformable)
319         ConstantSubscript n{GetSize(array->shape())};
320         std::vector<Scalar<LogicalResult>> mask_elements(
321             n, Scalar<LogicalResult>{scalarMask.value()});
322         *mask = Constant<LogicalResult>{
323             std::move(mask_elements), ConstantSubscripts{n}};
324       }
325       mask->SetLowerBoundsToOne();
326       maskAt = mask->lbounds();
327     }
328     if (dim) { // DIM=
329       if (*dim < 1 || *dim > array->Rank()) {
330         context_.messages().Say("DIM=%d is out of range"_err_en_US, *dim);
331         return std::nullopt;
332       }
333       int zbDim{*dim - 1};
334       resultShape = array->shape();
335       resultShape.erase(
336           resultShape.begin() + zbDim); // scalar if array is vector
337       ConstantSubscript dimLength{array->shape()[zbDim]};
338       ConstantSubscript n{GetSize(resultShape)};
339       for (ConstantSubscript j{0}; j < n; ++j) {
340         ConstantSubscript hit{0};
341         if constexpr (WHICH == WhichLocation::Maxloc ||
342             WHICH == WhichLocation::Minloc) {
343           value.reset();
344         }
345         for (ConstantSubscript k{0}; k < dimLength;
346              ++k, ++at[zbDim], mask && ++maskAt[zbDim]) {
347           if ((!mask || mask->At(maskAt).IsTrue()) &&
348               IsHit(array->At(at), value, relation)) {
349             hit = at[zbDim];
350             if constexpr (WHICH == WhichLocation::Findloc) {
351               if (!back) {
352                 break;
353               }
354             }
355           }
356         }
357         resultIndices.emplace_back(hit);
358         at[zbDim] = std::max<ConstantSubscript>(dimLength, 1);
359         array->IncrementSubscripts(at);
360         at[zbDim] = 1;
361         if (mask) {
362           maskAt[zbDim] = mask->lbounds()[zbDim] +
363               std::max<ConstantSubscript>(dimLength, 1) - 1;
364           mask->IncrementSubscripts(maskAt);
365           maskAt[zbDim] = mask->lbounds()[zbDim];
366         }
367       }
368     } else { // no DIM=
369       resultShape = ConstantSubscripts{array->Rank()}; // always a vector
370       ConstantSubscript n{GetSize(array->shape())};
371       resultIndices = ConstantSubscripts(array->Rank(), 0);
372       for (ConstantSubscript j{0}; j < n; ++j, array->IncrementSubscripts(at),
373            mask && mask->IncrementSubscripts(maskAt)) {
374         if ((!mask || mask->At(maskAt).IsTrue()) &&
375             IsHit(array->At(at), value, relation)) {
376           resultIndices = at;
377           if constexpr (WHICH == WhichLocation::Findloc) {
378             if (!back) {
379               break;
380             }
381           }
382         }
383       }
384     }
385     std::vector<Scalar<SubscriptInteger>> resultElements;
386     for (ConstantSubscript j : resultIndices) {
387       resultElements.emplace_back(j);
388     }
389     return Constant<SubscriptInteger>{
390         std::move(resultElements), std::move(resultShape)};
391   }
392 
393 private:
394   template <typename T>
395   bool IsHit(typename Constant<T>::Element element,
396       std::optional<Constant<T>> &value,
397       [[maybe_unused]] RelationalOperator relation) const {
398     std::optional<Expr<LogicalResult>> cmp;
399     bool result{true};
400     if (value) {
401       if constexpr (T::category == TypeCategory::Logical) {
402         // array(at) .EQV. value?
403         static_assert(WHICH == WhichLocation::Findloc);
404         cmp.emplace(ConvertToType<LogicalResult>(
405             Expr<T>{LogicalOperation<T::kind>{LogicalOperator::Eqv,
406                 Expr<T>{Constant<T>{element}}, Expr<T>{Constant<T>{*value}}}}));
407       } else { // compare array(at) to value
408         cmp.emplace(PackageRelation(relation, Expr<T>{Constant<T>{element}},
409             Expr<T>{Constant<T>{*value}}));
410       }
411       Expr<LogicalResult> folded{Fold(context_, std::move(*cmp))};
412       result = GetScalarConstantValue<LogicalResult>(folded).value().IsTrue();
413     } else {
414       // first unmasked element for MAXLOC/MINLOC - always take it
415     }
416     if constexpr (WHICH == WhichLocation::Maxloc ||
417         WHICH == WhichLocation::Minloc) {
418       if (result) {
419         value.emplace(std::move(element));
420       }
421     }
422     return result;
423   }
424 
425   static constexpr int dimArg{WHICH == WhichLocation::Findloc ? 2 : 1};
426   static constexpr int maskArg{dimArg + 1};
427   static constexpr int backArg{maskArg + 2};
428 
429   DynamicType type_;
430   ActualArguments &arg_;
431   FoldingContext &context_;
432 };
433 
434 template <WhichLocation which>
435 static std::optional<Constant<SubscriptInteger>> FoldLocationCall(
436     ActualArguments &arg, FoldingContext &context) {
437   if (arg[0]) {
438     if (auto type{arg[0]->GetType()}) {
439       return common::SearchTypes(
440           LocationHelper<which>{std::move(*type), arg, context});
441     }
442   }
443   return std::nullopt;
444 }
445 
446 template <WhichLocation which, typename T>
447 static Expr<T> FoldLocation(FoldingContext &context, FunctionRef<T> &&ref) {
448   static_assert(T::category == TypeCategory::Integer);
449   if (std::optional<Constant<SubscriptInteger>> found{
450           FoldLocationCall<which>(ref.arguments(), context)}) {
451     return Expr<T>{Fold(
452         context, ConvertToType<T>(Expr<SubscriptInteger>{std::move(*found)}))};
453   } else {
454     return Expr<T>{std::move(ref)};
455   }
456 }
457 
458 // for IALL, IANY, & IPARITY
459 template <typename T>
460 static Expr<T> FoldBitReduction(FoldingContext &context, FunctionRef<T> &&ref,
461     Scalar<T> (Scalar<T>::*operation)(const Scalar<T> &) const,
462     Scalar<T> identity) {
463   static_assert(T::category == TypeCategory::Integer);
464   std::optional<int> dim;
465   if (std::optional<Constant<T>> array{
466           ProcessReductionArgs<T>(context, ref.arguments(), dim, identity,
467               /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) {
468     auto accumulator{[&](Scalar<T> &element, const ConstantSubscripts &at) {
469       element = (element.*operation)(array->At(at));
470     }};
471     return Expr<T>{DoReduction<T>(*array, dim, identity, accumulator)};
472   }
473   return Expr<T>{std::move(ref)};
474 }
475 
476 template <int KIND>
477 Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
478     FoldingContext &context,
479     FunctionRef<Type<TypeCategory::Integer, KIND>> &&funcRef) {
480   using T = Type<TypeCategory::Integer, KIND>;
481   using Int4 = Type<TypeCategory::Integer, 4>;
482   ActualArguments &args{funcRef.arguments()};
483   auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)};
484   CHECK(intrinsic);
485   std::string name{intrinsic->name};
486   if (name == "abs") { // incl. babs, iiabs, jiaabs, & kiabs
487     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
488         ScalarFunc<T, T>([&context](const Scalar<T> &i) -> Scalar<T> {
489           typename Scalar<T>::ValueWithOverflow j{i.ABS()};
490           if (j.overflow) {
491             context.messages().Say(
492                 "abs(integer(kind=%d)) folding overflowed"_warn_en_US, KIND);
493           }
494           return j.value;
495         }));
496   } else if (name == "bit_size") {
497     return Expr<T>{Scalar<T>::bits};
498   } else if (name == "ceiling" || name == "floor" || name == "nint") {
499     if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
500       // NINT rounds ties away from zero, not to even
501       common::RoundingMode mode{name == "ceiling" ? common::RoundingMode::Up
502               : name == "floor"                   ? common::RoundingMode::Down
503                                 : common::RoundingMode::TiesAwayFromZero};
504       return common::visit(
505           [&](const auto &kx) {
506             using TR = ResultType<decltype(kx)>;
507             return FoldElementalIntrinsic<T, TR>(context, std::move(funcRef),
508                 ScalarFunc<T, TR>([&](const Scalar<TR> &x) {
509                   auto y{x.template ToInteger<Scalar<T>>(mode)};
510                   if (y.flags.test(RealFlag::Overflow)) {
511                     context.messages().Say(
512                         "%s intrinsic folding overflow"_warn_en_US, name);
513                   }
514                   return y.value;
515                 }));
516           },
517           cx->u);
518     }
519   } else if (name == "count") {
520     return FoldCount<T>(context, std::move(funcRef));
521   } else if (name == "digits") {
522     if (const auto *cx{UnwrapExpr<Expr<SomeInteger>>(args[0])}) {
523       return Expr<T>{common::visit(
524           [](const auto &kx) {
525             return Scalar<ResultType<decltype(kx)>>::DIGITS;
526           },
527           cx->u)};
528     } else if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
529       return Expr<T>{common::visit(
530           [](const auto &kx) {
531             return Scalar<ResultType<decltype(kx)>>::DIGITS;
532           },
533           cx->u)};
534     } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) {
535       return Expr<T>{common::visit(
536           [](const auto &kx) {
537             return Scalar<typename ResultType<decltype(kx)>::Part>::DIGITS;
538           },
539           cx->u)};
540     }
541   } else if (name == "dim") {
542     return FoldElementalIntrinsic<T, T, T>(
543         context, std::move(funcRef), &Scalar<T>::DIM);
544   } else if (name == "dshiftl" || name == "dshiftr") {
545     const auto fptr{
546         name == "dshiftl" ? &Scalar<T>::DSHIFTL : &Scalar<T>::DSHIFTR};
547     // Third argument can be of any kind. However, it must be smaller or equal
548     // than BIT_SIZE. It can be converted to Int4 to simplify.
549     return FoldElementalIntrinsic<T, T, T, Int4>(context, std::move(funcRef),
550         ScalarFunc<T, T, T, Int4>(
551             [&fptr](const Scalar<T> &i, const Scalar<T> &j,
552                 const Scalar<Int4> &shift) -> Scalar<T> {
553               return std::invoke(fptr, i, j, static_cast<int>(shift.ToInt64()));
554             }));
555   } else if (name == "exponent") {
556     if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
557       return common::visit(
558           [&funcRef, &context](const auto &x) -> Expr<T> {
559             using TR = typename std::decay_t<decltype(x)>::Result;
560             return FoldElementalIntrinsic<T, TR>(context, std::move(funcRef),
561                 &Scalar<TR>::template EXPONENT<Scalar<T>>);
562           },
563           sx->u);
564     } else {
565       DIE("exponent argument must be real");
566     }
567   } else if (name == "findloc") {
568     return FoldLocation<WhichLocation::Findloc, T>(context, std::move(funcRef));
569   } else if (name == "huge") {
570     return Expr<T>{Scalar<T>::HUGE()};
571   } else if (name == "iachar" || name == "ichar") {
572     auto *someChar{UnwrapExpr<Expr<SomeCharacter>>(args[0])};
573     CHECK(someChar);
574     if (auto len{ToInt64(someChar->LEN())}) {
575       if (len.value() != 1) {
576         // Do not die, this was not checked before
577         context.messages().Say(
578             "Character in intrinsic function %s must have length one"_warn_en_US,
579             name);
580       } else {
581         return common::visit(
582             [&funcRef, &context](const auto &str) -> Expr<T> {
583               using Char = typename std::decay_t<decltype(str)>::Result;
584               return FoldElementalIntrinsic<T, Char>(context,
585                   std::move(funcRef),
586                   ScalarFunc<T, Char>([](const Scalar<Char> &c) {
587                     return Scalar<T>{CharacterUtils<Char::kind>::ICHAR(c)};
588                   }));
589             },
590             someChar->u);
591       }
592     }
593   } else if (name == "iand" || name == "ior" || name == "ieor") {
594     auto fptr{&Scalar<T>::IAND};
595     if (name == "iand") { // done in fptr declaration
596     } else if (name == "ior") {
597       fptr = &Scalar<T>::IOR;
598     } else if (name == "ieor") {
599       fptr = &Scalar<T>::IEOR;
600     } else {
601       common::die("missing case to fold intrinsic function %s", name.c_str());
602     }
603     return FoldElementalIntrinsic<T, T, T>(
604         context, std::move(funcRef), ScalarFunc<T, T, T>(fptr));
605   } else if (name == "iall") {
606     return FoldBitReduction(
607         context, std::move(funcRef), &Scalar<T>::IAND, Scalar<T>{}.NOT());
608   } else if (name == "iany") {
609     return FoldBitReduction(
610         context, std::move(funcRef), &Scalar<T>::IOR, Scalar<T>{});
611   } else if (name == "ibclr" || name == "ibset") {
612     // Second argument can be of any kind. However, it must be smaller
613     // than BIT_SIZE. It can be converted to Int4 to simplify.
614     auto fptr{&Scalar<T>::IBCLR};
615     if (name == "ibclr") { // done in fptr definition
616     } else if (name == "ibset") {
617       fptr = &Scalar<T>::IBSET;
618     } else {
619       common::die("missing case to fold intrinsic function %s", name.c_str());
620     }
621     return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef),
622         ScalarFunc<T, T, Int4>([&](const Scalar<T> &i,
623                                    const Scalar<Int4> &pos) -> Scalar<T> {
624           auto posVal{static_cast<int>(pos.ToInt64())};
625           if (posVal < 0) {
626             context.messages().Say(
627                 "bit position for %s (%d) is negative"_err_en_US, name, posVal);
628           } else if (posVal >= i.bits) {
629             context.messages().Say(
630                 "bit position for %s (%d) is not less than %d"_err_en_US, name,
631                 posVal, i.bits);
632           }
633           return std::invoke(fptr, i, posVal);
634         }));
635   } else if (name == "ibits") {
636     return FoldElementalIntrinsic<T, T, Int4, Int4>(context, std::move(funcRef),
637         ScalarFunc<T, T, Int4, Int4>([&](const Scalar<T> &i,
638                                          const Scalar<Int4> &pos,
639                                          const Scalar<Int4> &len) -> Scalar<T> {
640           auto posVal{static_cast<int>(pos.ToInt64())};
641           auto lenVal{static_cast<int>(len.ToInt64())};
642           if (posVal < 0) {
643             context.messages().Say(
644                 "bit position for IBITS(POS=%d,LEN=%d) is negative"_err_en_US,
645                 posVal, lenVal);
646           } else if (lenVal < 0) {
647             context.messages().Say(
648                 "bit length for IBITS(POS=%d,LEN=%d) is negative"_err_en_US,
649                 posVal, lenVal);
650           } else if (posVal + lenVal > i.bits) {
651             context.messages().Say(
652                 "IBITS(POS=%d,LEN=%d) must have POS+LEN no greater than %d"_err_en_US,
653                 posVal + lenVal, i.bits);
654           }
655           return i.IBITS(posVal, lenVal);
656         }));
657   } else if (name == "index" || name == "scan" || name == "verify") {
658     if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) {
659       return common::visit(
660           [&](const auto &kch) -> Expr<T> {
661             using TC = typename std::decay_t<decltype(kch)>::Result;
662             if (UnwrapExpr<Expr<SomeLogical>>(args[2])) { // BACK=
663               return FoldElementalIntrinsic<T, TC, TC, LogicalResult>(context,
664                   std::move(funcRef),
665                   ScalarFunc<T, TC, TC, LogicalResult>{
666                       [&name](const Scalar<TC> &str, const Scalar<TC> &other,
667                           const Scalar<LogicalResult> &back) -> Scalar<T> {
668                         return name == "index"
669                             ? CharacterUtils<TC::kind>::INDEX(
670                                   str, other, back.IsTrue())
671                             : name == "scan" ? CharacterUtils<TC::kind>::SCAN(
672                                                    str, other, back.IsTrue())
673                                              : CharacterUtils<TC::kind>::VERIFY(
674                                                    str, other, back.IsTrue());
675                       }});
676             } else {
677               return FoldElementalIntrinsic<T, TC, TC>(context,
678                   std::move(funcRef),
679                   ScalarFunc<T, TC, TC>{
680                       [&name](const Scalar<TC> &str,
681                           const Scalar<TC> &other) -> Scalar<T> {
682                         return name == "index"
683                             ? CharacterUtils<TC::kind>::INDEX(str, other)
684                             : name == "scan"
685                             ? CharacterUtils<TC::kind>::SCAN(str, other)
686                             : CharacterUtils<TC::kind>::VERIFY(str, other);
687                       }});
688             }
689           },
690           charExpr->u);
691     } else {
692       DIE("first argument must be CHARACTER");
693     }
694   } else if (name == "int") {
695     if (auto *expr{UnwrapExpr<Expr<SomeType>>(args[0])}) {
696       return common::visit(
697           [&](auto &&x) -> Expr<T> {
698             using From = std::decay_t<decltype(x)>;
699             if constexpr (std::is_same_v<From, BOZLiteralConstant> ||
700                 IsNumericCategoryExpr<From>()) {
701               return Fold(context, ConvertToType<T>(std::move(x)));
702             }
703             DIE("int() argument type not valid");
704           },
705           std::move(expr->u));
706     }
707   } else if (name == "int_ptr_kind") {
708     return Expr<T>{8};
709   } else if (name == "kind") {
710     if constexpr (common::HasMember<T, IntegerTypes>) {
711       return Expr<T>{args[0].value().GetType()->kind()};
712     } else {
713       DIE("kind() result not integral");
714     }
715   } else if (name == "iparity") {
716     return FoldBitReduction(
717         context, std::move(funcRef), &Scalar<T>::IEOR, Scalar<T>{});
718   } else if (name == "ishft") {
719     return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef),
720         ScalarFunc<T, T, Int4>([&](const Scalar<T> &i,
721                                    const Scalar<Int4> &pos) -> Scalar<T> {
722           auto posVal{static_cast<int>(pos.ToInt64())};
723           if (posVal < -i.bits) {
724             context.messages().Say(
725                 "SHIFT=%d count for ishft is less than %d"_err_en_US, posVal,
726                 -i.bits);
727           } else if (posVal > i.bits) {
728             context.messages().Say(
729                 "SHIFT=%d count for ishft is greater than %d"_err_en_US, posVal,
730                 i.bits);
731           }
732           return i.ISHFT(posVal);
733         }));
734   } else if (name == "lbound") {
735     return LBOUND(context, std::move(funcRef));
736   } else if (name == "leadz" || name == "trailz" || name == "poppar" ||
737       name == "popcnt") {
738     if (auto *sn{UnwrapExpr<Expr<SomeInteger>>(args[0])}) {
739       return common::visit(
740           [&funcRef, &context, &name](const auto &n) -> Expr<T> {
741             using TI = typename std::decay_t<decltype(n)>::Result;
742             if (name == "poppar") {
743               return FoldElementalIntrinsic<T, TI>(context, std::move(funcRef),
744                   ScalarFunc<T, TI>([](const Scalar<TI> &i) -> Scalar<T> {
745                     return Scalar<T>{i.POPPAR() ? 1 : 0};
746                   }));
747             }
748             auto fptr{&Scalar<TI>::LEADZ};
749             if (name == "leadz") { // done in fptr definition
750             } else if (name == "trailz") {
751               fptr = &Scalar<TI>::TRAILZ;
752             } else if (name == "popcnt") {
753               fptr = &Scalar<TI>::POPCNT;
754             } else {
755               common::die(
756                   "missing case to fold intrinsic function %s", name.c_str());
757             }
758             return FoldElementalIntrinsic<T, TI>(context, std::move(funcRef),
759                 ScalarFunc<T, TI>([&fptr](const Scalar<TI> &i) -> Scalar<T> {
760                   return Scalar<T>{std::invoke(fptr, i)};
761                 }));
762           },
763           sn->u);
764     } else {
765       DIE("leadz argument must be integer");
766     }
767   } else if (name == "len") {
768     if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) {
769       return common::visit(
770           [&](auto &kx) {
771             if (auto len{kx.LEN()}) {
772               if (IsScopeInvariantExpr(*len)) {
773                 return Fold(context, ConvertToType<T>(*std::move(len)));
774               } else {
775                 return Expr<T>{std::move(funcRef)};
776               }
777             } else {
778               return Expr<T>{std::move(funcRef)};
779             }
780           },
781           charExpr->u);
782     } else {
783       DIE("len() argument must be of character type");
784     }
785   } else if (name == "len_trim") {
786     if (auto *charExpr{UnwrapExpr<Expr<SomeCharacter>>(args[0])}) {
787       return common::visit(
788           [&](const auto &kch) -> Expr<T> {
789             using TC = typename std::decay_t<decltype(kch)>::Result;
790             return FoldElementalIntrinsic<T, TC>(context, std::move(funcRef),
791                 ScalarFunc<T, TC>{[](const Scalar<TC> &str) -> Scalar<T> {
792                   return CharacterUtils<TC::kind>::LEN_TRIM(str);
793                 }});
794           },
795           charExpr->u);
796     } else {
797       DIE("len_trim() argument must be of character type");
798     }
799   } else if (name == "maskl" || name == "maskr") {
800     // Argument can be of any kind but value has to be smaller than BIT_SIZE.
801     // It can be safely converted to Int4 to simplify.
802     const auto fptr{name == "maskl" ? &Scalar<T>::MASKL : &Scalar<T>::MASKR};
803     return FoldElementalIntrinsic<T, Int4>(context, std::move(funcRef),
804         ScalarFunc<T, Int4>([&fptr](const Scalar<Int4> &places) -> Scalar<T> {
805           return fptr(static_cast<int>(places.ToInt64()));
806         }));
807   } else if (name == "max") {
808     return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater);
809   } else if (name == "max0" || name == "max1") {
810     return RewriteSpecificMINorMAX(context, std::move(funcRef));
811   } else if (name == "maxexponent") {
812     if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
813       return common::visit(
814           [](const auto &x) {
815             using TR = typename std::decay_t<decltype(x)>::Result;
816             return Expr<T>{Scalar<TR>::MAXEXPONENT};
817           },
818           sx->u);
819     }
820   } else if (name == "maxloc") {
821     return FoldLocation<WhichLocation::Maxloc, T>(context, std::move(funcRef));
822   } else if (name == "maxval") {
823     return FoldMaxvalMinval<T>(context, std::move(funcRef),
824         RelationalOperator::GT, T::Scalar::Least());
825   } else if (name == "merge") {
826     return FoldMerge<T>(context, std::move(funcRef));
827   } else if (name == "merge_bits") {
828     return FoldElementalIntrinsic<T, T, T, T>(
829         context, std::move(funcRef), &Scalar<T>::MERGE_BITS);
830   } else if (name == "min") {
831     return FoldMINorMAX(context, std::move(funcRef), Ordering::Less);
832   } else if (name == "min0" || name == "min1") {
833     return RewriteSpecificMINorMAX(context, std::move(funcRef));
834   } else if (name == "minexponent") {
835     if (auto *sx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
836       return common::visit(
837           [](const auto &x) {
838             using TR = typename std::decay_t<decltype(x)>::Result;
839             return Expr<T>{Scalar<TR>::MINEXPONENT};
840           },
841           sx->u);
842     }
843   } else if (name == "minloc") {
844     return FoldLocation<WhichLocation::Minloc, T>(context, std::move(funcRef));
845   } else if (name == "minval") {
846     return FoldMaxvalMinval<T>(
847         context, std::move(funcRef), RelationalOperator::LT, T::Scalar::HUGE());
848   } else if (name == "mod") {
849     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
850         ScalarFuncWithContext<T, T, T>(
851             [](FoldingContext &context, const Scalar<T> &x,
852                 const Scalar<T> &y) -> Scalar<T> {
853               auto quotRem{x.DivideSigned(y)};
854               if (quotRem.divisionByZero) {
855                 context.messages().Say("mod() by zero"_warn_en_US);
856               } else if (quotRem.overflow) {
857                 context.messages().Say("mod() folding overflowed"_warn_en_US);
858               }
859               return quotRem.remainder;
860             }));
861   } else if (name == "modulo") {
862     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
863         ScalarFuncWithContext<T, T, T>(
864             [](FoldingContext &context, const Scalar<T> &x,
865                 const Scalar<T> &y) -> Scalar<T> {
866               auto result{x.MODULO(y)};
867               if (result.overflow) {
868                 context.messages().Say(
869                     "modulo() folding overflowed"_warn_en_US);
870               }
871               return result.value;
872             }));
873   } else if (name == "not") {
874     return FoldElementalIntrinsic<T, T>(
875         context, std::move(funcRef), &Scalar<T>::NOT);
876   } else if (name == "precision") {
877     if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
878       return Expr<T>{common::visit(
879           [](const auto &kx) {
880             return Scalar<ResultType<decltype(kx)>>::PRECISION;
881           },
882           cx->u)};
883     } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) {
884       return Expr<T>{common::visit(
885           [](const auto &kx) {
886             return Scalar<typename ResultType<decltype(kx)>::Part>::PRECISION;
887           },
888           cx->u)};
889     }
890   } else if (name == "product") {
891     return FoldProduct<T>(context, std::move(funcRef), Scalar<T>{1});
892   } else if (name == "radix") {
893     return Expr<T>{2};
894   } else if (name == "range") {
895     if (const auto *cx{UnwrapExpr<Expr<SomeInteger>>(args[0])}) {
896       return Expr<T>{common::visit(
897           [](const auto &kx) {
898             return Scalar<ResultType<decltype(kx)>>::RANGE;
899           },
900           cx->u)};
901     } else if (const auto *cx{UnwrapExpr<Expr<SomeReal>>(args[0])}) {
902       return Expr<T>{common::visit(
903           [](const auto &kx) {
904             return Scalar<ResultType<decltype(kx)>>::RANGE;
905           },
906           cx->u)};
907     } else if (const auto *cx{UnwrapExpr<Expr<SomeComplex>>(args[0])}) {
908       return Expr<T>{common::visit(
909           [](const auto &kx) {
910             return Scalar<typename ResultType<decltype(kx)>::Part>::RANGE;
911           },
912           cx->u)};
913     }
914   } else if (name == "rank") {
915     if (const auto *array{UnwrapExpr<Expr<SomeType>>(args[0])}) {
916       if (auto named{ExtractNamedEntity(*array)}) {
917         const Symbol &symbol{named->GetLastSymbol()};
918         if (IsAssumedRank(symbol)) {
919           // DescriptorInquiry can only be placed in expression of kind
920           // DescriptorInquiry::Result::kind.
921           return ConvertToType<T>(Expr<
922               Type<TypeCategory::Integer, DescriptorInquiry::Result::kind>>{
923               DescriptorInquiry{*named, DescriptorInquiry::Field::Rank}});
924         }
925       }
926       return Expr<T>{args[0].value().Rank()};
927     }
928     return Expr<T>{args[0].value().Rank()};
929   } else if (name == "selected_char_kind") {
930     if (const auto *chCon{UnwrapExpr<Constant<TypeOf<std::string>>>(args[0])}) {
931       if (std::optional<std::string> value{chCon->GetScalarValue()}) {
932         int defaultKind{
933             context.defaults().GetDefaultKind(TypeCategory::Character)};
934         return Expr<T>{SelectedCharKind(*value, defaultKind)};
935       }
936     }
937   } else if (name == "selected_int_kind") {
938     if (auto p{GetInt64Arg(args[0])}) {
939       return Expr<T>{SelectedIntKind(*p)};
940     }
941   } else if (name == "selected_real_kind" ||
942       name == "__builtin_ieee_selected_real_kind") {
943     if (auto p{GetInt64ArgOr(args[0], 0)}) {
944       if (auto r{GetInt64ArgOr(args[1], 0)}) {
945         if (auto radix{GetInt64ArgOr(args[2], 2)}) {
946           return Expr<T>{SelectedRealKind(*p, *r, *radix)};
947         }
948       }
949     }
950   } else if (name == "shape") {
951     if (auto shape{GetContextFreeShape(context, args[0])}) {
952       if (auto shapeExpr{AsExtentArrayExpr(*shape)}) {
953         return Fold(context, ConvertToType<T>(std::move(*shapeExpr)));
954       }
955     }
956   } else if (name == "shifta" || name == "shiftr" || name == "shiftl") {
957     // Second argument can be of any kind. However, it must be smaller or
958     // equal than BIT_SIZE. It can be converted to Int4 to simplify.
959     auto fptr{&Scalar<T>::SHIFTA};
960     if (name == "shifta") { // done in fptr definition
961     } else if (name == "shiftr") {
962       fptr = &Scalar<T>::SHIFTR;
963     } else if (name == "shiftl") {
964       fptr = &Scalar<T>::SHIFTL;
965     } else {
966       common::die("missing case to fold intrinsic function %s", name.c_str());
967     }
968     return FoldElementalIntrinsic<T, T, Int4>(context, std::move(funcRef),
969         ScalarFunc<T, T, Int4>([&](const Scalar<T> &i,
970                                    const Scalar<Int4> &pos) -> Scalar<T> {
971           auto posVal{static_cast<int>(pos.ToInt64())};
972           if (posVal < 0) {
973             context.messages().Say(
974                 "SHIFT=%d count for %s is negative"_err_en_US, posVal, name);
975           } else if (posVal > i.bits) {
976             context.messages().Say(
977                 "SHIFT=%d count for %s is greater than %d"_err_en_US, posVal,
978                 name, i.bits);
979           }
980           return std::invoke(fptr, i, posVal);
981         }));
982   } else if (name == "sign") {
983     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
984         ScalarFunc<T, T, T>(
985             [&context](const Scalar<T> &j, const Scalar<T> &k) -> Scalar<T> {
986               typename Scalar<T>::ValueWithOverflow result{j.SIGN(k)};
987               if (result.overflow) {
988                 context.messages().Say(
989                     "sign(integer(kind=%d)) folding overflowed"_warn_en_US,
990                     KIND);
991               }
992               return result.value;
993             }));
994   } else if (name == "size") {
995     if (auto shape{GetContextFreeShape(context, args[0])}) {
996       if (auto &dimArg{args[1]}) { // DIM= is present, get one extent
997         if (auto dim{GetInt64Arg(args[1])}) {
998           int rank{GetRank(*shape)};
999           if (*dim >= 1 && *dim <= rank) {
1000             const Symbol *symbol{UnwrapWholeSymbolDataRef(args[0])};
1001             if (symbol && IsAssumedSizeArray(*symbol) && *dim == rank) {
1002               context.messages().Say(
1003                   "size(array,dim=%jd) of last dimension is not available for rank-%d assumed-size array dummy argument"_err_en_US,
1004                   *dim, rank);
1005               return MakeInvalidIntrinsic<T>(std::move(funcRef));
1006             } else if (auto &extent{shape->at(*dim - 1)}) {
1007               return Fold(context, ConvertToType<T>(std::move(*extent)));
1008             }
1009           } else {
1010             context.messages().Say(
1011                 "size(array,dim=%jd) dimension is out of range for rank-%d array"_warn_en_US,
1012                 *dim, rank);
1013           }
1014         }
1015       } else if (auto extents{common::AllElementsPresent(std::move(*shape))}) {
1016         // DIM= is absent; compute PRODUCT(SHAPE())
1017         ExtentExpr product{1};
1018         for (auto &&extent : std::move(*extents)) {
1019           product = std::move(product) * std::move(extent);
1020         }
1021         return Expr<T>{ConvertToType<T>(Fold(context, std::move(product)))};
1022       }
1023     }
1024   } else if (name == "sizeof") { // in bytes; extension
1025     if (auto info{
1026             characteristics::TypeAndShape::Characterize(args[0], context)}) {
1027       if (auto bytes{info->MeasureSizeInBytes(context)}) {
1028         return Expr<T>{Fold(context, ConvertToType<T>(std::move(*bytes)))};
1029       }
1030     }
1031   } else if (name == "storage_size") { // in bits
1032     if (auto info{
1033             characteristics::TypeAndShape::Characterize(args[0], context)}) {
1034       if (auto bytes{info->MeasureElementSizeInBytes(context, true)}) {
1035         return Expr<T>{
1036             Fold(context, Expr<T>{8} * ConvertToType<T>(std::move(*bytes)))};
1037       }
1038     }
1039   } else if (name == "sum") {
1040     return FoldSum<T>(context, std::move(funcRef));
1041   } else if (name == "ubound") {
1042     return UBOUND(context, std::move(funcRef));
1043   }
1044   // TODO: dot_product, ishftc, matmul, sign, transfer
1045   return Expr<T>{std::move(funcRef)};
1046 }
1047 
1048 // Substitutes a bare type parameter reference with its value if it has one now
1049 // in an instantiation.  Bare LEN type parameters are substituted only when
1050 // the known value is constant.
1051 Expr<TypeParamInquiry::Result> FoldOperation(
1052     FoldingContext &context, TypeParamInquiry &&inquiry) {
1053   std::optional<NamedEntity> base{inquiry.base()};
1054   parser::CharBlock parameterName{inquiry.parameter().name()};
1055   if (base) {
1056     // Handling "designator%typeParam".  Get the value of the type parameter
1057     // from the instantiation of the base
1058     if (const semantics::DeclTypeSpec *
1059         declType{base->GetLastSymbol().GetType()}) {
1060       if (const semantics::ParamValue *
1061           paramValue{
1062               declType->derivedTypeSpec().FindParameter(parameterName)}) {
1063         const semantics::MaybeIntExpr &paramExpr{paramValue->GetExplicit()};
1064         if (paramExpr && IsConstantExpr(*paramExpr)) {
1065           Expr<SomeInteger> intExpr{*paramExpr};
1066           return Fold(context,
1067               ConvertToType<TypeParamInquiry::Result>(std::move(intExpr)));
1068         }
1069       }
1070     }
1071   } else {
1072     // A "bare" type parameter: replace with its value, if that's now known
1073     // in a current derived type instantiation, for KIND type parameters.
1074     if (const auto *pdt{context.pdtInstance()}) {
1075       bool isLen{false};
1076       if (const semantics::Scope * scope{context.pdtInstance()->scope()}) {
1077         auto iter{scope->find(parameterName)};
1078         if (iter != scope->end()) {
1079           const Symbol &symbol{*iter->second};
1080           const auto *details{symbol.detailsIf<semantics::TypeParamDetails>()};
1081           if (details) {
1082             isLen = details->attr() == common::TypeParamAttr::Len;
1083             const semantics::MaybeIntExpr &initExpr{details->init()};
1084             if (initExpr && IsConstantExpr(*initExpr) &&
1085                 (!isLen || ToInt64(*initExpr))) {
1086               Expr<SomeInteger> expr{*initExpr};
1087               return Fold(context,
1088                   ConvertToType<TypeParamInquiry::Result>(std::move(expr)));
1089             }
1090           }
1091         }
1092       }
1093       if (const auto *value{pdt->FindParameter(parameterName)}) {
1094         if (value->isExplicit()) {
1095           auto folded{Fold(context,
1096               AsExpr(ConvertToType<TypeParamInquiry::Result>(
1097                   Expr<SomeInteger>{value->GetExplicit().value()})))};
1098           if (!isLen || ToInt64(folded)) {
1099             return folded;
1100           }
1101         }
1102       }
1103     }
1104   }
1105   return AsExpr(std::move(inquiry));
1106 }
1107 
1108 std::optional<std::int64_t> ToInt64(const Expr<SomeInteger> &expr) {
1109   return common::visit(
1110       [](const auto &kindExpr) { return ToInt64(kindExpr); }, expr.u);
1111 }
1112 
1113 std::optional<std::int64_t> ToInt64(const Expr<SomeType> &expr) {
1114   if (const auto *intExpr{UnwrapExpr<Expr<SomeInteger>>(expr)}) {
1115     return ToInt64(*intExpr);
1116   } else {
1117     return std::nullopt;
1118   }
1119 }
1120 
1121 #ifdef _MSC_VER // disable bogus warning about missing definitions
1122 #pragma warning(disable : 4661)
1123 #endif
1124 FOR_EACH_INTEGER_KIND(template class ExpressionBase, )
1125 template class ExpressionBase<SomeInteger>;
1126 } // namespace Fortran::evaluate
1127