xref: /llvm-project/flang/lib/Evaluate/check-expression.cpp (revision 1f8790050b0e99e7b46cc69518aa84f46f50738e)
164ab3302SCarolineConcatto //===-- lib/Evaluate/check-expression.cpp ---------------------------------===//
264ab3302SCarolineConcatto //
364ab3302SCarolineConcatto // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
464ab3302SCarolineConcatto // See https://llvm.org/LICENSE.txt for license information.
564ab3302SCarolineConcatto // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
664ab3302SCarolineConcatto //
764ab3302SCarolineConcatto //===----------------------------------------------------------------------===//
864ab3302SCarolineConcatto 
964ab3302SCarolineConcatto #include "flang/Evaluate/check-expression.h"
1064ab3302SCarolineConcatto #include "flang/Evaluate/traverse.h"
1164ab3302SCarolineConcatto #include "flang/Evaluate/type.h"
1264ab3302SCarolineConcatto #include "flang/Semantics/symbol.h"
1364ab3302SCarolineConcatto #include "flang/Semantics/tools.h"
1464ab3302SCarolineConcatto 
1564ab3302SCarolineConcatto namespace Fortran::evaluate {
1664ab3302SCarolineConcatto 
1764ab3302SCarolineConcatto // Constant expression predicate IsConstantExpr().
1864ab3302SCarolineConcatto // This code determines whether an expression is a "constant expression"
1964ab3302SCarolineConcatto // in the sense of section 10.1.12.  This is not the same thing as being
2064ab3302SCarolineConcatto // able to fold it (yet) into a known constant value; specifically,
2164ab3302SCarolineConcatto // the expression may reference derived type kind parameters whose values
2264ab3302SCarolineConcatto // are not yet known.
2364ab3302SCarolineConcatto class IsConstantExprHelper : public AllTraverse<IsConstantExprHelper, true> {
2464ab3302SCarolineConcatto public:
2564ab3302SCarolineConcatto   using Base = AllTraverse<IsConstantExprHelper, true>;
2664ab3302SCarolineConcatto   IsConstantExprHelper() : Base{*this} {}
2764ab3302SCarolineConcatto   using Base::operator();
2864ab3302SCarolineConcatto 
2964ab3302SCarolineConcatto   template <int KIND> bool operator()(const TypeParamInquiry<KIND> &inq) const {
3064ab3302SCarolineConcatto     return IsKindTypeParameter(inq.parameter());
3164ab3302SCarolineConcatto   }
3264ab3302SCarolineConcatto   bool operator()(const semantics::Symbol &symbol) const {
3364ab3302SCarolineConcatto     return IsNamedConstant(symbol) || IsImpliedDoIndex(symbol);
3464ab3302SCarolineConcatto   }
3564ab3302SCarolineConcatto   bool operator()(const CoarrayRef &) const { return false; }
3664ab3302SCarolineConcatto   bool operator()(const semantics::ParamValue &param) const {
3764ab3302SCarolineConcatto     return param.isExplicit() && (*this)(param.GetExplicit());
3864ab3302SCarolineConcatto   }
3964ab3302SCarolineConcatto   template <typename T> bool operator()(const FunctionRef<T> &call) const {
4064ab3302SCarolineConcatto     if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) {
4164ab3302SCarolineConcatto       return intrinsic->name == "kind";
4264ab3302SCarolineConcatto       // TODO: other inquiry intrinsics
4364ab3302SCarolineConcatto     } else {
4464ab3302SCarolineConcatto       return false;
4564ab3302SCarolineConcatto     }
4664ab3302SCarolineConcatto   }
4764ab3302SCarolineConcatto 
4864ab3302SCarolineConcatto   // Forbid integer division by zero in constants.
4964ab3302SCarolineConcatto   template <int KIND>
5064ab3302SCarolineConcatto   bool operator()(
5164ab3302SCarolineConcatto       const Divide<Type<TypeCategory::Integer, KIND>> &division) const {
5264ab3302SCarolineConcatto     using T = Type<TypeCategory::Integer, KIND>;
5364ab3302SCarolineConcatto     if (const auto divisor{GetScalarConstantValue<T>(division.right())}) {
5464ab3302SCarolineConcatto       return !divisor->IsZero();
5564ab3302SCarolineConcatto     } else {
5664ab3302SCarolineConcatto       return false;
5764ab3302SCarolineConcatto     }
5864ab3302SCarolineConcatto   }
5964ab3302SCarolineConcatto };
6064ab3302SCarolineConcatto 
6164ab3302SCarolineConcatto template <typename A> bool IsConstantExpr(const A &x) {
6264ab3302SCarolineConcatto   return IsConstantExprHelper{}(x);
6364ab3302SCarolineConcatto }
6464ab3302SCarolineConcatto template bool IsConstantExpr(const Expr<SomeType> &);
6564ab3302SCarolineConcatto template bool IsConstantExpr(const Expr<SomeInteger> &);
669977b24aSpeter klausler template bool IsConstantExpr(const Expr<SubscriptInteger> &);
6764ab3302SCarolineConcatto 
6864ab3302SCarolineConcatto // Object pointer initialization checking predicate IsInitialDataTarget().
6964ab3302SCarolineConcatto // This code determines whether an expression is allowable as the static
7064ab3302SCarolineConcatto // data address used to initialize a pointer with "=> x".  See C765.
7164ab3302SCarolineConcatto struct IsInitialDataTargetHelper
7264ab3302SCarolineConcatto     : public AllTraverse<IsInitialDataTargetHelper, true> {
7364ab3302SCarolineConcatto   using Base = AllTraverse<IsInitialDataTargetHelper, true>;
7464ab3302SCarolineConcatto   using Base::operator();
7564ab3302SCarolineConcatto   explicit IsInitialDataTargetHelper(parser::ContextualMessages &m)
7664ab3302SCarolineConcatto       : Base{*this}, messages_{m} {}
7764ab3302SCarolineConcatto 
7864ab3302SCarolineConcatto   bool operator()(const BOZLiteralConstant &) const { return false; }
7964ab3302SCarolineConcatto   bool operator()(const NullPointer &) const { return true; }
8064ab3302SCarolineConcatto   template <typename T> bool operator()(const Constant<T> &) const {
8164ab3302SCarolineConcatto     return false;
8264ab3302SCarolineConcatto   }
8364ab3302SCarolineConcatto   bool operator()(const semantics::Symbol &symbol) const {
8464ab3302SCarolineConcatto     const Symbol &ultimate{symbol.GetUltimate()};
8564ab3302SCarolineConcatto     if (IsAllocatable(ultimate)) {
8664ab3302SCarolineConcatto       messages_.Say(
8764ab3302SCarolineConcatto           "An initial data target may not be a reference to an ALLOCATABLE '%s'"_err_en_US,
8864ab3302SCarolineConcatto           ultimate.name());
8964ab3302SCarolineConcatto     } else if (ultimate.Corank() > 0) {
9064ab3302SCarolineConcatto       messages_.Say(
9164ab3302SCarolineConcatto           "An initial data target may not be a reference to a coarray '%s'"_err_en_US,
9264ab3302SCarolineConcatto           ultimate.name());
9364ab3302SCarolineConcatto     } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) {
9464ab3302SCarolineConcatto       messages_.Say(
9564ab3302SCarolineConcatto           "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US,
9664ab3302SCarolineConcatto           ultimate.name());
9764ab3302SCarolineConcatto     } else if (!IsSaved(ultimate)) {
9864ab3302SCarolineConcatto       messages_.Say(
9964ab3302SCarolineConcatto           "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US,
10064ab3302SCarolineConcatto           ultimate.name());
10164ab3302SCarolineConcatto     }
10264ab3302SCarolineConcatto     return true;
10364ab3302SCarolineConcatto   }
10464ab3302SCarolineConcatto   bool operator()(const StaticDataObject &) const { return false; }
10564ab3302SCarolineConcatto   template <int KIND> bool operator()(const TypeParamInquiry<KIND> &) const {
10664ab3302SCarolineConcatto     return false;
10764ab3302SCarolineConcatto   }
10864ab3302SCarolineConcatto   bool operator()(const Triplet &x) const {
10964ab3302SCarolineConcatto     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
11064ab3302SCarolineConcatto         IsConstantExpr(x.stride());
11164ab3302SCarolineConcatto   }
11264ab3302SCarolineConcatto   bool operator()(const Subscript &x) const {
113*1f879005STim Keith     return std::visit(common::visitors{
11464ab3302SCarolineConcatto                           [&](const Triplet &t) { return (*this)(t); },
11564ab3302SCarolineConcatto                           [&](const auto &y) {
116*1f879005STim Keith                             return y.value().Rank() == 0 &&
117*1f879005STim Keith                                 IsConstantExpr(y.value());
11864ab3302SCarolineConcatto                           },
11964ab3302SCarolineConcatto                       },
12064ab3302SCarolineConcatto         x.u);
12164ab3302SCarolineConcatto   }
12264ab3302SCarolineConcatto   bool operator()(const CoarrayRef &) const { return false; }
12364ab3302SCarolineConcatto   bool operator()(const Substring &x) const {
12464ab3302SCarolineConcatto     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
12564ab3302SCarolineConcatto         (*this)(x.parent());
12664ab3302SCarolineConcatto   }
12764ab3302SCarolineConcatto   bool operator()(const DescriptorInquiry &) const { return false; }
12864ab3302SCarolineConcatto   template <typename T> bool operator()(const ArrayConstructor<T> &) const {
12964ab3302SCarolineConcatto     return false;
13064ab3302SCarolineConcatto   }
13164ab3302SCarolineConcatto   bool operator()(const StructureConstructor &) const { return false; }
132*1f879005STim Keith   template <typename T> bool operator()(const FunctionRef<T> &) {
133*1f879005STim Keith     return false;
134*1f879005STim Keith   }
13564ab3302SCarolineConcatto   template <typename D, typename R, typename... O>
13664ab3302SCarolineConcatto   bool operator()(const Operation<D, R, O...> &) const {
13764ab3302SCarolineConcatto     return false;
13864ab3302SCarolineConcatto   }
13964ab3302SCarolineConcatto   template <typename T> bool operator()(const Parentheses<T> &x) const {
14064ab3302SCarolineConcatto     return (*this)(x.left());
14164ab3302SCarolineConcatto   }
14264ab3302SCarolineConcatto   bool operator()(const Relational<SomeType> &) const { return false; }
14364ab3302SCarolineConcatto 
14464ab3302SCarolineConcatto private:
14564ab3302SCarolineConcatto   parser::ContextualMessages &messages_;
14664ab3302SCarolineConcatto };
14764ab3302SCarolineConcatto 
14864ab3302SCarolineConcatto bool IsInitialDataTarget(
14964ab3302SCarolineConcatto     const Expr<SomeType> &x, parser::ContextualMessages &messages) {
15064ab3302SCarolineConcatto   return IsInitialDataTargetHelper{messages}(x);
15164ab3302SCarolineConcatto }
15264ab3302SCarolineConcatto 
15364ab3302SCarolineConcatto // Specification expression validation (10.1.11(2), C1010)
15464ab3302SCarolineConcatto class CheckSpecificationExprHelper
15564ab3302SCarolineConcatto     : public AnyTraverse<CheckSpecificationExprHelper,
15664ab3302SCarolineConcatto           std::optional<std::string>> {
15764ab3302SCarolineConcatto public:
15864ab3302SCarolineConcatto   using Result = std::optional<std::string>;
15964ab3302SCarolineConcatto   using Base = AnyTraverse<CheckSpecificationExprHelper, Result>;
16064ab3302SCarolineConcatto   explicit CheckSpecificationExprHelper(const semantics::Scope &s)
16164ab3302SCarolineConcatto       : Base{*this}, scope_{s} {}
16264ab3302SCarolineConcatto   using Base::operator();
16364ab3302SCarolineConcatto 
16464ab3302SCarolineConcatto   Result operator()(const ProcedureDesignator &) const {
16564ab3302SCarolineConcatto     return "dummy procedure argument";
16664ab3302SCarolineConcatto   }
16764ab3302SCarolineConcatto   Result operator()(const CoarrayRef &) const { return "coindexed reference"; }
16864ab3302SCarolineConcatto 
16964ab3302SCarolineConcatto   Result operator()(const semantics::Symbol &symbol) const {
17064ab3302SCarolineConcatto     if (semantics::IsNamedConstant(symbol)) {
17164ab3302SCarolineConcatto       return std::nullopt;
17264ab3302SCarolineConcatto     } else if (symbol.IsDummy()) {
17364ab3302SCarolineConcatto       if (symbol.attrs().test(semantics::Attr::OPTIONAL)) {
17464ab3302SCarolineConcatto         return "reference to OPTIONAL dummy argument '"s +
17564ab3302SCarolineConcatto             symbol.name().ToString() + "'";
17664ab3302SCarolineConcatto       } else if (symbol.attrs().test(semantics::Attr::INTENT_OUT)) {
17764ab3302SCarolineConcatto         return "reference to INTENT(OUT) dummy argument '"s +
17864ab3302SCarolineConcatto             symbol.name().ToString() + "'";
17964ab3302SCarolineConcatto       } else if (symbol.has<semantics::ObjectEntityDetails>()) {
18064ab3302SCarolineConcatto         return std::nullopt;
18164ab3302SCarolineConcatto       } else {
18264ab3302SCarolineConcatto         return "dummy procedure argument";
18364ab3302SCarolineConcatto       }
18464ab3302SCarolineConcatto     } else if (symbol.has<semantics::UseDetails>() ||
18564ab3302SCarolineConcatto         symbol.has<semantics::HostAssocDetails>() ||
18664ab3302SCarolineConcatto         symbol.owner().kind() == semantics::Scope::Kind::Module) {
18764ab3302SCarolineConcatto       return std::nullopt;
18864ab3302SCarolineConcatto     } else if (const auto *object{
18964ab3302SCarolineConcatto                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
19064ab3302SCarolineConcatto       // TODO: what about EQUIVALENCE with data in COMMON?
19164ab3302SCarolineConcatto       // TODO: does this work for blank COMMON?
19264ab3302SCarolineConcatto       if (object->commonBlock()) {
19364ab3302SCarolineConcatto         return std::nullopt;
19464ab3302SCarolineConcatto       }
19564ab3302SCarolineConcatto     }
19664ab3302SCarolineConcatto     for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) {
19764ab3302SCarolineConcatto       s = &s->parent();
19864ab3302SCarolineConcatto       if (s == &symbol.owner()) {
19964ab3302SCarolineConcatto         return std::nullopt;
20064ab3302SCarolineConcatto       }
20164ab3302SCarolineConcatto     }
20264ab3302SCarolineConcatto     return "reference to local entity '"s + symbol.name().ToString() + "'";
20364ab3302SCarolineConcatto   }
20464ab3302SCarolineConcatto 
20564ab3302SCarolineConcatto   Result operator()(const Component &x) const {
20664ab3302SCarolineConcatto     // Don't look at the component symbol.
20764ab3302SCarolineConcatto     return (*this)(x.base());
20864ab3302SCarolineConcatto   }
20964ab3302SCarolineConcatto   Result operator()(const DescriptorInquiry &) const {
21064ab3302SCarolineConcatto     // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification
21164ab3302SCarolineConcatto     // expressions will have been converted to expressions over descriptor
21264ab3302SCarolineConcatto     // inquiries by Fold().
21364ab3302SCarolineConcatto     return std::nullopt;
21464ab3302SCarolineConcatto   }
21564ab3302SCarolineConcatto 
21664ab3302SCarolineConcatto   template <typename T> Result operator()(const FunctionRef<T> &x) const {
21764ab3302SCarolineConcatto     if (const auto *symbol{x.proc().GetSymbol()}) {
21864ab3302SCarolineConcatto       if (!semantics::IsPureProcedure(*symbol)) {
21964ab3302SCarolineConcatto         return "reference to impure function '"s + symbol->name().ToString() +
22064ab3302SCarolineConcatto             "'";
22164ab3302SCarolineConcatto       }
22264ab3302SCarolineConcatto       // TODO: other checks for standard module procedures
22364ab3302SCarolineConcatto     } else {
22464ab3302SCarolineConcatto       const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())};
22564ab3302SCarolineConcatto       if (intrin.name == "present") {
22664ab3302SCarolineConcatto         return std::nullopt; // no need to check argument(s)
22764ab3302SCarolineConcatto       }
22864ab3302SCarolineConcatto       if (IsConstantExpr(x)) {
22964ab3302SCarolineConcatto         // inquiry functions may not need to check argument(s)
23064ab3302SCarolineConcatto         return std::nullopt;
23164ab3302SCarolineConcatto       }
23264ab3302SCarolineConcatto     }
23364ab3302SCarolineConcatto     return (*this)(x.arguments());
23464ab3302SCarolineConcatto   }
23564ab3302SCarolineConcatto 
23664ab3302SCarolineConcatto private:
23764ab3302SCarolineConcatto   const semantics::Scope &scope_;
23864ab3302SCarolineConcatto };
23964ab3302SCarolineConcatto 
24064ab3302SCarolineConcatto template <typename A>
24164ab3302SCarolineConcatto void CheckSpecificationExpr(const A &x, parser::ContextualMessages &messages,
24264ab3302SCarolineConcatto     const semantics::Scope &scope) {
24364ab3302SCarolineConcatto   if (auto why{CheckSpecificationExprHelper{scope}(x)}) {
24464ab3302SCarolineConcatto     messages.Say("Invalid specification expression: %s"_err_en_US, *why);
24564ab3302SCarolineConcatto   }
24664ab3302SCarolineConcatto }
24764ab3302SCarolineConcatto 
24864ab3302SCarolineConcatto template void CheckSpecificationExpr(const Expr<SomeType> &,
24964ab3302SCarolineConcatto     parser::ContextualMessages &, const semantics::Scope &);
2509977b24aSpeter klausler template void CheckSpecificationExpr(const Expr<SomeInteger> &,
2519977b24aSpeter klausler     parser::ContextualMessages &, const semantics::Scope &);
2529977b24aSpeter klausler template void CheckSpecificationExpr(const Expr<SubscriptInteger> &,
2539977b24aSpeter klausler     parser::ContextualMessages &, const semantics::Scope &);
2549977b24aSpeter klausler template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &,
2559977b24aSpeter klausler     parser::ContextualMessages &, const semantics::Scope &);
25664ab3302SCarolineConcatto template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &,
25764ab3302SCarolineConcatto     parser::ContextualMessages &, const semantics::Scope &);
25864ab3302SCarolineConcatto template void CheckSpecificationExpr(
25964ab3302SCarolineConcatto     const std::optional<Expr<SubscriptInteger>> &, parser::ContextualMessages &,
26064ab3302SCarolineConcatto     const semantics::Scope &);
26164ab3302SCarolineConcatto 
26264ab3302SCarolineConcatto // IsSimplyContiguous() -- 9.5.4
26364ab3302SCarolineConcatto class IsSimplyContiguousHelper
26464ab3302SCarolineConcatto     : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> {
26564ab3302SCarolineConcatto public:
26664ab3302SCarolineConcatto   using Result = std::optional<bool>; // tri-state
26764ab3302SCarolineConcatto   using Base = AnyTraverse<IsSimplyContiguousHelper, Result>;
26864ab3302SCarolineConcatto   explicit IsSimplyContiguousHelper(const IntrinsicProcTable &t)
26964ab3302SCarolineConcatto       : Base{*this}, table_{t} {}
27064ab3302SCarolineConcatto   using Base::operator();
27164ab3302SCarolineConcatto 
27264ab3302SCarolineConcatto   Result operator()(const semantics::Symbol &symbol) const {
27364ab3302SCarolineConcatto     if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) ||
27464ab3302SCarolineConcatto         symbol.Rank() == 0) {
27564ab3302SCarolineConcatto       return true;
27664ab3302SCarolineConcatto     } else if (semantics::IsPointer(symbol)) {
27764ab3302SCarolineConcatto       return false;
27864ab3302SCarolineConcatto     } else if (const auto *details{
27964ab3302SCarolineConcatto                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
28064ab3302SCarolineConcatto       // N.B. ALLOCATABLEs are deferred shape, not assumed, and
28164ab3302SCarolineConcatto       // are obviously contiguous.
28264ab3302SCarolineConcatto       return !details->IsAssumedShape() && !details->IsAssumedRank();
28364ab3302SCarolineConcatto     } else {
28464ab3302SCarolineConcatto       return false;
28564ab3302SCarolineConcatto     }
28664ab3302SCarolineConcatto   }
28764ab3302SCarolineConcatto 
28864ab3302SCarolineConcatto   Result operator()(const ArrayRef &x) const {
28964ab3302SCarolineConcatto     const auto &symbol{x.GetLastSymbol()};
29064ab3302SCarolineConcatto     if (!(*this)(symbol)) {
29164ab3302SCarolineConcatto       return false;
29264ab3302SCarolineConcatto     } else if (auto rank{CheckSubscripts(x.subscript())}) {
29364ab3302SCarolineConcatto       // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is
29464ab3302SCarolineConcatto       return *rank > 0 || x.Rank() == 0;
29564ab3302SCarolineConcatto     } else {
29664ab3302SCarolineConcatto       return false;
29764ab3302SCarolineConcatto     }
29864ab3302SCarolineConcatto   }
29964ab3302SCarolineConcatto   Result operator()(const CoarrayRef &x) const {
30064ab3302SCarolineConcatto     return CheckSubscripts(x.subscript()).has_value();
30164ab3302SCarolineConcatto   }
30264ab3302SCarolineConcatto   Result operator()(const Component &x) const {
30364ab3302SCarolineConcatto     return x.base().Rank() == 0 && (*this)(x.GetLastSymbol());
30464ab3302SCarolineConcatto   }
30564ab3302SCarolineConcatto   Result operator()(const ComplexPart &) const { return false; }
30664ab3302SCarolineConcatto   Result operator()(const Substring &) const { return false; }
30764ab3302SCarolineConcatto 
30864ab3302SCarolineConcatto   template <typename T> Result operator()(const FunctionRef<T> &x) const {
30964ab3302SCarolineConcatto     if (auto chars{
31064ab3302SCarolineConcatto             characteristics::Procedure::Characterize(x.proc(), table_)}) {
31164ab3302SCarolineConcatto       if (chars->functionResult) {
31264ab3302SCarolineConcatto         const auto &result{*chars->functionResult};
31364ab3302SCarolineConcatto         return !result.IsProcedurePointer() &&
31464ab3302SCarolineConcatto             result.attrs.test(characteristics::FunctionResult::Attr::Pointer) &&
31564ab3302SCarolineConcatto             result.attrs.test(
31664ab3302SCarolineConcatto                 characteristics::FunctionResult::Attr::Contiguous);
31764ab3302SCarolineConcatto       }
31864ab3302SCarolineConcatto     }
31964ab3302SCarolineConcatto     return false;
32064ab3302SCarolineConcatto   }
32164ab3302SCarolineConcatto 
32264ab3302SCarolineConcatto private:
32364ab3302SCarolineConcatto   // If the subscripts can possibly be on a simply-contiguous array reference,
32464ab3302SCarolineConcatto   // return the rank.
32564ab3302SCarolineConcatto   static std::optional<int> CheckSubscripts(
32664ab3302SCarolineConcatto       const std::vector<Subscript> &subscript) {
32764ab3302SCarolineConcatto     bool anyTriplet{false};
32864ab3302SCarolineConcatto     int rank{0};
32964ab3302SCarolineConcatto     for (auto j{subscript.size()}; j-- > 0;) {
33064ab3302SCarolineConcatto       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
33164ab3302SCarolineConcatto         if (!triplet->IsStrideOne()) {
33264ab3302SCarolineConcatto           return std::nullopt;
33364ab3302SCarolineConcatto         } else if (anyTriplet) {
33464ab3302SCarolineConcatto           if (triplet->lower() || triplet->upper()) {
33564ab3302SCarolineConcatto             // all triplets before the last one must be just ":"
33664ab3302SCarolineConcatto             return std::nullopt;
33764ab3302SCarolineConcatto           }
33864ab3302SCarolineConcatto         } else {
33964ab3302SCarolineConcatto           anyTriplet = true;
34064ab3302SCarolineConcatto         }
34164ab3302SCarolineConcatto         ++rank;
34264ab3302SCarolineConcatto       } else if (anyTriplet || subscript[j].Rank() > 0) {
34364ab3302SCarolineConcatto         return std::nullopt;
34464ab3302SCarolineConcatto       }
34564ab3302SCarolineConcatto     }
34664ab3302SCarolineConcatto     return rank;
34764ab3302SCarolineConcatto   }
34864ab3302SCarolineConcatto 
34964ab3302SCarolineConcatto   const IntrinsicProcTable &table_;
35064ab3302SCarolineConcatto };
35164ab3302SCarolineConcatto 
35264ab3302SCarolineConcatto template <typename A>
35364ab3302SCarolineConcatto bool IsSimplyContiguous(const A &x, const IntrinsicProcTable &table) {
35464ab3302SCarolineConcatto   if (IsVariable(x)) {
35564ab3302SCarolineConcatto     auto known{IsSimplyContiguousHelper{table}(x)};
35664ab3302SCarolineConcatto     return known && *known;
35764ab3302SCarolineConcatto   } else {
35864ab3302SCarolineConcatto     return true; // not a variable
35964ab3302SCarolineConcatto   }
36064ab3302SCarolineConcatto }
36164ab3302SCarolineConcatto 
36264ab3302SCarolineConcatto template bool IsSimplyContiguous(
36364ab3302SCarolineConcatto     const Expr<SomeType> &, const IntrinsicProcTable &);
36464ab3302SCarolineConcatto 
365*1f879005STim Keith } // namespace Fortran::evaluate
366