1 //===-- lib/Semantics/check-directive-structure.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 // Directive structure validity checks common to OpenMP, OpenACC and other
10 // directive language.
11 
12 #ifndef FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
13 #define FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
14 
15 #include "flang/Common/enum-set.h"
16 #include "flang/Semantics/semantics.h"
17 #include "flang/Semantics/tools.h"
18 #include <unordered_map>
19 
20 namespace Fortran::semantics {
21 
22 template <typename C, std::size_t ClauseEnumSize> struct DirectiveClauses {
23   const common::EnumSet<C, ClauseEnumSize> allowed;
24   const common::EnumSet<C, ClauseEnumSize> allowedOnce;
25   const common::EnumSet<C, ClauseEnumSize> allowedExclusive;
26   const common::EnumSet<C, ClauseEnumSize> requiredOneOf;
27 };
28 
29 // Generic branching checker for invalid branching out of OpenMP/OpenACC
30 // directive.
31 // typename D is the directive enumeration.
32 template <typename D> class NoBranchingEnforce {
33 public:
NoBranchingEnforce(SemanticsContext & context,parser::CharBlock sourcePosition,D directive,std::string && upperCaseDirName)34   NoBranchingEnforce(SemanticsContext &context,
35       parser::CharBlock sourcePosition, D directive,
36       std::string &&upperCaseDirName)
37       : context_{context}, sourcePosition_{sourcePosition},
38         upperCaseDirName_{std::move(upperCaseDirName)},
39         currentDirective_{directive}, numDoConstruct_{0} {}
Pre(const T &)40   template <typename T> bool Pre(const T &) { return true; }
Post(const T &)41   template <typename T> void Post(const T &) {}
42 
Pre(const parser::Statement<T> & statement)43   template <typename T> bool Pre(const parser::Statement<T> &statement) {
44     currentStatementSourcePosition_ = statement.source;
45     return true;
46   }
47 
Pre(const parser::DoConstruct &)48   bool Pre(const parser::DoConstruct &) {
49     numDoConstruct_++;
50     return true;
51   }
Post(const parser::DoConstruct &)52   void Post(const parser::DoConstruct &) { numDoConstruct_--; }
Post(const parser::ReturnStmt &)53   void Post(const parser::ReturnStmt &) { EmitBranchOutError("RETURN"); }
Post(const parser::ExitStmt & exitStmt)54   void Post(const parser::ExitStmt &exitStmt) {
55     if (const auto &exitName{exitStmt.v}) {
56       CheckConstructNameBranching("EXIT", exitName.value());
57     } else {
58       CheckConstructNameBranching("EXIT");
59     }
60   }
Post(const parser::CycleStmt & cycleStmt)61   void Post(const parser::CycleStmt &cycleStmt) {
62     if (const auto &cycleName{cycleStmt.v}) {
63       CheckConstructNameBranching("CYCLE", cycleName.value());
64     } else {
65       switch ((llvm::omp::Directive)currentDirective_) {
66       // exclude directives which do not need a check for unlabelled CYCLES
67       case llvm::omp::Directive::OMPD_do:
68       case llvm::omp::Directive::OMPD_simd:
69       case llvm::omp::Directive::OMPD_parallel_do:
70       case llvm::omp::Directive::OMPD_parallel_do_simd:
71       case llvm::omp::Directive::OMPD_distribute_parallel_do:
72       case llvm::omp::Directive::OMPD_distribute_parallel_do_simd:
73       case llvm::omp::Directive::OMPD_distribute_parallel_for:
74       case llvm::omp::Directive::OMPD_distribute_simd:
75       case llvm::omp::Directive::OMPD_distribute_parallel_for_simd:
76         return;
77       default:
78         break;
79       }
80       CheckConstructNameBranching("CYCLE");
81     }
82   }
83 
84 private:
GetEnclosingMsg()85   parser::MessageFormattedText GetEnclosingMsg() const {
86     return {"Enclosing %s construct"_en_US, upperCaseDirName_};
87   }
88 
EmitBranchOutError(const char * stmt)89   void EmitBranchOutError(const char *stmt) const {
90     context_
91         .Say(currentStatementSourcePosition_,
92             "%s statement is not allowed in a %s construct"_err_en_US, stmt,
93             upperCaseDirName_)
94         .Attach(sourcePosition_, GetEnclosingMsg());
95   }
96 
EmitUnlabelledBranchOutError(const char * stmt)97   inline void EmitUnlabelledBranchOutError(const char *stmt) {
98     context_
99         .Say(currentStatementSourcePosition_,
100             "%s to construct outside of %s construct is not allowed"_err_en_US,
101             stmt, upperCaseDirName_)
102         .Attach(sourcePosition_, GetEnclosingMsg());
103   }
104 
EmitBranchOutErrorWithName(const char * stmt,const parser::Name & toName)105   void EmitBranchOutErrorWithName(
106       const char *stmt, const parser::Name &toName) const {
107     const std::string branchingToName{toName.ToString()};
108     context_
109         .Say(currentStatementSourcePosition_,
110             "%s to construct '%s' outside of %s construct is not allowed"_err_en_US,
111             stmt, branchingToName, upperCaseDirName_)
112         .Attach(sourcePosition_, GetEnclosingMsg());
113   }
114 
115   // Current semantic checker is not following OpenACC/OpenMP constructs as they
116   // are not Fortran constructs. Hence the ConstructStack doesn't capture
117   // OpenACC/OpenMP constructs. Apply an inverse way to figure out if a
118   // construct-name is branching out of an OpenACC/OpenMP construct. The control
119   // flow goes out of an OpenACC/OpenMP construct, if a construct-name from
120   // statement is found in ConstructStack.
CheckConstructNameBranching(const char * stmt,const parser::Name & stmtName)121   void CheckConstructNameBranching(
122       const char *stmt, const parser::Name &stmtName) {
123     const ConstructStack &stack{context_.constructStack()};
124     for (auto iter{stack.cend()}; iter-- != stack.cbegin();) {
125       const ConstructNode &construct{*iter};
126       const auto &constructName{MaybeGetNodeName(construct)};
127       if (constructName) {
128         if (stmtName.source == constructName->source) {
129           EmitBranchOutErrorWithName(stmt, stmtName);
130           return;
131         }
132       }
133     }
134   }
135 
136   // Check branching for unlabelled CYCLES and EXITs
CheckConstructNameBranching(const char * stmt)137   void CheckConstructNameBranching(const char *stmt) {
138     // found an enclosing looping construct for the unlabelled EXIT/CYCLE
139     if (numDoConstruct_ > 0) {
140       return;
141     }
142     // did not found an enclosing looping construct within the OpenMP/OpenACC
143     // directive
144     EmitUnlabelledBranchOutError(stmt);
145   }
146 
147   SemanticsContext &context_;
148   parser::CharBlock currentStatementSourcePosition_;
149   parser::CharBlock sourcePosition_;
150   std::string upperCaseDirName_;
151   D currentDirective_;
152   int numDoConstruct_; // tracks number of DoConstruct found AFTER encountering
153                        // an OpenMP/OpenACC directive
154 };
155 
156 // Generic structure checker for directives/clauses language such as OpenMP
157 // and OpenACC.
158 // typename D is the directive enumeration.
159 // tyepname C is the clause enumeration.
160 // typename PC is the parser class defined in parse-tree.h for the clauses.
161 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
162 class DirectiveStructureChecker : public virtual BaseChecker {
163 protected:
DirectiveStructureChecker(SemanticsContext & context,std::unordered_map<D,DirectiveClauses<C,ClauseEnumSize>> directiveClausesMap)164   DirectiveStructureChecker(SemanticsContext &context,
165       std::unordered_map<D, DirectiveClauses<C, ClauseEnumSize>>
166           directiveClausesMap)
167       : context_{context}, directiveClausesMap_(directiveClausesMap) {}
~DirectiveStructureChecker()168   virtual ~DirectiveStructureChecker() {}
169 
170   using ClauseMapTy = std::multimap<C, const PC *>;
171   struct DirectiveContext {
DirectiveContextDirectiveContext172     DirectiveContext(parser::CharBlock source, D d)
173         : directiveSource{source}, directive{d} {}
174 
175     parser::CharBlock directiveSource{nullptr};
176     parser::CharBlock clauseSource{nullptr};
177     D directive;
178     common::EnumSet<C, ClauseEnumSize> allowedClauses{};
179     common::EnumSet<C, ClauseEnumSize> allowedOnceClauses{};
180     common::EnumSet<C, ClauseEnumSize> allowedExclusiveClauses{};
181     common::EnumSet<C, ClauseEnumSize> requiredClauses{};
182 
183     const PC *clause{nullptr};
184     ClauseMapTy clauseInfo;
185     std::list<C> actualClauses;
186     Symbol *loopIV{nullptr};
187   };
188 
SetLoopIv(Symbol * symbol)189   void SetLoopIv(Symbol *symbol) { GetContext().loopIV = symbol; }
190 
191   // back() is the top of the stack
GetContext()192   DirectiveContext &GetContext() {
193     CHECK(!dirContext_.empty());
194     return dirContext_.back();
195   }
196 
GetContextParent()197   DirectiveContext &GetContextParent() {
198     CHECK(dirContext_.size() >= 2);
199     return dirContext_[dirContext_.size() - 2];
200   }
201 
SetContextClause(const PC & clause)202   void SetContextClause(const PC &clause) {
203     GetContext().clauseSource = clause.source;
204     GetContext().clause = &clause;
205   }
206 
ResetPartialContext(const parser::CharBlock & source)207   void ResetPartialContext(const parser::CharBlock &source) {
208     CHECK(!dirContext_.empty());
209     SetContextDirectiveSource(source);
210     GetContext().allowedClauses = {};
211     GetContext().allowedOnceClauses = {};
212     GetContext().allowedExclusiveClauses = {};
213     GetContext().requiredClauses = {};
214     GetContext().clauseInfo = {};
215     GetContext().loopIV = {nullptr};
216   }
217 
SetContextDirectiveSource(const parser::CharBlock & directive)218   void SetContextDirectiveSource(const parser::CharBlock &directive) {
219     GetContext().directiveSource = directive;
220   }
221 
SetContextDirectiveEnum(D dir)222   void SetContextDirectiveEnum(D dir) { GetContext().directive = dir; }
223 
SetContextAllowed(const common::EnumSet<C,ClauseEnumSize> & allowed)224   void SetContextAllowed(const common::EnumSet<C, ClauseEnumSize> &allowed) {
225     GetContext().allowedClauses = allowed;
226   }
227 
SetContextAllowedOnce(const common::EnumSet<C,ClauseEnumSize> & allowedOnce)228   void SetContextAllowedOnce(
229       const common::EnumSet<C, ClauseEnumSize> &allowedOnce) {
230     GetContext().allowedOnceClauses = allowedOnce;
231   }
232 
SetContextAllowedExclusive(const common::EnumSet<C,ClauseEnumSize> & allowedExclusive)233   void SetContextAllowedExclusive(
234       const common::EnumSet<C, ClauseEnumSize> &allowedExclusive) {
235     GetContext().allowedExclusiveClauses = allowedExclusive;
236   }
237 
SetContextRequired(const common::EnumSet<C,ClauseEnumSize> & required)238   void SetContextRequired(const common::EnumSet<C, ClauseEnumSize> &required) {
239     GetContext().requiredClauses = required;
240   }
241 
SetContextClauseInfo(C type)242   void SetContextClauseInfo(C type) {
243     GetContext().clauseInfo.emplace(type, GetContext().clause);
244   }
245 
AddClauseToCrtContext(C type)246   void AddClauseToCrtContext(C type) {
247     GetContext().actualClauses.push_back(type);
248   }
249 
250   // Check if the given clause is present in the current context
FindClause(C type)251   const PC *FindClause(C type) { return FindClause(GetContext(), type); }
252 
253   // Check if the given clause is present in the given context
FindClause(DirectiveContext & context,C type)254   const PC *FindClause(DirectiveContext &context, C type) {
255     auto it{context.clauseInfo.find(type)};
256     if (it != context.clauseInfo.end()) {
257       return it->second;
258     }
259     return nullptr;
260   }
261 
262   // Check if the given clause is present in the parent context
FindClauseParent(C type)263   const PC *FindClauseParent(C type) {
264     auto it{GetContextParent().clauseInfo.find(type)};
265     if (it != GetContextParent().clauseInfo.end()) {
266       return it->second;
267     }
268     return nullptr;
269   }
270 
271   std::pair<typename ClauseMapTy::iterator, typename ClauseMapTy::iterator>
FindClauses(C type)272   FindClauses(C type) {
273     auto it{GetContext().clauseInfo.equal_range(type)};
274     return it;
275   }
276 
GetEnclosingDirContext()277   DirectiveContext *GetEnclosingDirContext() {
278     CHECK(!dirContext_.empty());
279     auto it{dirContext_.rbegin()};
280     if (++it != dirContext_.rend()) {
281       return &(*it);
282     }
283     return nullptr;
284   }
285 
PushContext(const parser::CharBlock & source,D dir)286   void PushContext(const parser::CharBlock &source, D dir) {
287     dirContext_.emplace_back(source, dir);
288   }
289 
GetEnclosingContextWithDir(D dir)290   DirectiveContext *GetEnclosingContextWithDir(D dir) {
291     CHECK(!dirContext_.empty());
292     auto it{dirContext_.rbegin()};
293     while (++it != dirContext_.rend()) {
294       if (it->directive == dir) {
295         return &(*it);
296       }
297     }
298     return nullptr;
299   }
300 
CurrentDirectiveIsNested()301   bool CurrentDirectiveIsNested() { return dirContext_.size() > 1; };
302 
SetClauseSets(D dir)303   void SetClauseSets(D dir) {
304     dirContext_.back().allowedClauses = directiveClausesMap_[dir].allowed;
305     dirContext_.back().allowedOnceClauses =
306         directiveClausesMap_[dir].allowedOnce;
307     dirContext_.back().allowedExclusiveClauses =
308         directiveClausesMap_[dir].allowedExclusive;
309     dirContext_.back().requiredClauses =
310         directiveClausesMap_[dir].requiredOneOf;
311   }
PushContextAndClauseSets(const parser::CharBlock & source,D dir)312   void PushContextAndClauseSets(const parser::CharBlock &source, D dir) {
313     PushContext(source, dir);
314     SetClauseSets(dir);
315   }
316 
317   void SayNotMatching(const parser::CharBlock &, const parser::CharBlock &);
318 
CheckMatching(const B & beginDir,const B & endDir)319   template <typename B> void CheckMatching(const B &beginDir, const B &endDir) {
320     const auto &begin{beginDir.v};
321     const auto &end{endDir.v};
322     if (begin != end) {
323       SayNotMatching(beginDir.source, endDir.source);
324     }
325   }
326   // Check illegal branching out of `Parser::Block` for `Parser::Name` based
327   // nodes (example `Parser::ExitStmt`)
328   void CheckNoBranching(const parser::Block &block, D directive,
329       const parser::CharBlock &directiveSource);
330 
331   // Check that only clauses in set are after the specific clauses.
332   void CheckOnlyAllowedAfter(C clause, common::EnumSet<C, ClauseEnumSize> set);
333 
334   void CheckRequireAtLeastOneOf();
335 
336   void CheckAllowed(C clause);
337 
338   void CheckAtLeastOneClause();
339 
340   void CheckNotAllowedIfClause(
341       C clause, common::EnumSet<C, ClauseEnumSize> set);
342 
343   std::string ContextDirectiveAsFortran();
344 
345   void RequiresConstantPositiveParameter(
346       const C &clause, const parser::ScalarIntConstantExpr &i);
347 
348   void RequiresPositiveParameter(const C &clause,
349       const parser::ScalarIntExpr &i, llvm::StringRef paramName = "parameter");
350 
351   void OptionalConstantPositiveParameter(
352       const C &clause, const std::optional<parser::ScalarIntConstantExpr> &o);
353 
getClauseName(C clause)354   virtual llvm::StringRef getClauseName(C clause) { return ""; };
355 
getDirectiveName(D directive)356   virtual llvm::StringRef getDirectiveName(D directive) { return ""; };
357 
358   SemanticsContext &context_;
359   std::vector<DirectiveContext> dirContext_; // used as a stack
360   std::unordered_map<D, DirectiveClauses<C, ClauseEnumSize>>
361       directiveClausesMap_;
362 
363   std::string ClauseSetToString(const common::EnumSet<C, ClauseEnumSize> set);
364 };
365 
366 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
CheckNoBranching(const parser::Block & block,D directive,const parser::CharBlock & directiveSource)367 void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckNoBranching(
368     const parser::Block &block, D directive,
369     const parser::CharBlock &directiveSource) {
370   NoBranchingEnforce<D> noBranchingEnforce{
371       context_, directiveSource, directive, ContextDirectiveAsFortran()};
372   parser::Walk(block, noBranchingEnforce);
373 }
374 
375 // Check that only clauses included in the given set are present after the given
376 // clause.
377 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
CheckOnlyAllowedAfter(C clause,common::EnumSet<C,ClauseEnumSize> set)378 void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckOnlyAllowedAfter(
379     C clause, common::EnumSet<C, ClauseEnumSize> set) {
380   bool enforceCheck = false;
381   for (auto cl : GetContext().actualClauses) {
382     if (cl == clause) {
383       enforceCheck = true;
384       continue;
385     } else if (enforceCheck && !set.test(cl)) {
386       auto parserClause = GetContext().clauseInfo.find(cl);
387       context_.Say(parserClause->second->source,
388           "Clause %s is not allowed after clause %s on the %s "
389           "directive"_err_en_US,
390           parser::ToUpperCaseLetters(getClauseName(cl).str()),
391           parser::ToUpperCaseLetters(getClauseName(clause).str()),
392           ContextDirectiveAsFortran());
393     }
394   }
395 }
396 
397 // Check that at least one clause is attached to the directive.
398 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
399 void DirectiveStructureChecker<D, C, PC,
CheckAtLeastOneClause()400     ClauseEnumSize>::CheckAtLeastOneClause() {
401   if (GetContext().actualClauses.empty()) {
402     context_.Say(GetContext().directiveSource,
403         "At least one clause is required on the %s directive"_err_en_US,
404         ContextDirectiveAsFortran());
405   }
406 }
407 
408 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
409 std::string
ClauseSetToString(const common::EnumSet<C,ClauseEnumSize> set)410 DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::ClauseSetToString(
411     const common::EnumSet<C, ClauseEnumSize> set) {
412   std::string list;
413   set.IterateOverMembers([&](C o) {
414     if (!list.empty())
415       list.append(", ");
416     list.append(parser::ToUpperCaseLetters(getClauseName(o).str()));
417   });
418   return list;
419 }
420 
421 // Check that at least one clause in the required set is present on the
422 // directive.
423 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
424 void DirectiveStructureChecker<D, C, PC,
CheckRequireAtLeastOneOf()425     ClauseEnumSize>::CheckRequireAtLeastOneOf() {
426   if (GetContext().requiredClauses.empty())
427     return;
428   for (auto cl : GetContext().actualClauses) {
429     if (GetContext().requiredClauses.test(cl))
430       return;
431   }
432   // No clause matched in the actual clauses list
433   context_.Say(GetContext().directiveSource,
434       "At least one of %s clause must appear on the %s directive"_err_en_US,
435       ClauseSetToString(GetContext().requiredClauses),
436       ContextDirectiveAsFortran());
437 }
438 
439 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
440 std::string DirectiveStructureChecker<D, C, PC,
ContextDirectiveAsFortran()441     ClauseEnumSize>::ContextDirectiveAsFortran() {
442   return parser::ToUpperCaseLetters(
443       getDirectiveName(GetContext().directive).str());
444 }
445 
446 // Check that clauses present on the directive are allowed clauses.
447 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
CheckAllowed(C clause)448 void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckAllowed(
449     C clause) {
450   if (!GetContext().allowedClauses.test(clause) &&
451       !GetContext().allowedOnceClauses.test(clause) &&
452       !GetContext().allowedExclusiveClauses.test(clause) &&
453       !GetContext().requiredClauses.test(clause)) {
454     context_.Say(GetContext().clauseSource,
455         "%s clause is not allowed on the %s directive"_err_en_US,
456         parser::ToUpperCaseLetters(getClauseName(clause).str()),
457         parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
458     return;
459   }
460   if ((GetContext().allowedOnceClauses.test(clause) ||
461           GetContext().allowedExclusiveClauses.test(clause)) &&
462       FindClause(clause)) {
463     context_.Say(GetContext().clauseSource,
464         "At most one %s clause can appear on the %s directive"_err_en_US,
465         parser::ToUpperCaseLetters(getClauseName(clause).str()),
466         parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
467     return;
468   }
469   if (GetContext().allowedExclusiveClauses.test(clause)) {
470     std::vector<C> others;
471     GetContext().allowedExclusiveClauses.IterateOverMembers([&](C o) {
472       if (FindClause(o)) {
473         others.emplace_back(o);
474       }
475     });
476     for (const auto &e : others) {
477       context_.Say(GetContext().clauseSource,
478           "%s and %s clauses are mutually exclusive and may not appear on the "
479           "same %s directive"_err_en_US,
480           parser::ToUpperCaseLetters(getClauseName(clause).str()),
481           parser::ToUpperCaseLetters(getClauseName(e).str()),
482           parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
483     }
484     if (!others.empty()) {
485       return;
486     }
487   }
488   SetContextClauseInfo(clause);
489   AddClauseToCrtContext(clause);
490 }
491 
492 // Enforce restriction where clauses in the given set are not allowed if the
493 // given clause appears.
494 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
495 void DirectiveStructureChecker<D, C, PC,
CheckNotAllowedIfClause(C clause,common::EnumSet<C,ClauseEnumSize> set)496     ClauseEnumSize>::CheckNotAllowedIfClause(C clause,
497     common::EnumSet<C, ClauseEnumSize> set) {
498   if (std::find(GetContext().actualClauses.begin(),
499           GetContext().actualClauses.end(),
500           clause) == GetContext().actualClauses.end()) {
501     return; // Clause is not present
502   }
503 
504   for (auto cl : GetContext().actualClauses) {
505     if (set.test(cl)) {
506       context_.Say(GetContext().directiveSource,
507           "Clause %s is not allowed if clause %s appears on the %s directive"_err_en_US,
508           parser::ToUpperCaseLetters(getClauseName(cl).str()),
509           parser::ToUpperCaseLetters(getClauseName(clause).str()),
510           ContextDirectiveAsFortran());
511     }
512   }
513 }
514 
515 // Check the value of the clause is a constant positive integer.
516 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
517 void DirectiveStructureChecker<D, C, PC,
RequiresConstantPositiveParameter(const C & clause,const parser::ScalarIntConstantExpr & i)518     ClauseEnumSize>::RequiresConstantPositiveParameter(const C &clause,
519     const parser::ScalarIntConstantExpr &i) {
520   if (const auto v{GetIntValue(i)}) {
521     if (*v <= 0) {
522       context_.Say(GetContext().clauseSource,
523           "The parameter of the %s clause must be "
524           "a constant positive integer expression"_err_en_US,
525           parser::ToUpperCaseLetters(getClauseName(clause).str()));
526     }
527   }
528 }
529 
530 // Check the value of the clause is a constant positive parameter.
531 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
532 void DirectiveStructureChecker<D, C, PC,
OptionalConstantPositiveParameter(const C & clause,const std::optional<parser::ScalarIntConstantExpr> & o)533     ClauseEnumSize>::OptionalConstantPositiveParameter(const C &clause,
534     const std::optional<parser::ScalarIntConstantExpr> &o) {
535   if (o != std::nullopt) {
536     RequiresConstantPositiveParameter(clause, o.value());
537   }
538 }
539 
540 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
SayNotMatching(const parser::CharBlock & beginSource,const parser::CharBlock & endSource)541 void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::SayNotMatching(
542     const parser::CharBlock &beginSource, const parser::CharBlock &endSource) {
543   context_
544       .Say(endSource, "Unmatched %s directive"_err_en_US,
545           parser::ToUpperCaseLetters(endSource.ToString()))
546       .Attach(beginSource, "Does not match directive"_en_US);
547 }
548 
549 // Check the value of the clause is a positive parameter.
550 template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
551 void DirectiveStructureChecker<D, C, PC,
RequiresPositiveParameter(const C & clause,const parser::ScalarIntExpr & i,llvm::StringRef paramName)552     ClauseEnumSize>::RequiresPositiveParameter(const C &clause,
553     const parser::ScalarIntExpr &i, llvm::StringRef paramName) {
554   if (const auto v{GetIntValue(i)}) {
555     if (*v < 0) {
556       context_.Say(GetContext().clauseSource,
557           "The %s of the %s clause must be "
558           "a positive integer expression"_err_en_US,
559           paramName.str(),
560           parser::ToUpperCaseLetters(getClauseName(clause).str()));
561     }
562   }
563 }
564 
565 } // namespace Fortran::semantics
566 
567 #endif // FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
568