xref: /llvm-project/flang/lib/Evaluate/check-expression.cpp (revision 9696355484152eda5684e0ec6249f4c423f08e42)
1 //===-- lib/Evaluate/check-expression.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 "flang/Evaluate/check-expression.h"
10 #include "flang/Evaluate/characteristics.h"
11 #include "flang/Evaluate/intrinsics.h"
12 #include "flang/Evaluate/tools.h"
13 #include "flang/Evaluate/traverse.h"
14 #include "flang/Evaluate/type.h"
15 #include "flang/Semantics/semantics.h"
16 #include "flang/Semantics/symbol.h"
17 #include "flang/Semantics/tools.h"
18 #include <set>
19 #include <string>
20 
21 namespace Fortran::evaluate {
22 
23 // Constant expression predicates IsConstantExpr() & IsScopeInvariantExpr().
24 // This code determines whether an expression is a "constant expression"
25 // in the sense of section 10.1.12.  This is not the same thing as being
26 // able to fold it (yet) into a known constant value; specifically,
27 // the expression may reference derived type kind parameters whose values
28 // are not yet known.
29 //
30 // The variant form (IsScopeInvariantExpr()) also accepts symbols that are
31 // INTENT(IN) dummy arguments without the VALUE attribute.
32 template <bool INVARIANT>
33 class IsConstantExprHelper
34     : public AllTraverse<IsConstantExprHelper<INVARIANT>, true> {
35 public:
36   using Base = AllTraverse<IsConstantExprHelper, true>;
37   IsConstantExprHelper() : Base{*this} {}
38   using Base::operator();
39 
40   // A missing expression is not considered to be constant.
41   template <typename A> bool operator()(const std::optional<A> &x) const {
42     return x && (*this)(*x);
43   }
44 
45   bool operator()(const TypeParamInquiry &inq) const {
46     return INVARIANT || semantics::IsKindTypeParameter(inq.parameter());
47   }
48   bool operator()(const semantics::Symbol &symbol) const {
49     const auto &ultimate{GetAssociationRoot(symbol)};
50     return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) ||
51         IsInitialProcedureTarget(ultimate) ||
52         ultimate.has<semantics::TypeParamDetails>() ||
53         (INVARIANT && IsIntentIn(symbol) && !IsOptional(symbol) &&
54             !symbol.attrs().test(semantics::Attr::VALUE));
55   }
56   bool operator()(const CoarrayRef &) const { return false; }
57   bool operator()(const semantics::ParamValue &param) const {
58     return param.isExplicit() && (*this)(param.GetExplicit());
59   }
60   bool operator()(const ProcedureRef &) const;
61   bool operator()(const StructureConstructor &constructor) const {
62     for (const auto &[symRef, expr] : constructor) {
63       if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) {
64         return false;
65       }
66     }
67     return true;
68   }
69   bool operator()(const Component &component) const {
70     return (*this)(component.base());
71   }
72   // Forbid integer division by zero in constants.
73   template <int KIND>
74   bool operator()(
75       const Divide<Type<TypeCategory::Integer, KIND>> &division) const {
76     using T = Type<TypeCategory::Integer, KIND>;
77     if (const auto divisor{GetScalarConstantValue<T>(division.right())}) {
78       return !divisor->IsZero() && (*this)(division.left());
79     } else {
80       return false;
81     }
82   }
83 
84   bool operator()(const Constant<SomeDerived> &) const { return true; }
85   bool operator()(const DescriptorInquiry &x) const {
86     const Symbol &sym{x.base().GetLastSymbol()};
87     return INVARIANT && !IsAllocatable(sym) &&
88         (!IsDummy(sym) ||
89             (IsIntentIn(sym) && !IsOptional(sym) &&
90                 !sym.attrs().test(semantics::Attr::VALUE)));
91   }
92 
93 private:
94   bool IsConstantStructureConstructorComponent(
95       const Symbol &, const Expr<SomeType> &) const;
96   bool IsConstantExprShape(const Shape &) const;
97 };
98 
99 template <bool INVARIANT>
100 bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent(
101     const Symbol &component, const Expr<SomeType> &expr) const {
102   if (IsAllocatable(component)) {
103     return IsNullObjectPointer(expr);
104   } else if (IsPointer(component)) {
105     return IsNullPointer(expr) || IsInitialDataTarget(expr) ||
106         IsInitialProcedureTarget(expr);
107   } else {
108     return (*this)(expr);
109   }
110 }
111 
112 template <bool INVARIANT>
113 bool IsConstantExprHelper<INVARIANT>::operator()(
114     const ProcedureRef &call) const {
115   // LBOUND, UBOUND, and SIZE with truly constant DIM= arguments will have
116   // been rewritten into DescriptorInquiry operations.
117   if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) {
118     const characteristics::Procedure &proc{intrinsic->characteristics.value()};
119     if (intrinsic->name == "kind" ||
120         intrinsic->name == IntrinsicProcTable::InvalidName ||
121         call.arguments().empty() || !call.arguments()[0]) {
122       // kind is always a constant, and we avoid cascading errors by considering
123       // invalid calls to intrinsics to be constant
124       return true;
125     } else if (intrinsic->name == "lbound") {
126       auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())};
127       return base && IsConstantExprShape(GetLBOUNDs(*base));
128     } else if (intrinsic->name == "ubound") {
129       auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())};
130       return base && IsConstantExprShape(GetUBOUNDs(*base));
131     } else if (intrinsic->name == "shape" || intrinsic->name == "size") {
132       auto shape{GetShape(call.arguments()[0]->UnwrapExpr())};
133       return shape && IsConstantExprShape(*shape);
134     } else if (proc.IsPure()) {
135       for (const auto &arg : call.arguments()) {
136         if (!arg) {
137           return false;
138         } else if (const auto *expr{arg->UnwrapExpr()};
139                    !expr || !(*this)(*expr)) {
140           return false;
141         }
142       }
143       return true;
144     }
145     // TODO: STORAGE_SIZE
146   }
147   return false;
148 }
149 
150 template <bool INVARIANT>
151 bool IsConstantExprHelper<INVARIANT>::IsConstantExprShape(
152     const Shape &shape) const {
153   for (const auto &extent : shape) {
154     if (!(*this)(extent)) {
155       return false;
156     }
157   }
158   return true;
159 }
160 
161 template <typename A> bool IsConstantExpr(const A &x) {
162   return IsConstantExprHelper<false>{}(x);
163 }
164 template bool IsConstantExpr(const Expr<SomeType> &);
165 template bool IsConstantExpr(const Expr<SomeInteger> &);
166 template bool IsConstantExpr(const Expr<SubscriptInteger> &);
167 template bool IsConstantExpr(const StructureConstructor &);
168 
169 // IsScopeInvariantExpr()
170 template <typename A> bool IsScopeInvariantExpr(const A &x) {
171   return IsConstantExprHelper<true>{}(x);
172 }
173 template bool IsScopeInvariantExpr(const Expr<SomeType> &);
174 template bool IsScopeInvariantExpr(const Expr<SomeInteger> &);
175 template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &);
176 
177 // IsActuallyConstant()
178 struct IsActuallyConstantHelper {
179   template <typename A> bool operator()(const A &) { return false; }
180   template <typename T> bool operator()(const Constant<T> &) { return true; }
181   template <typename T> bool operator()(const Parentheses<T> &x) {
182     return (*this)(x.left());
183   }
184   template <typename T> bool operator()(const Expr<T> &x) {
185     return common::visit([=](const auto &y) { return (*this)(y); }, x.u);
186   }
187   bool operator()(const Expr<SomeType> &x) {
188     return common::visit([this](const auto &y) { return (*this)(y); }, x.u);
189   }
190   bool operator()(const StructureConstructor &x) {
191     for (const auto &pair : x) {
192       const Expr<SomeType> &y{pair.second.value()};
193       const auto sym{pair.first};
194       const bool compIsConstant{(*this)(y)};
195       // If an allocatable component is initialized by a constant,
196       // the structure constructor is not a constant.
197       if ((!compIsConstant && !IsNullPointer(y)) ||
198           (compIsConstant && IsAllocatable(sym))) {
199         return false;
200       }
201     }
202     return true;
203   }
204   template <typename A> bool operator()(const A *x) { return x && (*this)(*x); }
205   template <typename A> bool operator()(const std::optional<A> &x) {
206     return x && (*this)(*x);
207   }
208 };
209 
210 template <typename A> bool IsActuallyConstant(const A &x) {
211   return IsActuallyConstantHelper{}(x);
212 }
213 
214 template bool IsActuallyConstant(const Expr<SomeType> &);
215 template bool IsActuallyConstant(const Expr<SomeInteger> &);
216 template bool IsActuallyConstant(const Expr<SubscriptInteger> &);
217 template bool IsActuallyConstant(const std::optional<Expr<SubscriptInteger>> &);
218 
219 // Object pointer initialization checking predicate IsInitialDataTarget().
220 // This code determines whether an expression is allowable as the static
221 // data address used to initialize a pointer with "=> x".  See C765.
222 class IsInitialDataTargetHelper
223     : public AllTraverse<IsInitialDataTargetHelper, true> {
224 public:
225   using Base = AllTraverse<IsInitialDataTargetHelper, true>;
226   using Base::operator();
227   explicit IsInitialDataTargetHelper(parser::ContextualMessages *m)
228       : Base{*this}, messages_{m} {}
229 
230   bool emittedMessage() const { return emittedMessage_; }
231 
232   bool operator()(const BOZLiteralConstant &) const { return false; }
233   bool operator()(const NullPointer &) const { return true; }
234   template <typename T> bool operator()(const Constant<T> &) const {
235     return false;
236   }
237   bool operator()(const semantics::Symbol &symbol) {
238     // This function checks only base symbols, not components.
239     const Symbol &ultimate{symbol.GetUltimate()};
240     if (const auto *assoc{
241             ultimate.detailsIf<semantics::AssocEntityDetails>()}) {
242       if (const auto &expr{assoc->expr()}) {
243         if (IsVariable(*expr)) {
244           return (*this)(*expr);
245         } else if (messages_) {
246           messages_->Say(
247               "An initial data target may not be an associated expression ('%s')"_err_en_US,
248               ultimate.name());
249           emittedMessage_ = true;
250         }
251       }
252       return false;
253     } else if (!CheckVarOrComponent(ultimate)) {
254       return false;
255     } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) {
256       if (messages_) {
257         messages_->Say(
258             "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US,
259             ultimate.name());
260         emittedMessage_ = true;
261       }
262       return false;
263     } else if (!IsSaved(ultimate)) {
264       if (messages_) {
265         messages_->Say(
266             "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US,
267             ultimate.name());
268         emittedMessage_ = true;
269       }
270       return false;
271     } else {
272       return true;
273     }
274   }
275   bool operator()(const StaticDataObject &) const { return false; }
276   bool operator()(const TypeParamInquiry &) const { return false; }
277   bool operator()(const Triplet &x) const {
278     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
279         IsConstantExpr(x.stride());
280   }
281   bool operator()(const Subscript &x) const {
282     return common::visit(common::visitors{
283                              [&](const Triplet &t) { return (*this)(t); },
284                              [&](const auto &y) {
285                                return y.value().Rank() == 0 &&
286                                    IsConstantExpr(y.value());
287                              },
288                          },
289         x.u);
290   }
291   bool operator()(const CoarrayRef &) const { return false; }
292   bool operator()(const Component &x) {
293     return CheckVarOrComponent(x.GetLastSymbol()) && (*this)(x.base());
294   }
295   bool operator()(const Substring &x) const {
296     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
297         (*this)(x.parent());
298   }
299   bool operator()(const DescriptorInquiry &) const { return false; }
300   template <typename T> bool operator()(const ArrayConstructor<T> &) const {
301     return false;
302   }
303   bool operator()(const StructureConstructor &) const { return false; }
304   template <typename D, typename R, typename... O>
305   bool operator()(const Operation<D, R, O...> &) const {
306     return false;
307   }
308   template <typename T> bool operator()(const Parentheses<T> &x) const {
309     return (*this)(x.left());
310   }
311   bool operator()(const ProcedureRef &x) const {
312     if (const SpecificIntrinsic * intrinsic{x.proc().GetSpecificIntrinsic()}) {
313       return intrinsic->characteristics.value().attrs.test(
314           characteristics::Procedure::Attr::NullPointer);
315     }
316     return false;
317   }
318   bool operator()(const Relational<SomeType> &) const { return false; }
319 
320 private:
321   bool CheckVarOrComponent(const semantics::Symbol &symbol) {
322     const Symbol &ultimate{symbol.GetUltimate()};
323     const char *unacceptable{nullptr};
324     if (ultimate.Corank() > 0) {
325       unacceptable = "a coarray";
326     } else if (IsAllocatable(ultimate)) {
327       unacceptable = "an ALLOCATABLE";
328     } else if (IsPointer(ultimate)) {
329       unacceptable = "a POINTER";
330     } else {
331       return true;
332     }
333     if (messages_) {
334       messages_->Say(
335           "An initial data target may not be a reference to %s '%s'"_err_en_US,
336           unacceptable, ultimate.name());
337       emittedMessage_ = true;
338     }
339     return false;
340   }
341 
342   parser::ContextualMessages *messages_;
343   bool emittedMessage_{false};
344 };
345 
346 bool IsInitialDataTarget(
347     const Expr<SomeType> &x, parser::ContextualMessages *messages) {
348   IsInitialDataTargetHelper helper{messages};
349   bool result{helper(x)};
350   if (!result && messages && !helper.emittedMessage()) {
351     messages->Say(
352         "An initial data target must be a designator with constant subscripts"_err_en_US);
353   }
354   return result;
355 }
356 
357 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) {
358   const auto &ultimate{symbol.GetUltimate()};
359   return common::visit(
360       common::visitors{
361           [&](const semantics::SubprogramDetails &subp) {
362             return !subp.isDummy() && !subp.stmtFunction() &&
363                 symbol.owner().kind() != semantics::Scope::Kind::MainProgram &&
364                 symbol.owner().kind() != semantics::Scope::Kind::Subprogram;
365           },
366           [](const semantics::SubprogramNameDetails &x) {
367             return x.kind() != semantics::SubprogramKind::Internal;
368           },
369           [&](const semantics::ProcEntityDetails &proc) {
370             return !semantics::IsPointer(ultimate) && !proc.isDummy();
371           },
372           [](const auto &) { return false; },
373       },
374       ultimate.details());
375 }
376 
377 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) {
378   if (const auto *intrin{proc.GetSpecificIntrinsic()}) {
379     return !intrin->isRestrictedSpecific;
380   } else if (proc.GetComponent()) {
381     return false;
382   } else {
383     return IsInitialProcedureTarget(DEREF(proc.GetSymbol()));
384   }
385 }
386 
387 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
388   if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
389     return IsInitialProcedureTarget(*proc);
390   } else {
391     return IsNullProcedurePointer(expr);
392   }
393 }
394 
395 // Converts, folds, and then checks type, rank, and shape of an
396 // initialization expression for a named constant, a non-pointer
397 // variable static initialization, a component default initializer,
398 // a type parameter default value, or instantiated type parameter value.
399 std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
400     Expr<SomeType> &&x, FoldingContext &context,
401     const semantics::Scope *instantiation) {
402   CHECK(!IsPointer(symbol));
403   if (auto symTS{
404           characteristics::TypeAndShape::Characterize(symbol, context)}) {
405     auto xType{x.GetType()};
406     auto converted{ConvertToType(symTS->type(), Expr<SomeType>{x})};
407     if (!converted &&
408         symbol.owner().context().IsEnabled(
409             common::LanguageFeature::LogicalIntegerAssignment)) {
410       converted = DataConstantConversionExtension(context, symTS->type(), x);
411       if (converted &&
412           symbol.owner().context().ShouldWarn(
413               common::LanguageFeature::LogicalIntegerAssignment)) {
414         context.messages().Say(
415             common::LanguageFeature::LogicalIntegerAssignment,
416             "nonstandard usage: initialization of %s with %s"_port_en_US,
417             symTS->type().AsFortran(), x.GetType().value().AsFortran());
418       }
419     }
420     if (converted) {
421       auto folded{Fold(context, std::move(*converted))};
422       if (IsActuallyConstant(folded)) {
423         int symRank{symTS->Rank()};
424         if (IsImpliedShape(symbol)) {
425           if (folded.Rank() == symRank) {
426             return ArrayConstantBoundChanger{
427                 std::move(*AsConstantExtents(
428                     context, GetRawLowerBounds(context, NamedEntity{symbol})))}
429                 .ChangeLbounds(std::move(folded));
430           } else {
431             context.messages().Say(
432                 "Implied-shape parameter '%s' has rank %d but its initializer has rank %d"_err_en_US,
433                 symbol.name(), symRank, folded.Rank());
434           }
435         } else if (auto extents{AsConstantExtents(context, symTS->shape())};
436             extents && !HasNegativeExtent(*extents)) {
437           if (folded.Rank() == 0 && symRank == 0) {
438             // symbol and constant are both scalars
439             return {std::move(folded)};
440           } else if (folded.Rank() == 0 && symRank > 0) {
441             // expand the scalar constant to an array
442             return ScalarConstantExpander{std::move(*extents),
443                 AsConstantExtents(
444                     context, GetRawLowerBounds(context, NamedEntity{symbol}))}
445                 .Expand(std::move(folded));
446           } else if (auto resultShape{GetShape(context, folded)}) {
447             CHECK(symTS->shape()); // Assumed-ranks cannot be initialized.
448             if (CheckConformance(context.messages(), *symTS->shape(),
449                     *resultShape, CheckConformanceFlags::None,
450                     "initialized object", "initialization expression")
451                     .value_or(false /*fail if not known now to conform*/)) {
452               // make a constant array with adjusted lower bounds
453               return ArrayConstantBoundChanger{
454                   std::move(*AsConstantExtents(context,
455                       GetRawLowerBounds(context, NamedEntity{symbol})))}
456                   .ChangeLbounds(std::move(folded));
457             }
458           }
459         } else if (IsNamedConstant(symbol)) {
460           if (IsExplicitShape(symbol)) {
461             context.messages().Say(
462                 "Named constant '%s' array must have constant shape"_err_en_US,
463                 symbol.name());
464           } else {
465             // Declaration checking handles other cases
466           }
467         } else {
468           context.messages().Say(
469               "Shape of initialized object '%s' must be constant"_err_en_US,
470               symbol.name());
471         }
472       } else if (IsErrorExpr(folded)) {
473       } else if (IsLenTypeParameter(symbol)) {
474         return {std::move(folded)};
475       } else if (IsKindTypeParameter(symbol)) {
476         if (instantiation) {
477           context.messages().Say(
478               "Value of kind type parameter '%s' (%s) must be a scalar INTEGER constant"_err_en_US,
479               symbol.name(), folded.AsFortran());
480         } else {
481           return {std::move(folded)};
482         }
483       } else if (IsNamedConstant(symbol)) {
484         if (symbol.name() == "numeric_storage_size" &&
485             symbol.owner().IsModule() &&
486             DEREF(symbol.owner().symbol()).name() == "iso_fortran_env") {
487           // Very special case: numeric_storage_size is not folded until
488           // it read from the iso_fortran_env module file, as its value
489           // depends on compilation options.
490           return {std::move(folded)};
491         }
492         context.messages().Say(
493             "Value of named constant '%s' (%s) cannot be computed as a constant value"_err_en_US,
494             symbol.name(), folded.AsFortran());
495       } else {
496         context.messages().Say(
497             "Initialization expression for '%s' (%s) cannot be computed as a constant value"_err_en_US,
498             symbol.name(), x.AsFortran());
499       }
500     } else if (xType) {
501       context.messages().Say(
502           "Initialization expression cannot be converted to declared type of '%s' from %s"_err_en_US,
503           symbol.name(), xType->AsFortran());
504     } else {
505       context.messages().Say(
506           "Initialization expression cannot be converted to declared type of '%s'"_err_en_US,
507           symbol.name());
508     }
509   }
510   return std::nullopt;
511 }
512 
513 // Specification expression validation (10.1.11(2), C1010)
514 class CheckSpecificationExprHelper
515     : public AnyTraverse<CheckSpecificationExprHelper,
516           std::optional<std::string>> {
517 public:
518   using Result = std::optional<std::string>;
519   using Base = AnyTraverse<CheckSpecificationExprHelper, Result>;
520   explicit CheckSpecificationExprHelper(const semantics::Scope &s,
521       FoldingContext &context, bool forElementalFunctionResult)
522       : Base{*this}, scope_{s}, context_{context},
523         forElementalFunctionResult_{forElementalFunctionResult} {}
524   using Base::operator();
525 
526   Result operator()(const CoarrayRef &) const { return "coindexed reference"; }
527 
528   Result operator()(const semantics::Symbol &symbol) const {
529     const auto &ultimate{symbol.GetUltimate()};
530     const auto *object{ultimate.detailsIf<semantics::ObjectEntityDetails>()};
531     bool isInitialized{semantics::IsSaved(ultimate) &&
532         !IsAllocatable(ultimate) && object &&
533         (ultimate.test(Symbol::Flag::InDataStmt) ||
534             object->init().has_value())};
535     if (const auto *assoc{
536             ultimate.detailsIf<semantics::AssocEntityDetails>()}) {
537       return (*this)(assoc->expr());
538     } else if (semantics::IsNamedConstant(ultimate) ||
539         ultimate.owner().IsModule() || ultimate.owner().IsSubmodule()) {
540       return std::nullopt;
541     } else if (scope_.IsDerivedType() &&
542         IsVariableName(ultimate)) { // C750, C754
543       return "derived type component or type parameter value not allowed to "
544              "reference variable '"s +
545           ultimate.name().ToString() + "'";
546     } else if (IsDummy(ultimate)) {
547       if (!inInquiry_ && forElementalFunctionResult_) {
548         return "dependence on value of dummy argument '"s +
549             ultimate.name().ToString() + "'";
550       } else if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) {
551         return "reference to OPTIONAL dummy argument '"s +
552             ultimate.name().ToString() + "'";
553       } else if (!inInquiry_ &&
554           ultimate.attrs().test(semantics::Attr::INTENT_OUT)) {
555         return "reference to INTENT(OUT) dummy argument '"s +
556             ultimate.name().ToString() + "'";
557       } else if (ultimate.has<semantics::ObjectEntityDetails>()) {
558         return std::nullopt;
559       } else {
560         return "dummy procedure argument";
561       }
562     } else if (&symbol.owner() != &scope_ || &ultimate.owner() != &scope_) {
563       return std::nullopt; // host association is in play
564     } else if (isInitialized &&
565         context_.languageFeatures().IsEnabled(
566             common::LanguageFeature::SavedLocalInSpecExpr)) {
567       if (!scope_.IsModuleFile() &&
568           context_.languageFeatures().ShouldWarn(
569               common::LanguageFeature::SavedLocalInSpecExpr)) {
570         context_.messages().Say(common::LanguageFeature::SavedLocalInSpecExpr,
571             "specification expression refers to local object '%s' (initialized and saved)"_port_en_US,
572             ultimate.name().ToString());
573       }
574       return std::nullopt;
575     } else if (const auto *object{
576                    ultimate.detailsIf<semantics::ObjectEntityDetails>()}) {
577       if (object->commonBlock()) {
578         return std::nullopt;
579       }
580     }
581     if (inInquiry_) {
582       return std::nullopt;
583     } else {
584       return "reference to local entity '"s + ultimate.name().ToString() + "'";
585     }
586   }
587 
588   Result operator()(const Component &x) const {
589     // Don't look at the component symbol.
590     return (*this)(x.base());
591   }
592   Result operator()(const ArrayRef &x) const {
593     if (auto result{(*this)(x.base())}) {
594       return result;
595     }
596     // The subscripts don't get special protection for being in a
597     // specification inquiry context;
598     auto restorer{common::ScopedSet(inInquiry_, false)};
599     return (*this)(x.subscript());
600   }
601   Result operator()(const Substring &x) const {
602     if (auto result{(*this)(x.parent())}) {
603       return result;
604     }
605     // The bounds don't get special protection for being in a
606     // specification inquiry context;
607     auto restorer{common::ScopedSet(inInquiry_, false)};
608     if (auto result{(*this)(x.lower())}) {
609       return result;
610     }
611     return (*this)(x.upper());
612   }
613   Result operator()(const DescriptorInquiry &x) const {
614     // Many uses of SIZE(), LBOUND(), &c. that are valid in specification
615     // expressions will have been converted to expressions over descriptor
616     // inquiries by Fold().
617     // Catch REAL, ALLOCATABLE :: X(:); REAL :: Y(SIZE(X))
618     if (IsPermissibleInquiry(
619             x.base().GetFirstSymbol(), x.base().GetLastSymbol(), x.field())) {
620       auto restorer{common::ScopedSet(inInquiry_, true)};
621       return (*this)(x.base());
622     } else if (IsConstantExpr(x)) {
623       return std::nullopt;
624     } else {
625       return "non-constant descriptor inquiry not allowed for local object";
626     }
627   }
628 
629   Result operator()(const TypeParamInquiry &inq) const {
630     if (scope_.IsDerivedType()) {
631       if (!IsConstantExpr(inq) &&
632           inq.base() /* X%T, not local T */) { // C750, C754
633         return "non-constant reference to a type parameter inquiry not allowed "
634                "for derived type components or type parameter values";
635       }
636     } else if (inq.base() &&
637         IsInquiryAlwaysPermissible(inq.base()->GetFirstSymbol())) {
638       auto restorer{common::ScopedSet(inInquiry_, true)};
639       return (*this)(inq.base());
640     } else if (!IsConstantExpr(inq)) {
641       return "non-constant type parameter inquiry not allowed for local object";
642     }
643     return std::nullopt;
644   }
645 
646   Result operator()(const ProcedureRef &x) const {
647     bool inInquiry{false};
648     if (const auto *symbol{x.proc().GetSymbol()}) {
649       const Symbol &ultimate{symbol->GetUltimate()};
650       if (!semantics::IsPureProcedure(ultimate)) {
651         return "reference to impure function '"s + ultimate.name().ToString() +
652             "'";
653       }
654       if (semantics::IsStmtFunction(ultimate)) {
655         return "reference to statement function '"s +
656             ultimate.name().ToString() + "'";
657       }
658       if (scope_.IsDerivedType()) { // C750, C754
659         return "reference to function '"s + ultimate.name().ToString() +
660             "' not allowed for derived type components or type parameter"
661             " values";
662       }
663       if (auto procChars{characteristics::Procedure::Characterize(
664               x.proc(), context_, /*emitError=*/true)}) {
665         const auto iter{std::find_if(procChars->dummyArguments.begin(),
666             procChars->dummyArguments.end(),
667             [](const characteristics::DummyArgument &dummy) {
668               return std::holds_alternative<characteristics::DummyProcedure>(
669                   dummy.u);
670             })};
671         if (iter != procChars->dummyArguments.end() &&
672             ultimate.name().ToString() != "__builtin_c_funloc") {
673           return "reference to function '"s + ultimate.name().ToString() +
674               "' with dummy procedure argument '" + iter->name + '\'';
675         }
676       }
677       // References to internal functions are caught in expression semantics.
678       // TODO: other checks for standard module procedures
679     } else { // intrinsic
680       const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())};
681       inInquiry = context_.intrinsics().GetIntrinsicClass(intrin.name) ==
682           IntrinsicClass::inquiryFunction;
683       if (scope_.IsDerivedType()) { // C750, C754
684         if ((context_.intrinsics().IsIntrinsic(intrin.name) &&
685                 badIntrinsicsForComponents_.find(intrin.name) !=
686                     badIntrinsicsForComponents_.end())) {
687           return "reference to intrinsic '"s + intrin.name +
688               "' not allowed for derived type components or type parameter"
689               " values";
690         }
691         if (inInquiry && !IsConstantExpr(x)) {
692           return "non-constant reference to inquiry intrinsic '"s +
693               intrin.name +
694               "' not allowed for derived type components or type"
695               " parameter values";
696         }
697       }
698       // Type-determined inquiries (DIGITS, HUGE, &c.) will have already been
699       // folded and won't arrive here.  Inquiries that are represented with
700       // DescriptorInquiry operations (LBOUND) are checked elsewhere.  If a
701       // call that makes it to here satisfies the requirements of a constant
702       // expression (as Fortran defines it), it's fine.
703       if (IsConstantExpr(x)) {
704         return std::nullopt;
705       }
706       if (intrin.name == "present") {
707         return std::nullopt; // always ok
708       }
709       // Catch CHARACTER(:), ALLOCATABLE :: X; CHARACTER(LEN(X)) :: Y
710       if (inInquiry && x.arguments().size() >= 1) {
711         if (const auto &arg{x.arguments().at(0)}) {
712           if (auto dataRef{ExtractDataRef(*arg, true, true)}) {
713             if (intrin.name == "allocated" || intrin.name == "associated" ||
714                 intrin.name == "is_contiguous") { // ok
715             } else if (intrin.name == "len" &&
716                 IsPermissibleInquiry(dataRef->GetFirstSymbol(),
717                     dataRef->GetLastSymbol(),
718                     DescriptorInquiry::Field::Len)) { // ok
719             } else if (intrin.name == "lbound" &&
720                 IsPermissibleInquiry(dataRef->GetFirstSymbol(),
721                     dataRef->GetLastSymbol(),
722                     DescriptorInquiry::Field::LowerBound)) { // ok
723             } else if ((intrin.name == "shape" || intrin.name == "size" ||
724                            intrin.name == "sizeof" ||
725                            intrin.name == "storage_size" ||
726                            intrin.name == "ubound") &&
727                 IsPermissibleInquiry(dataRef->GetFirstSymbol(),
728                     dataRef->GetLastSymbol(),
729                     DescriptorInquiry::Field::Extent)) { // ok
730             } else {
731               return "non-constant inquiry function '"s + intrin.name +
732                   "' not allowed for local object";
733             }
734           }
735         }
736       }
737     }
738     auto restorer{common::ScopedSet(inInquiry_, inInquiry)};
739     return (*this)(x.arguments());
740   }
741 
742 private:
743   const semantics::Scope &scope_;
744   FoldingContext &context_;
745   // Contextual information: this flag is true when in an argument to
746   // an inquiry intrinsic like SIZE().
747   mutable bool inInquiry_{false};
748   bool forElementalFunctionResult_{false}; // F'2023 C15121
749   const std::set<std::string> badIntrinsicsForComponents_{
750       "allocated", "associated", "extends_type_of", "present", "same_type_as"};
751 
752   bool IsInquiryAlwaysPermissible(const semantics::Symbol &) const;
753   bool IsPermissibleInquiry(const semantics::Symbol &firstSymbol,
754       const semantics::Symbol &lastSymbol,
755       DescriptorInquiry::Field field) const;
756 };
757 
758 bool CheckSpecificationExprHelper::IsInquiryAlwaysPermissible(
759     const semantics::Symbol &symbol) const {
760   if (&symbol.owner() != &scope_ || symbol.has<semantics::UseDetails>() ||
761       symbol.owner().kind() == semantics::Scope::Kind::Module ||
762       semantics::FindCommonBlockContaining(symbol) ||
763       symbol.has<semantics::HostAssocDetails>()) {
764     return true; // it's nonlocal
765   } else if (semantics::IsDummy(symbol) && !forElementalFunctionResult_) {
766     return true;
767   } else {
768     return false;
769   }
770 }
771 
772 bool CheckSpecificationExprHelper::IsPermissibleInquiry(
773     const semantics::Symbol &firstSymbol, const semantics::Symbol &lastSymbol,
774     DescriptorInquiry::Field field) const {
775   if (IsInquiryAlwaysPermissible(firstSymbol)) {
776     return true;
777   }
778   // Inquiries on local objects may not access a deferred bound or length.
779   // (This code used to be a switch, but it proved impossible to write it
780   // thus without running afoul of bogus warnings from different C++
781   // compilers.)
782   if (field == DescriptorInquiry::Field::Rank) {
783     return true; // always known
784   }
785   const auto *object{lastSymbol.detailsIf<semantics::ObjectEntityDetails>()};
786   if (field == DescriptorInquiry::Field::LowerBound ||
787       field == DescriptorInquiry::Field::Extent ||
788       field == DescriptorInquiry::Field::Stride) {
789     return object && !object->shape().CanBeDeferredShape();
790   }
791   if (field == DescriptorInquiry::Field::Len) {
792     return object && object->type() &&
793         object->type()->category() == semantics::DeclTypeSpec::Character &&
794         !object->type()->characterTypeSpec().length().isDeferred();
795   }
796   return false;
797 }
798 
799 template <typename A>
800 void CheckSpecificationExpr(const A &x, const semantics::Scope &scope,
801     FoldingContext &context, bool forElementalFunctionResult) {
802   CheckSpecificationExprHelper helper{
803       scope, context, forElementalFunctionResult};
804   if (auto why{helper(x)}) {
805     context.messages().Say("Invalid specification expression%s: %s"_err_en_US,
806         forElementalFunctionResult ? " for elemental function result" : "",
807         *why);
808   }
809 }
810 
811 template void CheckSpecificationExpr(const Expr<SomeType> &,
812     const semantics::Scope &, FoldingContext &,
813     bool forElementalFunctionResult);
814 template void CheckSpecificationExpr(const Expr<SomeInteger> &,
815     const semantics::Scope &, FoldingContext &,
816     bool forElementalFunctionResult);
817 template void CheckSpecificationExpr(const Expr<SubscriptInteger> &,
818     const semantics::Scope &, FoldingContext &,
819     bool forElementalFunctionResult);
820 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &,
821     const semantics::Scope &, FoldingContext &,
822     bool forElementalFunctionResult);
823 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &,
824     const semantics::Scope &, FoldingContext &,
825     bool forElementalFunctionResult);
826 template void CheckSpecificationExpr(
827     const std::optional<Expr<SubscriptInteger>> &, const semantics::Scope &,
828     FoldingContext &, bool forElementalFunctionResult);
829 
830 // IsContiguous() -- 9.5.4
831 class IsContiguousHelper
832     : public AnyTraverse<IsContiguousHelper, std::optional<bool>> {
833 public:
834   using Result = std::optional<bool>; // tri-state
835   using Base = AnyTraverse<IsContiguousHelper, Result>;
836   explicit IsContiguousHelper(FoldingContext &c) : Base{*this}, context_{c} {}
837   using Base::operator();
838 
839   template <typename T> Result operator()(const Constant<T> &) const {
840     return true;
841   }
842   Result operator()(const StaticDataObject &) const { return true; }
843   Result operator()(const semantics::Symbol &symbol) const {
844     const auto &ultimate{symbol.GetUltimate()};
845     if (ultimate.attrs().test(semantics::Attr::CONTIGUOUS)) {
846       return true;
847     } else if (!IsVariable(symbol)) {
848       return true;
849     } else if (ultimate.Rank() == 0) {
850       // Extension: accept scalars as a degenerate case of
851       // simple contiguity to allow their use in contexts like
852       // data targets in pointer assignments with remapping.
853       return true;
854     } else if (const auto *details{
855                    ultimate.detailsIf<semantics::AssocEntityDetails>()}) {
856       // RANK(*) associating entity is contiguous.
857       if (details->IsAssumedSize()) {
858         return true;
859       } else {
860         return Base::operator()(ultimate); // use expr
861       }
862     } else if (semantics::IsPointer(ultimate) ||
863         semantics::IsAssumedShape(ultimate) || IsAssumedRank(ultimate)) {
864       return std::nullopt;
865     } else if (ultimate.has<semantics::ObjectEntityDetails>()) {
866       return true;
867     } else {
868       return Base::operator()(ultimate);
869     }
870   }
871 
872   Result operator()(const ArrayRef &x) const {
873     if (x.Rank() == 0) {
874       return true; // scalars considered contiguous
875     }
876     int subscriptRank{0};
877     auto baseLbounds{GetLBOUNDs(context_, x.base())};
878     auto baseUbounds{GetUBOUNDs(context_, x.base())};
879     auto subscripts{CheckSubscripts(
880         x.subscript(), subscriptRank, &baseLbounds, &baseUbounds)};
881     if (!subscripts.value_or(false)) {
882       return subscripts; // subscripts not known to be contiguous
883     } else if (subscriptRank > 0) {
884       // a(1)%b(:,:) is contiguous if and only if a(1)%b is contiguous.
885       return (*this)(x.base());
886     } else {
887       // a(:)%b(1,1) is (probably) not contiguous.
888       return std::nullopt;
889     }
890   }
891   Result operator()(const CoarrayRef &x) const {
892     int rank{0};
893     return CheckSubscripts(x.subscript(), rank).has_value();
894   }
895   Result operator()(const Component &x) const {
896     if (x.base().Rank() == 0) {
897       return (*this)(x.GetLastSymbol());
898     } else {
899       if (Result baseIsContiguous{(*this)(x.base())}) {
900         if (!*baseIsContiguous) {
901           return false;
902         }
903         // TODO could be true if base contiguous and this is only component, or
904         // if base has only one element?
905       }
906       return std::nullopt;
907     }
908   }
909   Result operator()(const ComplexPart &x) const {
910     return x.complex().Rank() == 0;
911   }
912   Result operator()(const Substring &x) const {
913     if (x.Rank() == 0) {
914       return true; // scalar substring always contiguous
915     }
916     // Substrings with rank must have DataRefs as their parents
917     const DataRef &parentDataRef{DEREF(x.GetParentIf<DataRef>())};
918     std::optional<std::int64_t> len;
919     if (auto lenExpr{parentDataRef.LEN()}) {
920       len = ToInt64(Fold(context_, std::move(*lenExpr)));
921       if (len) {
922         if (*len <= 0) {
923           return true; // empty substrings
924         } else if (*len == 1) {
925           // Substrings can't be incomplete; is base array contiguous?
926           return (*this)(parentDataRef);
927         }
928       }
929     }
930     std::optional<std::int64_t> upper;
931     bool upperIsLen{false};
932     if (auto upperExpr{x.upper()}) {
933       upper = ToInt64(Fold(context_, common::Clone(*upperExpr)));
934       if (upper) {
935         if (*upper < 1) {
936           return true; // substring(n:0) empty
937         }
938         upperIsLen = len && *upper >= *len;
939       } else if (const auto *inquiry{
940                      UnwrapConvertedExpr<DescriptorInquiry>(*upperExpr)};
941                  inquiry && inquiry->field() == DescriptorInquiry::Field::Len) {
942         upperIsLen =
943             &parentDataRef.GetLastSymbol() == &inquiry->base().GetLastSymbol();
944       }
945     } else {
946       upperIsLen = true; // substring(n:)
947     }
948     if (auto lower{ToInt64(Fold(context_, x.lower()))}) {
949       if (*lower == 1 && upperIsLen) {
950         // known complete substring; is base contiguous?
951         return (*this)(parentDataRef);
952       } else if (upper) {
953         if (*upper < *lower) {
954           return true; // empty substring(3:2)
955         } else if (*lower > 1) {
956           return false; // known incomplete substring
957         } else if (len && *upper < *len) {
958           return false; // known incomplete substring
959         }
960       }
961     }
962     return std::nullopt; // contiguity not known
963   }
964 
965   Result operator()(const ProcedureRef &x) const {
966     if (auto chars{characteristics::Procedure::Characterize(
967             x.proc(), context_, /*emitError=*/true)}) {
968       if (chars->functionResult) {
969         const auto &result{*chars->functionResult};
970         if (!result.IsProcedurePointer()) {
971           if (result.attrs.test(
972                   characteristics::FunctionResult::Attr::Contiguous)) {
973             return true;
974           }
975           if (!result.attrs.test(
976                   characteristics::FunctionResult::Attr::Pointer)) {
977             return true;
978           }
979           if (const auto *type{result.GetTypeAndShape()};
980               type && type->Rank() == 0) {
981             return true; // pointer to scalar
982           }
983           // Must be non-CONTIGUOUS pointer to array
984         }
985       }
986     }
987     return std::nullopt;
988   }
989 
990   Result operator()(const NullPointer &) const { return true; }
991 
992 private:
993   // Returns "true" for a provably empty or simply contiguous array section;
994   // return "false" for a provably nonempty discontiguous section or for use
995   // of a vector subscript.
996   std::optional<bool> CheckSubscripts(const std::vector<Subscript> &subscript,
997       int &rank, const Shape *baseLbounds = nullptr,
998       const Shape *baseUbounds = nullptr) const {
999     bool anyTriplet{false};
1000     rank = 0;
1001     // Detect any provably empty dimension in this array section, which would
1002     // render the whole section empty and therefore vacuously contiguous.
1003     std::optional<bool> result;
1004     bool mayBeEmpty{false};
1005     auto dims{subscript.size()};
1006     std::vector<bool> knownPartialSlice(dims, false);
1007     for (auto j{dims}; j-- > 0;) {
1008       std::optional<ConstantSubscript> dimLbound;
1009       std::optional<ConstantSubscript> dimUbound;
1010       std::optional<ConstantSubscript> dimExtent;
1011       if (baseLbounds && j < baseLbounds->size()) {
1012         if (const auto &lb{baseLbounds->at(j)}) {
1013           dimLbound = ToInt64(Fold(context_, Expr<SubscriptInteger>{*lb}));
1014         }
1015       }
1016       if (baseUbounds && j < baseUbounds->size()) {
1017         if (const auto &ub{baseUbounds->at(j)}) {
1018           dimUbound = ToInt64(Fold(context_, Expr<SubscriptInteger>{*ub}));
1019         }
1020       }
1021       if (dimLbound && dimUbound) {
1022         if (*dimLbound <= *dimUbound) {
1023           dimExtent = *dimUbound - *dimLbound + 1;
1024         } else {
1025           // This is an empty dimension.
1026           result = true;
1027           dimExtent = 0;
1028         }
1029       }
1030 
1031       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
1032         ++rank;
1033         if (auto stride{ToInt64(triplet->stride())}) {
1034           const Expr<SubscriptInteger> *lowerBound{triplet->GetLower()};
1035           const Expr<SubscriptInteger> *upperBound{triplet->GetUpper()};
1036           std::optional<ConstantSubscript> lowerVal{lowerBound
1037                   ? ToInt64(Fold(context_, Expr<SubscriptInteger>{*lowerBound}))
1038                   : dimLbound};
1039           std::optional<ConstantSubscript> upperVal{upperBound
1040                   ? ToInt64(Fold(context_, Expr<SubscriptInteger>{*upperBound}))
1041                   : dimUbound};
1042           if (lowerVal && upperVal) {
1043             if (*lowerVal < *upperVal) {
1044               if (*stride < 0) {
1045                 result = true; // empty dimension
1046               } else if (!result && *stride > 1 &&
1047                   *lowerVal + *stride <= *upperVal) {
1048                 result = false; // discontiguous if not empty
1049               }
1050             } else if (*lowerVal > *upperVal) {
1051               if (*stride > 0) {
1052                 result = true; // empty dimension
1053               } else if (!result && *stride < 0 &&
1054                   *lowerVal + *stride >= *upperVal) {
1055                 result = false; // discontiguous if not empty
1056               }
1057             } else {
1058               mayBeEmpty = true;
1059             }
1060           } else {
1061             mayBeEmpty = true;
1062           }
1063         } else {
1064           mayBeEmpty = true;
1065         }
1066       } else if (subscript[j].Rank() > 0) {
1067         ++rank;
1068         if (!result) {
1069           result = false; // vector subscript
1070         }
1071         mayBeEmpty = true;
1072       } else {
1073         // Scalar subscript.
1074         if (dimExtent && *dimExtent > 1) {
1075           knownPartialSlice[j] = true;
1076         }
1077       }
1078     }
1079     if (rank == 0) {
1080       result = true; // scalar
1081     }
1082     if (result) {
1083       return result;
1084     }
1085     // Not provably discontiguous at this point.
1086     // Return "true" if simply contiguous, otherwise nullopt.
1087     for (auto j{subscript.size()}; j-- > 0;) {
1088       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
1089         auto stride{ToInt64(triplet->stride())};
1090         if (!stride || stride != 1) {
1091           return std::nullopt;
1092         } else if (anyTriplet) {
1093           if (triplet->GetLower() || triplet->GetUpper()) {
1094             // all triplets before the last one must be just ":" for
1095             // simple contiguity
1096             return std::nullopt;
1097           }
1098         } else {
1099           anyTriplet = true;
1100         }
1101         ++rank;
1102       } else if (anyTriplet) {
1103         // If the section cannot be empty, and this dimension's
1104         // scalar subscript is known not to cover the whole
1105         // dimension, then the array section is provably
1106         // discontiguous.
1107         return (mayBeEmpty || !knownPartialSlice[j])
1108             ? std::nullopt
1109             : std::make_optional(false);
1110       }
1111     }
1112     return true; // simply contiguous
1113   }
1114 
1115   FoldingContext &context_;
1116 };
1117 
1118 template <typename A>
1119 std::optional<bool> IsContiguous(const A &x, FoldingContext &context) {
1120   return IsContiguousHelper{context}(x);
1121 }
1122 
1123 template std::optional<bool> IsContiguous(
1124     const Expr<SomeType> &, FoldingContext &);
1125 template std::optional<bool> IsContiguous(const ArrayRef &, FoldingContext &);
1126 template std::optional<bool> IsContiguous(const Substring &, FoldingContext &);
1127 template std::optional<bool> IsContiguous(const Component &, FoldingContext &);
1128 template std::optional<bool> IsContiguous(
1129     const ComplexPart &, FoldingContext &);
1130 template std::optional<bool> IsContiguous(const CoarrayRef &, FoldingContext &);
1131 template std::optional<bool> IsContiguous(const Symbol &, FoldingContext &);
1132 
1133 // IsErrorExpr()
1134 struct IsErrorExprHelper : public AnyTraverse<IsErrorExprHelper, bool> {
1135   using Result = bool;
1136   using Base = AnyTraverse<IsErrorExprHelper, Result>;
1137   IsErrorExprHelper() : Base{*this} {}
1138   using Base::operator();
1139 
1140   bool operator()(const SpecificIntrinsic &x) {
1141     return x.name == IntrinsicProcTable::InvalidName;
1142   }
1143 };
1144 
1145 template <typename A> bool IsErrorExpr(const A &x) {
1146   return IsErrorExprHelper{}(x);
1147 }
1148 
1149 template bool IsErrorExpr(const Expr<SomeType> &);
1150 
1151 // C1577
1152 // TODO: Also check C1579 & C1582 here
1153 class StmtFunctionChecker
1154     : public AnyTraverse<StmtFunctionChecker, std::optional<parser::Message>> {
1155 public:
1156   using Result = std::optional<parser::Message>;
1157   using Base = AnyTraverse<StmtFunctionChecker, Result>;
1158 
1159   static constexpr auto feature{
1160       common::LanguageFeature::StatementFunctionExtensions};
1161 
1162   StmtFunctionChecker(const Symbol &sf, FoldingContext &context)
1163       : Base{*this}, sf_{sf}, context_{context} {
1164     if (!context_.languageFeatures().IsEnabled(feature)) {
1165       severity_ = parser::Severity::Error;
1166     } else if (context_.languageFeatures().ShouldWarn(feature)) {
1167       severity_ = parser::Severity::Portability;
1168     }
1169   }
1170   using Base::operator();
1171 
1172   Result Return(parser::Message &&msg) const {
1173     if (severity_) {
1174       msg.set_severity(*severity_);
1175       if (*severity_ != parser::Severity::Error) {
1176         msg.set_languageFeature(feature);
1177       }
1178     }
1179     return std::move(msg);
1180   }
1181 
1182   template <typename T> Result operator()(const ArrayConstructor<T> &) const {
1183     if (severity_) {
1184       return Return(parser::Message{sf_.name(),
1185           "Statement function '%s' should not contain an array constructor"_port_en_US,
1186           sf_.name()});
1187     } else {
1188       return std::nullopt;
1189     }
1190   }
1191   Result operator()(const StructureConstructor &) const {
1192     if (severity_) {
1193       return Return(parser::Message{sf_.name(),
1194           "Statement function '%s' should not contain a structure constructor"_port_en_US,
1195           sf_.name()});
1196     } else {
1197       return std::nullopt;
1198     }
1199   }
1200   Result operator()(const TypeParamInquiry &) const {
1201     if (severity_) {
1202       return Return(parser::Message{sf_.name(),
1203           "Statement function '%s' should not contain a type parameter inquiry"_port_en_US,
1204           sf_.name()});
1205     } else {
1206       return std::nullopt;
1207     }
1208   }
1209   Result operator()(const ProcedureDesignator &proc) const {
1210     if (const Symbol * symbol{proc.GetSymbol()}) {
1211       const Symbol &ultimate{symbol->GetUltimate()};
1212       if (const auto *subp{
1213               ultimate.detailsIf<semantics::SubprogramDetails>()}) {
1214         if (subp->stmtFunction() && &ultimate.owner() == &sf_.owner()) {
1215           if (ultimate.name().begin() > sf_.name().begin()) {
1216             return parser::Message{sf_.name(),
1217                 "Statement function '%s' may not reference another statement function '%s' that is defined later"_err_en_US,
1218                 sf_.name(), ultimate.name()};
1219           }
1220         }
1221       }
1222       if (auto chars{characteristics::Procedure::Characterize(
1223               proc, context_, /*emitError=*/true)}) {
1224         if (!chars->CanBeCalledViaImplicitInterface()) {
1225           if (severity_) {
1226             return Return(parser::Message{sf_.name(),
1227                 "Statement function '%s' should not reference function '%s' that requires an explicit interface"_port_en_US,
1228                 sf_.name(), symbol->name()});
1229           }
1230         }
1231       }
1232     }
1233     if (proc.Rank() > 0) {
1234       if (severity_) {
1235         return Return(parser::Message{sf_.name(),
1236             "Statement function '%s' should not reference a function that returns an array"_port_en_US,
1237             sf_.name()});
1238       }
1239     }
1240     return std::nullopt;
1241   }
1242   Result operator()(const ActualArgument &arg) const {
1243     if (const auto *expr{arg.UnwrapExpr()}) {
1244       if (auto result{(*this)(*expr)}) {
1245         return result;
1246       }
1247       if (expr->Rank() > 0 && !UnwrapWholeSymbolOrComponentDataRef(*expr)) {
1248         if (severity_) {
1249           return Return(parser::Message{sf_.name(),
1250               "Statement function '%s' should not pass an array argument that is not a whole array"_port_en_US,
1251               sf_.name()});
1252         }
1253       }
1254     }
1255     return std::nullopt;
1256   }
1257 
1258 private:
1259   const Symbol &sf_;
1260   FoldingContext &context_;
1261   std::optional<parser::Severity> severity_;
1262 };
1263 
1264 std::optional<parser::Message> CheckStatementFunction(
1265     const Symbol &sf, const Expr<SomeType> &expr, FoldingContext &context) {
1266   return StmtFunctionChecker{sf, context}(expr);
1267 }
1268 
1269 } // namespace Fortran::evaluate
1270