1 //===-- include/flang/Semantics/expression.h --------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef FORTRAN_SEMANTICS_EXPRESSION_H_
10 #define FORTRAN_SEMANTICS_EXPRESSION_H_
11 
12 #include "semantics.h"
13 #include "flang/Common/Fortran.h"
14 #include "flang/Common/indirection.h"
15 #include "flang/Common/restorer.h"
16 #include "flang/Common/visit.h"
17 #include "flang/Evaluate/characteristics.h"
18 #include "flang/Evaluate/check-expression.h"
19 #include "flang/Evaluate/expression.h"
20 #include "flang/Evaluate/fold.h"
21 #include "flang/Evaluate/tools.h"
22 #include "flang/Evaluate/type.h"
23 #include "flang/Parser/char-block.h"
24 #include "flang/Parser/parse-tree-visitor.h"
25 #include "flang/Parser/parse-tree.h"
26 #include "flang/Parser/tools.h"
27 #include <map>
28 #include <optional>
29 #include <type_traits>
30 #include <variant>
31 
32 using namespace Fortran::parser::literals;
33 
34 namespace Fortran::parser {
35 struct SourceLocationFindingVisitor {
PreSourceLocationFindingVisitor36   template <typename A> bool Pre(const A &x) {
37     if constexpr (HasSource<A>::value) {
38       source.ExtendToCover(x.source);
39       return false;
40     } else {
41       return true;
42     }
43   }
PostSourceLocationFindingVisitor44   template <typename A> void Post(const A &) {}
PostSourceLocationFindingVisitor45   void Post(const CharBlock &at) { source.ExtendToCover(at); }
46 
47   CharBlock source;
48 };
49 
FindSourceLocation(const A & x)50 template <typename A> CharBlock FindSourceLocation(const A &x) {
51   SourceLocationFindingVisitor visitor;
52   Walk(x, visitor);
53   return visitor.source;
54 }
55 } // namespace Fortran::parser
56 
57 using namespace Fortran::parser::literals;
58 
59 // The expression semantic analysis code has its implementation in
60 // namespace Fortran::evaluate, but the exposed API to it is in the
61 // namespace Fortran::semantics (below).
62 //
63 // The ExpressionAnalyzer wraps a SemanticsContext reference
64 // and implements constraint checking on expressions using the
65 // parse tree node wrappers that mirror the grammar annotations used
66 // in the Fortran standard (i.e., scalar-, constant-, &c.).
67 
68 namespace Fortran::evaluate {
69 
70 class IntrinsicProcTable;
71 
72 struct SetExprHelper {
SetExprHelperSetExprHelper73   explicit SetExprHelper(GenericExprWrapper &&expr) : expr_{std::move(expr)} {}
SetSetExprHelper74   void Set(parser::TypedExpr &x) {
75     x.Reset(new GenericExprWrapper{std::move(expr_)},
76         evaluate::GenericExprWrapper::Deleter);
77   }
SetSetExprHelper78   template <typename T> void Set(const common::Indirection<T> &x) {
79     Set(x.value());
80   }
SetSetExprHelper81   template <typename T> void Set(const T &x) {
82     if constexpr (parser::HasTypedExpr<T>::value) {
83       Set(x.typedExpr);
84     } else if constexpr (ConstraintTrait<T>) {
85       Set(x.thing);
86     } else if constexpr (WrapperTrait<T>) {
87       Set(x.v);
88     }
89   }
90 
91   GenericExprWrapper expr_;
92 };
93 
ResetExpr(const T & x)94 template <typename T> void ResetExpr(const T &x) {
95   SetExprHelper{GenericExprWrapper{/* error indicator */}}.Set(x);
96 }
97 
SetExpr(const T & x,Expr<SomeType> && expr)98 template <typename T> void SetExpr(const T &x, Expr<SomeType> &&expr) {
99   SetExprHelper{GenericExprWrapper{std::move(expr)}}.Set(x);
100 }
101 
102 class ExpressionAnalyzer {
103 public:
104   using MaybeExpr = std::optional<Expr<SomeType>>;
105 
ExpressionAnalyzer(semantics::SemanticsContext & sc)106   explicit ExpressionAnalyzer(semantics::SemanticsContext &sc) : context_{sc} {}
ExpressionAnalyzer(semantics::SemanticsContext & sc,FoldingContext & fc)107   ExpressionAnalyzer(semantics::SemanticsContext &sc, FoldingContext &fc)
108       : context_{sc}, foldingContext_{fc} {}
109   ExpressionAnalyzer(const ExpressionAnalyzer &) = default;
110 
context()111   semantics::SemanticsContext &context() const { return context_; }
inWhereBody()112   bool inWhereBody() const { return inWhereBody_; }
113   void set_inWhereBody(bool yes = true) { inWhereBody_ = yes; }
114 
GetFoldingContext()115   FoldingContext &GetFoldingContext() const { return foldingContext_; }
116 
GetContextualMessages()117   parser::ContextualMessages &GetContextualMessages() {
118     return foldingContext_.messages();
119   }
120 
Say(A &&...args)121   template <typename... A> parser::Message *Say(A &&...args) {
122     return GetContextualMessages().Say(std::forward<A>(args)...);
123   }
124 
125   template <typename T, typename... A>
SayAt(const T & parsed,A &&...args)126   parser::Message *SayAt(const T &parsed, A &&...args) {
127     return Say(parser::FindSourceLocation(parsed), std::forward<A>(args)...);
128   }
129 
130   int GetDefaultKind(common::TypeCategory);
131   DynamicType GetDefaultKindOfType(common::TypeCategory);
132 
133   // Return false and emit error if these checks fail:
134   bool CheckIntrinsicKind(TypeCategory, std::int64_t kind);
135   bool CheckIntrinsicSize(TypeCategory, std::int64_t size);
136 
137   // Manage a set of active implied DO loops.
138   bool AddImpliedDo(parser::CharBlock, int kind);
139   void RemoveImpliedDo(parser::CharBlock);
140 
141   // When the argument is the name of an active implied DO index, returns
142   // its INTEGER kind type parameter.
143   std::optional<int> IsImpliedDo(parser::CharBlock) const;
144 
145   // Allows a whole assumed-size array to appear for the lifetime of
146   // the returned value.
AllowWholeAssumedSizeArray()147   common::Restorer<bool> AllowWholeAssumedSizeArray() {
148     return common::ScopedSet(isWholeAssumedSizeArrayOk_, true);
149   }
150 
DoNotUseSavedTypedExprs()151   common::Restorer<bool> DoNotUseSavedTypedExprs() {
152     return common::ScopedSet(useSavedTypedExprs_, false);
153   }
154 
155   Expr<SubscriptInteger> AnalyzeKindSelector(common::TypeCategory category,
156       const std::optional<parser::KindSelector> &);
157 
158   MaybeExpr Analyze(const parser::Expr &);
159   MaybeExpr Analyze(const parser::Variable &);
160   MaybeExpr Analyze(const parser::Selector &);
161   MaybeExpr Analyze(const parser::Designator &);
162   MaybeExpr Analyze(const parser::DataStmtValue &);
163   MaybeExpr Analyze(const parser::AllocateObject &);
164   MaybeExpr Analyze(const parser::PointerObject &);
165 
Analyze(const common::Indirection<A> & x)166   template <typename A> MaybeExpr Analyze(const common::Indirection<A> &x) {
167     return Analyze(x.value());
168   }
Analyze(const std::optional<A> & x)169   template <typename A> MaybeExpr Analyze(const std::optional<A> &x) {
170     if (x) {
171       return Analyze(*x);
172     } else {
173       return std::nullopt;
174     }
175   }
176 
177   // Implement constraint-checking wrappers from the Fortran grammar.
Analyze(const parser::Scalar<A> & x)178   template <typename A> MaybeExpr Analyze(const parser::Scalar<A> &x) {
179     auto result{Analyze(x.thing)};
180     if (result) {
181       if (int rank{result->Rank()}; rank != 0) {
182         SayAt(x, "Must be a scalar value, but is a rank-%d array"_err_en_US,
183             rank);
184         ResetExpr(x);
185         return std::nullopt;
186       }
187     }
188     return result;
189   }
Analyze(const parser::Constant<A> & x)190   template <typename A> MaybeExpr Analyze(const parser::Constant<A> &x) {
191     auto restorer{
192         GetFoldingContext().messages().SetLocation(FindSourceLocation(x))};
193     auto result{Analyze(x.thing)};
194     if (result) {
195       *result = Fold(std::move(*result));
196       if (!IsConstantExpr(*result)) { //  C886, C887, C713
197         SayAt(x, "Must be a constant value"_err_en_US);
198         ResetExpr(x);
199         return std::nullopt;
200       } else {
201         // Save folded expression for later use
202         SetExpr(x, common::Clone(*result));
203       }
204     }
205     return result;
206   }
Analyze(const parser::Integer<A> & x)207   template <typename A> MaybeExpr Analyze(const parser::Integer<A> &x) {
208     auto result{Analyze(x.thing)};
209     if (!EnforceTypeConstraint(
210             parser::FindSourceLocation(x), result, TypeCategory::Integer)) {
211       ResetExpr(x);
212       return std::nullopt;
213     }
214     return result;
215   }
Analyze(const parser::Logical<A> & x)216   template <typename A> MaybeExpr Analyze(const parser::Logical<A> &x) {
217     auto result{Analyze(x.thing)};
218     if (!EnforceTypeConstraint(
219             parser::FindSourceLocation(x), result, TypeCategory::Logical)) {
220       ResetExpr(x);
221       return std::nullopt;
222     }
223     return result;
224   }
Analyze(const parser::DefaultChar<A> & x)225   template <typename A> MaybeExpr Analyze(const parser::DefaultChar<A> &x) {
226     auto result{Analyze(x.thing)};
227     if (!EnforceTypeConstraint(parser::FindSourceLocation(x), result,
228             TypeCategory::Character, true /* default kind */)) {
229       ResetExpr(x);
230       return std::nullopt;
231     }
232     return result;
233   }
234 
235   MaybeExpr Analyze(const parser::Name &);
Analyze(const parser::DataRef & dr)236   MaybeExpr Analyze(const parser::DataRef &dr) {
237     return Analyze<parser::DataRef>(dr);
238   }
239   MaybeExpr Analyze(const parser::StructureComponent &);
240   MaybeExpr Analyze(const parser::SignedIntLiteralConstant &);
241   MaybeExpr Analyze(const parser::SignedRealLiteralConstant &);
242   MaybeExpr Analyze(const parser::SignedComplexLiteralConstant &);
243   MaybeExpr Analyze(const parser::StructureConstructor &);
244   MaybeExpr Analyze(const parser::InitialDataTarget &);
245   MaybeExpr Analyze(const parser::NullInit &);
246 
247   void Analyze(const parser::CallStmt &);
248   const Assignment *Analyze(const parser::AssignmentStmt &);
249   const Assignment *Analyze(const parser::PointerAssignmentStmt &);
250 
251   // Builds a typed Designator from an untyped DataRef
252   MaybeExpr Designate(DataRef &&);
253 
254 protected:
255   int IntegerTypeSpecKind(const parser::IntegerTypeSpec &);
256 
257 private:
258   MaybeExpr Analyze(const parser::IntLiteralConstant &, bool negated = false);
259   MaybeExpr Analyze(const parser::RealLiteralConstant &);
260   MaybeExpr Analyze(const parser::ComplexPart &);
261   MaybeExpr Analyze(const parser::ComplexLiteralConstant &);
262   MaybeExpr Analyze(const parser::LogicalLiteralConstant &);
263   MaybeExpr Analyze(const parser::CharLiteralConstant &);
264   MaybeExpr Analyze(const parser::HollerithLiteralConstant &);
265   MaybeExpr Analyze(const parser::BOZLiteralConstant &);
266   MaybeExpr Analyze(const parser::NamedConstant &);
267   MaybeExpr Analyze(const parser::DataStmtConstant &);
268   MaybeExpr Analyze(const parser::Substring &);
269   MaybeExpr Analyze(const parser::ArrayElement &);
270   MaybeExpr Analyze(const parser::CoindexedNamedObject &);
271   MaybeExpr Analyze(const parser::CharLiteralConstantSubstring &);
272   MaybeExpr Analyze(const parser::SubstringInquiry &);
273   MaybeExpr Analyze(const parser::ArrayConstructor &);
274   MaybeExpr Analyze(const parser::FunctionReference &,
275       std::optional<parser::StructureConstructor> * = nullptr);
276   MaybeExpr Analyze(const parser::Expr::Parentheses &);
277   MaybeExpr Analyze(const parser::Expr::UnaryPlus &);
278   MaybeExpr Analyze(const parser::Expr::Negate &);
279   MaybeExpr Analyze(const parser::Expr::NOT &);
280   MaybeExpr Analyze(const parser::Expr::PercentLoc &);
281   MaybeExpr Analyze(const parser::Expr::DefinedUnary &);
282   MaybeExpr Analyze(const parser::Expr::Power &);
283   MaybeExpr Analyze(const parser::Expr::Multiply &);
284   MaybeExpr Analyze(const parser::Expr::Divide &);
285   MaybeExpr Analyze(const parser::Expr::Add &);
286   MaybeExpr Analyze(const parser::Expr::Subtract &);
287   MaybeExpr Analyze(const parser::Expr::ComplexConstructor &);
288   MaybeExpr Analyze(const parser::Expr::Concat &);
289   MaybeExpr Analyze(const parser::Expr::LT &);
290   MaybeExpr Analyze(const parser::Expr::LE &);
291   MaybeExpr Analyze(const parser::Expr::EQ &);
292   MaybeExpr Analyze(const parser::Expr::NE &);
293   MaybeExpr Analyze(const parser::Expr::GE &);
294   MaybeExpr Analyze(const parser::Expr::GT &);
295   MaybeExpr Analyze(const parser::Expr::AND &);
296   MaybeExpr Analyze(const parser::Expr::OR &);
297   MaybeExpr Analyze(const parser::Expr::EQV &);
298   MaybeExpr Analyze(const parser::Expr::NEQV &);
299   MaybeExpr Analyze(const parser::Expr::DefinedBinary &);
Analyze(const A & x)300   template <typename A> MaybeExpr Analyze(const A &x) {
301     return Analyze(x.u); // default case
302   }
Analyze(const std::variant<As...> & u)303   template <typename... As> MaybeExpr Analyze(const std::variant<As...> &u) {
304     return common::visit([&](const auto &x) { return Analyze(x); }, u);
305   }
306 
307   // Analysis subroutines
308   int AnalyzeKindParam(
309       const std::optional<parser::KindParam> &, int defaultKind);
310   template <typename PARSED>
311   MaybeExpr ExprOrVariable(const PARSED &, parser::CharBlock source);
312   template <typename PARSED>
313   MaybeExpr IntLiteralConstant(const PARSED &, bool negated = false);
314   MaybeExpr AnalyzeString(std::string &&, int kind);
315   std::optional<Expr<SubscriptInteger>> AsSubscript(MaybeExpr &&);
316   std::optional<Expr<SubscriptInteger>> TripletPart(
317       const std::optional<parser::Subscript> &);
318   std::optional<Subscript> AnalyzeSectionSubscript(
319       const parser::SectionSubscript &);
320   std::vector<Subscript> AnalyzeSectionSubscripts(
321       const std::list<parser::SectionSubscript> &);
322   std::optional<Component> CreateComponent(
323       DataRef &&, const Symbol &, const semantics::Scope &);
324   MaybeExpr CompleteSubscripts(ArrayRef &&);
325   MaybeExpr ApplySubscripts(DataRef &&, std::vector<Subscript> &&);
326   bool CheckRanks(const DataRef &); // Return false if error exists.
327   std::optional<Expr<SubscriptInteger>> GetSubstringBound(
328       const std::optional<parser::ScalarIntExpr> &);
329   MaybeExpr AnalyzeDefinedOp(const parser::Name &, ActualArguments &&);
330   MaybeExpr FixMisparsedSubstring(const parser::Designator &);
331 
332   struct CalleeAndArguments {
333     // A non-component function reference may constitute a misparsed
334     // structure constructor, in which case its derived type's Symbol
335     // will appear here.
336     std::variant<ProcedureDesignator, SymbolRef> u;
337     ActualArguments arguments;
338   };
339 
340   std::optional<CalleeAndArguments> AnalyzeProcedureComponentRef(
341       const parser::ProcComponentRef &, ActualArguments &&, bool isSubroutine);
342   std::optional<characteristics::Procedure> CheckCall(
343       parser::CharBlock, const ProcedureDesignator &, ActualArguments &);
344   using AdjustActuals =
345       std::optional<std::function<bool(const Symbol &, ActualArguments &)>>;
346   bool ResolveForward(const Symbol &);
347   std::pair<const Symbol *, bool /* failure due to NULL() actuals */>
348   ResolveGeneric(const Symbol &, const ActualArguments &, const AdjustActuals &,
349       bool isSubroutine, bool mightBeStructureConstructor = false);
350   void EmitGenericResolutionError(const Symbol &, bool dueToNullActuals);
351   const Symbol &AccessSpecific(
352       const Symbol &originalGeneric, const Symbol &specific);
353   std::optional<CalleeAndArguments> GetCalleeAndArguments(const parser::Name &,
354       ActualArguments &&, bool isSubroutine = false,
355       bool mightBeStructureConstructor = false);
356   std::optional<CalleeAndArguments> GetCalleeAndArguments(
357       const parser::ProcedureDesignator &, ActualArguments &&,
358       bool isSubroutine, bool mightBeStructureConstructor = false);
359   void CheckBadExplicitType(const SpecificCall &, const Symbol &);
360   void CheckForBadRecursion(parser::CharBlock, const semantics::Symbol &);
361   bool EnforceTypeConstraint(parser::CharBlock, const MaybeExpr &, TypeCategory,
362       bool defaultKind = false);
363   MaybeExpr MakeFunctionRef(
364       parser::CharBlock, ProcedureDesignator &&, ActualArguments &&);
365   MaybeExpr MakeFunctionRef(parser::CharBlock intrinsic, ActualArguments &&);
Fold(T && expr)366   template <typename T> T Fold(T &&expr) {
367     return evaluate::Fold(foldingContext_, std::move(expr));
368   }
369   bool CheckIsValidForwardReference(const semantics::DerivedTypeSpec &);
370 
371   semantics::SemanticsContext &context_;
372   FoldingContext &foldingContext_{context_.foldingContext()};
373   std::map<parser::CharBlock, int> impliedDos_; // values are INTEGER kinds
374   bool isWholeAssumedSizeArrayOk_{false};
375   bool useSavedTypedExprs_{true};
376   bool inWhereBody_{false};
377   bool inDataStmtConstant_{false};
378   friend class ArgumentAnalyzer;
379 };
380 
AreConformable(int leftRank,int rightRank)381 inline bool AreConformable(int leftRank, int rightRank) {
382   return leftRank == 0 || rightRank == 0 || leftRank == rightRank;
383 }
384 
385 template <typename L, typename R>
AreConformable(const L & left,const R & right)386 bool AreConformable(const L &left, const R &right) {
387   return AreConformable(left.Rank(), right.Rank());
388 }
389 
390 template <typename L, typename R>
ConformabilityCheck(parser::ContextualMessages & context,const L & left,const R & right)391 void ConformabilityCheck(
392     parser::ContextualMessages &context, const L &left, const R &right) {
393   if (!AreConformable(left, right)) {
394     context.Say("left operand has rank %d, right operand has rank %d"_err_en_US,
395         left.Rank(), right.Rank());
396   }
397 }
398 } // namespace Fortran::evaluate
399 
400 namespace Fortran::semantics {
401 
402 // Semantic analysis of one expression, variable, selector, designator, &c.
403 template <typename A>
AnalyzeExpr(SemanticsContext & context,const A & expr)404 std::optional<evaluate::Expr<evaluate::SomeType>> AnalyzeExpr(
405     SemanticsContext &context, const A &expr) {
406   return evaluate::ExpressionAnalyzer{context}.Analyze(expr);
407 }
408 
409 // Semantic analysis of an intrinsic type's KIND parameter expression.
410 evaluate::Expr<evaluate::SubscriptInteger> AnalyzeKindSelector(
411     SemanticsContext &, common::TypeCategory,
412     const std::optional<parser::KindSelector> &);
413 
414 // Semantic analysis of all expressions in a parse tree, which becomes
415 // decorated with typed representations for top-level expressions.
416 class ExprChecker {
417 public:
418   explicit ExprChecker(SemanticsContext &);
419 
Pre(const A &)420   template <typename A> bool Pre(const A &) { return true; }
Post(const A &)421   template <typename A> void Post(const A &) {}
422   bool Walk(const parser::Program &);
423 
Pre(const parser::Expr & x)424   bool Pre(const parser::Expr &x) {
425     exprAnalyzer_.Analyze(x);
426     return false;
427   }
Pre(const parser::Variable & x)428   bool Pre(const parser::Variable &x) {
429     exprAnalyzer_.Analyze(x);
430     return false;
431   }
Pre(const parser::Selector & x)432   bool Pre(const parser::Selector &x) {
433     exprAnalyzer_.Analyze(x);
434     return false;
435   }
Pre(const parser::DataStmtValue & x)436   bool Pre(const parser::DataStmtValue &x) {
437     exprAnalyzer_.Analyze(x);
438     return false;
439   }
Pre(const parser::AllocateObject & x)440   bool Pre(const parser::AllocateObject &x) {
441     exprAnalyzer_.Analyze(x);
442     return false;
443   }
Pre(const parser::PointerObject & x)444   bool Pre(const parser::PointerObject &x) {
445     exprAnalyzer_.Analyze(x);
446     return false;
447   }
448   bool Pre(const parser::DataImpliedDo &);
449 
Pre(const parser::CallStmt & x)450   bool Pre(const parser::CallStmt &x) {
451     exprAnalyzer_.Analyze(x);
452     return false;
453   }
Pre(const parser::AssignmentStmt & x)454   bool Pre(const parser::AssignmentStmt &x) {
455     exprAnalyzer_.Analyze(x);
456     return false;
457   }
Pre(const parser::PointerAssignmentStmt & x)458   bool Pre(const parser::PointerAssignmentStmt &x) {
459     exprAnalyzer_.Analyze(x);
460     return false;
461   }
462 
463   // Track whether we're in a WHERE statement or construct body
Pre(const parser::WhereStmt &)464   bool Pre(const parser::WhereStmt &) {
465     ++whereDepth_;
466     exprAnalyzer_.set_inWhereBody(InWhereBody());
467     return true;
468   }
Post(const parser::WhereStmt &)469   void Post(const parser::WhereStmt &) {
470     --whereDepth_;
471     exprAnalyzer_.set_inWhereBody(InWhereBody());
472   }
Pre(const parser::WhereBodyConstruct &)473   bool Pre(const parser::WhereBodyConstruct &) {
474     ++whereDepth_;
475     exprAnalyzer_.set_inWhereBody(InWhereBody());
476     return true;
477   }
Post(const parser::WhereBodyConstruct &)478   void Post(const parser::WhereBodyConstruct &) {
479     --whereDepth_;
480     exprAnalyzer_.set_inWhereBody(InWhereBody());
481   }
482 
Pre(const parser::ComponentDefStmt &)483   bool Pre(const parser::ComponentDefStmt &) {
484     // Already analyzed in name resolution and PDT instantiation;
485     // do not attempt to re-analyze now without type parameters.
486     return false;
487   }
488 
Pre(const parser::Scalar<A> & x)489   template <typename A> bool Pre(const parser::Scalar<A> &x) {
490     exprAnalyzer_.Analyze(x);
491     return false;
492   }
Pre(const parser::Constant<A> & x)493   template <typename A> bool Pre(const parser::Constant<A> &x) {
494     exprAnalyzer_.Analyze(x);
495     return false;
496   }
Pre(const parser::Integer<A> & x)497   template <typename A> bool Pre(const parser::Integer<A> &x) {
498     exprAnalyzer_.Analyze(x);
499     return false;
500   }
Pre(const parser::Logical<A> & x)501   template <typename A> bool Pre(const parser::Logical<A> &x) {
502     exprAnalyzer_.Analyze(x);
503     return false;
504   }
Pre(const parser::DefaultChar<A> & x)505   template <typename A> bool Pre(const parser::DefaultChar<A> &x) {
506     exprAnalyzer_.Analyze(x);
507     return false;
508   }
509 
510 private:
InWhereBody()511   bool InWhereBody() const { return whereDepth_ > 0; }
512 
513   SemanticsContext &context_;
514   evaluate::ExpressionAnalyzer exprAnalyzer_{context_};
515   int whereDepth_{0}; // nesting of WHERE statements & constructs
516 };
517 } // namespace Fortran::semantics
518 #endif // FORTRAN_SEMANTICS_EXPRESSION_H_
519