Skip to content

Conversation

@kparzysz
Copy link
Contributor

For ALLOCATORS and executable ALLOCATE first perform list item checks in the context of an individual ALLOCATE clause or directive respectively, then perform "global" checks, e.g. whether all list items are present on the ALLOCATE statement.

These changes allowed to simplify the checks for presence on ALLOCATE statement and the use of a predefined allocator.

Additionally, allow variable list item lists to be empty, add a test for the related spec restriction.

This is a first step towards unifying OpenMPDeclarativeAllocate and OpenMPExecutableAllocate into a single directive.

For ALLOCATORS and executable ALLOCATE first perform list item checks in
the context of an individual ALLOCATE clause or directive respectively,
then perform "global" checks, e.g. whether all list items are present on
the ALLOCATE statement.

These changes allowed to simplify the checks for presence on ALLOCATE
statement and the use of a predefined allocator.

Additionally, allow variable list item lists to be empty, add a test for
the related spec restriction.

This is a first step towards unifying OpenMPDeclarativeAllocate and
OpenMPExecutableAllocate into a single directive.
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:openmp flang:semantics flang:parser labels Oct 30, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 30, 2025

@llvm/pr-subscribers-flang-parser

@llvm/pr-subscribers-flang-semantics

Author: Krzysztof Parzyszek (kparzysz)

Changes

For ALLOCATORS and executable ALLOCATE first perform list item checks in the context of an individual ALLOCATE clause or directive respectively, then perform "global" checks, e.g. whether all list items are present on the ALLOCATE statement.

These changes allowed to simplify the checks for presence on ALLOCATE statement and the use of a predefined allocator.

Additionally, allow variable list item lists to be empty, add a test for the related spec restriction.

This is a first step towards unifying OpenMPDeclarativeAllocate and OpenMPExecutableAllocate into a single directive.


Patch is 36.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165719.diff

16 Files Affected:

  • (modified) flang/include/flang/Parser/parse-tree.h (+1-1)
  • (modified) flang/include/flang/Semantics/openmp-utils.h (+2)
  • (modified) flang/lib/Parser/openmp-parsers.cpp (+8-6)
  • (modified) flang/lib/Parser/unparse.cpp (+1-1)
  • (modified) flang/lib/Semantics/check-omp-structure.cpp (+241-194)
  • (modified) flang/lib/Semantics/check-omp-structure.h (-10)
  • (modified) flang/lib/Semantics/openmp-utils.cpp (+17)
  • (modified) flang/lib/Semantics/resolve-directives.cpp (+3-2)
  • (modified) flang/test/Semantics/OpenMP/allocate08.f90 (+4-4)
  • (modified) flang/test/Semantics/OpenMP/allocate09.f90 (+2-2)
  • (added) flang/test/Semantics/OpenMP/allocate10.f90 (+11)
  • (added) flang/test/Semantics/OpenMP/allocate11.f90 (+27)
  • (modified) flang/test/Semantics/OpenMP/allocators01.f90 (+1-1)
  • (removed) flang/test/Semantics/OpenMP/allocators04.f90 (-31)
  • (modified) flang/test/Semantics/OpenMP/allocators05.f90 (+1-1)
  • (modified) flang/test/Semantics/OpenMP/allocators07.f90 (+3-3)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 375790af90b74..4dd5e84f60dfe 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5155,7 +5155,7 @@ struct OpenMPThreadprivate {
 struct OpenMPDeclarativeAllocate {
   TUPLE_CLASS_BOILERPLATE(OpenMPDeclarativeAllocate);
   CharBlock source;
-  std::tuple<Verbatim, OmpObjectList, OmpClauseList> t;
+  std::tuple<Verbatim, std::optional<OmpObjectList>, OmpClauseList> t;
 };
 
 struct OpenMPDeclarativeConstruct {
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 032944d8be370..14a4f0e93bda5 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -72,6 +72,8 @@ const parser::OmpObject *GetArgumentObject(const parser::OmpArgument &argument);
 bool IsCommonBlock(const Symbol &sym);
 bool IsExtendedListItem(const Symbol &sym);
 bool IsVariableListItem(const Symbol &sym);
+bool IsTypeParamInquiry(const Symbol &sym);
+bool IsStructureComponent(const Symbol &sym);
 bool IsVarOrFunctionRef(const MaybeExpr &expr);
 
 bool IsMapEnteringType(parser::OmpMapType::Value type);
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 4159d2e41b78c..a9de26ea09ff8 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2045,11 +2045,12 @@ TYPE_PARSER(sourced(construct<OpenMPCriticalConstruct>(
     OmpBlockConstructParser{llvm::omp::Directive::OMPD_critical})))
 
 // 2.11.3 Executable Allocate directive
-TYPE_PARSER(
-    sourced(construct<OpenMPExecutableAllocate>(verbatim("ALLOCATE"_tok),
-        maybe(parenthesized(Parser<OmpObjectList>{})), Parser<OmpClauseList>{},
-        maybe(nonemptyList(Parser<OpenMPDeclarativeAllocate>{})) / endOmpLine,
-        statement(allocateStmt))))
+TYPE_PARSER(sourced(construct<OpenMPExecutableAllocate>(
+    verbatim("ALLOCATE"_tok), maybe(parenthesized(Parser<OmpObjectList>{})),
+    Parser<OmpClauseList>{},
+    maybe(nonemptyList(startOmpLine >> Parser<OpenMPDeclarativeAllocate>{})) /
+        endOmpLine,
+    statement(allocateStmt))))
 
 // 2.8.2 Declare Simd construct
 TYPE_PARSER(sourced(construct<OpenMPDeclareSimdConstruct>(
@@ -2079,7 +2080,8 @@ TYPE_PARSER(sourced( //
 // 2.11.3 Declarative Allocate directive
 TYPE_PARSER(
     sourced(construct<OpenMPDeclarativeAllocate>(verbatim("ALLOCATE"_tok),
-        parenthesized(Parser<OmpObjectList>{}), Parser<OmpClauseList>{})) /
+        maybe(parenthesized(Parser<OmpObjectList>{})),
+        Parser<OmpClauseList>{})) /
     lookAhead(endOmpLine / !statement(allocateStmt)))
 
 // Assumes Construct
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 9b38cfc40c5b2..b3a395c4d72e1 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2503,7 +2503,7 @@ class UnparseVisitor {
     BeginOpenMP();
     Word("!$OMP ALLOCATE");
     Put(" (");
-    Walk(std::get<OmpObjectList>(x.t));
+    Walk(std::get<std::optional<OmpObjectList>>(x.t));
     Put(")");
     Walk(std::get<OmpClauseList>(x.t));
     Put("\n");
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index aaaf1ec5d4626..ece179c4a66fc 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -667,49 +667,6 @@ void OmpStructureChecker::HasInvalidTeamsNesting(
   }
 }
 
-void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
-    const parser::CharBlock &source, const parser::Name &name) {
-  if (const auto *symbol{name.symbol}) {
-    const auto *commonBlock{FindCommonBlockContaining(*symbol)};
-    const auto &scope{context_.FindScope(symbol->name())};
-    const Scope &containingScope{GetProgramUnitContaining(scope)};
-    if (!isPredefinedAllocator &&
-        (IsSaved(*symbol) || commonBlock ||
-            containingScope.kind() == Scope::Kind::Module)) {
-      context_.Say(source,
-          "If list items within the %s directive have the "
-          "SAVE attribute, are a common block name, or are "
-          "declared in the scope of a module, then only "
-          "predefined memory allocator parameters can be used "
-          "in the allocator clause"_err_en_US,
-          ContextDirectiveAsFortran());
-    }
-  }
-}
-
-void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
-    const parser::CharBlock &source,
-    const parser::OmpObjectList &ompObjectList) {
-  for (const auto &ompObject : ompObjectList.v) {
-    common::visit(
-        common::visitors{
-            [&](const parser::Designator &designator) {
-              if (const auto *dataRef{
-                      std::get_if<parser::DataRef>(&designator.u)}) {
-                if (const auto *name{std::get_if<parser::Name>(&dataRef->u)}) {
-                  CheckPredefinedAllocatorRestriction(source, *name);
-                }
-              }
-            },
-            [&](const parser::Name &name) {
-              CheckPredefinedAllocatorRestriction(source, name);
-            },
-            [&](const parser::OmpObject::Invalid &invalid) {},
-        },
-        ompObject.u);
-  }
-}
-
 void OmpStructureChecker::Enter(const parser::OmpClause::Hint &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_hint);
   auto &dirCtx{GetContext()};
@@ -1710,12 +1667,51 @@ void OmpStructureChecker::Leave(const parser::OpenMPRequiresConstruct &) {
   dirContext_.pop_back();
 }
 
+static std::pair<const parser::AllocateStmt *, parser::CharBlock>
+getAllocateStmtAndSource(const parser::Statement<parser::AllocateStmt> &stmt) {
+  return {&stmt.statement, stmt.source};
+}
+
+static std::pair<const parser::AllocateStmt *, parser::CharBlock>
+getAllocateStmtAndSource(const parser::ExecutionPartConstruct *epc) {
+  if (SourcedActionStmt as{GetActionStmt(epc)}) {
+    using IndirectionAllocateStmt = common::Indirection<parser::AllocateStmt>;
+    if (auto *indirect{std::get_if<IndirectionAllocateStmt>(&as.stmt->u)}) {
+      return {&indirect->value(), as.source};
+    }
+  }
+  return {nullptr, ""};
+}
+
+// Collect symbols that correspond to non-component objects on the
+// ALLOCATE statement.
+static UnorderedSymbolSet GetNonComponentSymbols(
+    const parser::AllocateStmt &stmt) {
+  UnorderedSymbolSet symbols;
+  for (auto &alloc : std::get<std::list<parser::Allocation>>(stmt.t)) {
+    auto &object{std::get<parser::AllocateObject>(alloc.t)};
+    if (auto *name{std::get_if<parser::Name>(&object.u)}) {
+      if (name->symbol) {
+        symbols.insert(name->symbol->GetUltimate());
+      }
+    }
+  }
+  return symbols;
+}
+
+static const parser::OmpObjectList &GetObjectsOrEmpty(
+    const std::optional<parser::OmpObjectList> &maybeObjects) {
+  static parser::OmpObjectList empty{std::list<parser::OmpObject>{}};
+  if (maybeObjects) {
+    return *maybeObjects;
+  }
+  return empty;
+}
+
 void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
     const parser::OmpObjectList &objects,
     const parser::OmpClauseList &clauses) {
   const Scope &thisScope{context_.FindScope(source)};
-  SymbolSourceMap symbols;
-  GetSymbolsInObjectList(objects, symbols);
 
   auto maybeHasPredefinedAllocator{[&](const parser::OmpClause *calloc) {
     // Return "true" if the ALLOCATOR clause was provided with an argument
@@ -1741,7 +1737,17 @@ void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
     return true;
   }};
 
-  const auto *allocator{FindClause(llvm::omp::Clause::OMPC_allocator)};
+  const auto *allocator{[&]() {
+    // Can't use FindClause in Enter (because clauses haven't been visited
+    // yet).
+    for (const parser::OmpClause &c : clauses.v) {
+      if (c.Id() == llvm::omp::Clause::OMPC_allocator) {
+        return &c;
+      }
+    }
+    return static_cast<const parser::OmpClause *>(nullptr);
+  }()};
+
   if (InTargetRegion()) {
     bool hasDynAllocators{
         HasRequires(llvm::omp::Clause::OMPC_dynamic_allocators)};
@@ -1753,61 +1759,85 @@ void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
 
   auto maybePredefined{maybeHasPredefinedAllocator(allocator)};
 
-  for (auto &[symbol, source] : symbols) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  std::string condStr{version == 50
+          ? "a named common block, has SAVE attribute or is declared in the "
+            "scope of a module"
+          : "a named common block or has SAVE attribute"};
+
+  auto checkSymbol{[&](const Symbol &symbol, parser::CharBlock source) {
     if (!inExecutableAllocate_) {
-      if (symbol->owner() != thisScope) {
+      // For structure members, the scope is the derived type, which is
+      // never "this" scope. Ignore this check for members, they will be
+      // flagged anyway.
+      if (symbol.owner() != thisScope && !IsStructureComponent(symbol)) {
         context_.Say(source,
             "A list item on a declarative ALLOCATE must be declared in the same scope in which the directive appears"_err_en_US);
       }
-      if (IsPointer(*symbol) || IsAllocatable(*symbol)) {
+      if (IsPointer(symbol) || IsAllocatable(symbol)) {
         context_.Say(source,
             "A list item in a declarative ALLOCATE cannot have the ALLOCATABLE or POINTER attribute"_err_en_US);
       }
     }
-    if (symbol->GetUltimate().has<AssocEntityDetails>()) {
+    if (symbol.GetUltimate().has<AssocEntityDetails>()) {
       context_.Say(source,
           "A list item in a declarative ALLOCATE cannot be an associate name"_err_en_US);
     }
-    if (symbol->attrs().test(Attr::SAVE) || IsCommonBlock(*symbol)) {
+    bool inModule{
+        version == 50 && symbol.owner().kind() == Scope::Kind::Module};
+    if (symbol.attrs().test(Attr::SAVE) || IsCommonBlock(symbol) || inModule) {
       if (!allocator) {
         context_.Say(source,
-            "If a list item is a named common block or has SAVE attribute, an ALLOCATOR clause must be present with a predefined allocator"_err_en_US);
+            "If a list item is %s, an ALLOCATOR clause must be present with a predefined allocator"_err_en_US,
+            condStr);
       } else if (!maybePredefined) {
         context_.Say(source,
-            "If a list item is a named common block or has SAVE attribute, only a predefined allocator may be used on the ALLOCATOR clause"_err_en_US);
+            "If a list item is %s, only a predefined allocator may be used on the ALLOCATOR clause"_err_en_US,
+            condStr);
       }
     }
-    if (FindCommonBlockContaining(*symbol)) {
+    if (FindCommonBlockContaining(symbol)) {
       context_.Say(source,
           "A variable that is part of a common block may not be specified as a list item in an ALLOCATE directive, except implicitly via the named common block"_err_en_US);
     }
+  }};
+
+  for (const parser::OmpObject &object : objects.v) {
+    parser::CharBlock objSource{[&]() {
+      if (auto &&maybeSource{GetObjectSource(object)}) {
+        return *maybeSource;
+      }
+      return source;
+    }()};
+    if (const Symbol *symbol{GetObjectSymbol(object)}) {
+      if (!IsTypeParamInquiry(*symbol)) {
+        checkSymbol(*symbol, objSource);
+      }
+      CheckVarIsNotPartOfAnotherVar(source, object);
+    }
   }
-  CheckVarIsNotPartOfAnotherVar(source, objects);
 }
 
 void OmpStructureChecker::Enter(const parser::OpenMPDeclarativeAllocate &x) {
   const auto &dir{std::get<parser::Verbatim>(x.t)};
   PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_allocate);
-}
 
-void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAllocate &x) {
   if (!inExecutableAllocate_) {
     const auto &dir{std::get<parser::Verbatim>(x.t)};
-    const auto &clauseList{std::get<parser::OmpClauseList>(x.t)};
-    const auto &objectList{std::get<parser::OmpObjectList>(x.t)};
+    const auto &clauses{std::get<parser::OmpClauseList>(x.t)};
+    const auto &objects{
+        GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
 
-    isPredefinedAllocator = true;
-    CheckAllocateDirective(dir.source, objectList, clauseList);
+    CheckAllocateDirective(dir.source, objects, clauses);
   }
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAllocate &x) {
   dirContext_.pop_back();
 }
 
 void OmpStructureChecker::Enter(const parser::OmpClause::Allocator &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_allocator);
-  // Note: Predefined allocators are stored in ScalarExpr as numbers
-  //   whereas custom allocators are stored as strings, so if the ScalarExpr
-  //   actually has an int value, then it must be a predefined allocator
-  isPredefinedAllocator = GetIntValue(x.v).has_value();
   RequiresPositiveParameter(llvm::omp::Clause::OMPC_allocator, x.v);
 }
 
@@ -1823,16 +1853,6 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Allocate &x) {
             "The alignment value should be a constant positive integer"_err_en_US);
       }
     }
-    // The simple and complex modifiers have the same structure. They only
-    // differ in their syntax.
-    if (auto *alloc{OmpGetUniqueModifier<parser::OmpAllocatorComplexModifier>(
-            modifiers)}) {
-      isPredefinedAllocator = GetIntValue(alloc->v).has_value();
-    }
-    if (auto *alloc{OmpGetUniqueModifier<parser::OmpAllocatorSimpleModifier>(
-            modifiers)}) {
-      isPredefinedAllocator = GetIntValue(alloc->v).has_value();
-    }
   }
 }
 
@@ -2115,44 +2135,6 @@ void OmpStructureChecker::Enter(const parser::OmpClause::At &x) {
   }
 }
 
-// Goes through the names in an OmpObjectList and checks if each name appears
-// in the given allocate statement
-void OmpStructureChecker::CheckAllNamesInAllocateStmt(
-    const parser::CharBlock &source, const parser::OmpObjectList &ompObjectList,
-    const parser::AllocateStmt &allocate) {
-  for (const auto &obj : ompObjectList.v) {
-    if (const auto *d{std::get_if<parser::Designator>(&obj.u)}) {
-      if (const auto *ref{std::get_if<parser::DataRef>(&d->u)}) {
-        if (const auto *n{std::get_if<parser::Name>(&ref->u)}) {
-          CheckNameInAllocateStmt(source, *n, allocate);
-        }
-      }
-    }
-  }
-}
-
-void OmpStructureChecker::CheckNameInAllocateStmt(
-    const parser::CharBlock &source, const parser::Name &name,
-    const parser::AllocateStmt &allocate) {
-  for (const auto &allocation :
-      std::get<std::list<parser::Allocation>>(allocate.t)) {
-    const auto &allocObj = std::get<parser::AllocateObject>(allocation.t);
-    if (const auto *n{std::get_if<parser::Name>(&allocObj.u)}) {
-      if (n->source == name.source) {
-        return;
-      }
-    }
-  }
-  unsigned version{context_.langOptions().OpenMPVersion};
-  context_.Say(source,
-      "Object '%s' in %s directive not "
-      "found in corresponding ALLOCATE statement"_err_en_US,
-      name.ToString(),
-      parser::ToUpperCaseLetters(
-          llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
-              .str()));
-}
-
 void OmpStructureChecker::Enter(const parser::OpenMPExecutableAllocate &x) {
   inExecutableAllocate_ = true;
   const auto &dir{std::get<parser::Verbatim>(x.t)};
@@ -2164,34 +2146,10 @@ void OmpStructureChecker::Enter(const parser::OpenMPExecutableAllocate &x) {
         "The executable form of the OpenMP ALLOCATE directive has been deprecated, please use ALLOCATORS instead"_warn_en_US);
   }
 
-  const auto &allocateStmt =
-      std::get<parser::Statement<parser::AllocateStmt>>(x.t).statement;
-  if (const auto &list{std::get<std::optional<parser::OmpObjectList>>(x.t)}) {
-    CheckAllNamesInAllocateStmt(
-        std::get<parser::Verbatim>(x.t).source, *list, allocateStmt);
-  }
-  if (const auto &subDirs{
-          std::get<std::optional<std::list<parser::OpenMPDeclarativeAllocate>>>(
-              x.t)}) {
-    for (const auto &dalloc : *subDirs) {
-      CheckAllNamesInAllocateStmt(std::get<parser::Verbatim>(dalloc.t).source,
-          std::get<parser::OmpObjectList>(dalloc.t), allocateStmt);
-    }
-  }
-
-  isPredefinedAllocator = true;
-}
-
-void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
-  parser::OmpObjectList empty{std::list<parser::OmpObject>{}};
-  auto &objects{[&]() -> const parser::OmpObjectList & {
-    if (auto &objects{std::get<std::optional<parser::OmpObjectList>>(x.t)}) {
-      return *objects;
-    } else {
-      return empty;
-    }
-  }()};
+  auto &objects{
+      GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
   auto &clauses{std::get<parser::OmpClauseList>(x.t)};
+
   CheckAllocateDirective(
       std::get<parser::Verbatim>(x.t).source, objects, clauses);
 
@@ -2201,82 +2159,171 @@ void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
     for (const auto &dalloc : *subDirs) {
       const auto &dir{std::get<parser::Verbatim>(x.t)};
       const auto &clauses{std::get<parser::OmpClauseList>(dalloc.t)};
-      const auto &objects{std::get<parser::OmpObjectList>(dalloc.t)};
+      const auto &objects{GetObjectsOrEmpty(
+          std::get<std::optional<parser::OmpObjectList>>(dalloc.t))};
       CheckAllocateDirective(dir.source, objects, clauses);
     }
   }
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
+  auto [allocStmt, allocSource]{getAllocateStmtAndSource(
+      std::get<parser::Statement<parser::AllocateStmt>>(x.t))};
+
+  UnorderedSymbolSet allocateSyms{GetNonComponentSymbols(*allocStmt)};
+  SymbolSourceMap directiveSyms;
+  auto &objects{
+      GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
+  auto emptyListCount{static_cast<size_t>(objects.v.empty())};
+  auto checkObjects{[&](const parser::OmpObjectList &objects,
+                        parser::CharBlock dirSource,
+                        parser::CharBlock allocSource) {
+    for (const parser::OmpObject &object : objects.v) {
+      parser::CharBlock objSource{[&]() {
+        if (auto &&maybeSource{GetObjectSource(object)}) {
+          return *maybeSource;
+        }
+        return dirSource;
+      }()};
+      if (auto *sym{GetObjectSymbol(object)}) {
+        // Ignore these checks for structure members. They are not allowed
+        // in the first place, so don't tell the users that they nened to
+        // be specified somewhere,
+        if (IsStructureComponent(*sym)) {
+          continue;
+        }
+        if (auto f{directiveSyms.find(sym)}; f != directiveSyms.end()) {
+          parser::MessageFormattedText txt(
+              "A list item on an executable ALLOCATE may only be specified once"_err_en_US);
+          parser::Message message(objSource, txt);
+          message.Attach(f->second, "The list item was specified here"_en_US);
+          context_.Say(std::move(message));
+        } else {
+          directiveSyms.insert(std::make_pair(sym, objSource));
+        }
+
+        if (auto f{allocateSyms.find(*sym)}; f == allocateSyms.end()) {
+          context_
+              .Say(objSource,
+                  "A list item on an executable ALLOCATE must be specified on the associated ALLOCATE statement"_err_en_US)
+              .Attach(allocSource, "The ALLOCATE statement"_en_US);
+        }
+      }
+    }
+  }};
+
+  checkObjects(objects, std::get<parser::Verbatim>(x.t).source, allocSource);
+
+  const auto &subDirs{
+      std::get<std::optional<std::list<parser::OpenMPDeclarativeAllocate>>>(
+          x.t)};
+  if (!subDirs) {
+    inExecutableAllocate_ = false;
+    dirContext_.pop_back();
+    return;
+  }
+
+  for (const parser::OpenMPDeclarativeAllocate &ompAlloc : *subDirs) {
+    parser::CharBlock dirSource{std::get<parser::Verbatim>(ompAlloc.t).source};
+    auto &objects{GetObjectsOrEmpty(
+        std::get<std::optional<parser::OmpObjectList>>(ompAlloc.t))};
+    if (objects.v.empty()) {
+      // Only show the message once per construct.
+      if (++emptyListCount == 2 && subDirs->size() >= 1) {
+        context_.Say(dirSource,
+            "If multiple directives are present in an executable ALLOCATE directive, at most one of them may specify no list items"_err_en_US);
+      }
+    }
+    checkObjects(objects, dirSource, allocSource);
+  }
 
-  dirContext_.pop_back();
   inExecutableAllocate_ = false;
+  dirContext_.pop_back();
 }
 
 void Om...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Oct 30, 2025

@llvm/pr-subscribers-flang-openmp

Author: Krzysztof Parzyszek (kparzysz)

Changes

For ALLOCATORS and executable ALLOCATE first perform list item checks in the context of an individual ALLOCATE clause or directive respectively, then perform "global" checks, e.g. whether all list items are present on the ALLOCATE statement.

These changes allowed to simplify the checks for presence on ALLOCATE statement and the use of a predefined allocator.

Additionally, allow variable list item lists to be empty, add a test for the related spec restriction.

This is a first step towards unifying OpenMPDeclarativeAllocate and OpenMPExecutableAllocate into a single directive.


Patch is 36.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165719.diff

16 Files Affected:

  • (modified) flang/include/flang/Parser/parse-tree.h (+1-1)
  • (modified) flang/include/flang/Semantics/openmp-utils.h (+2)
  • (modified) flang/lib/Parser/openmp-parsers.cpp (+8-6)
  • (modified) flang/lib/Parser/unparse.cpp (+1-1)
  • (modified) flang/lib/Semantics/check-omp-structure.cpp (+241-194)
  • (modified) flang/lib/Semantics/check-omp-structure.h (-10)
  • (modified) flang/lib/Semantics/openmp-utils.cpp (+17)
  • (modified) flang/lib/Semantics/resolve-directives.cpp (+3-2)
  • (modified) flang/test/Semantics/OpenMP/allocate08.f90 (+4-4)
  • (modified) flang/test/Semantics/OpenMP/allocate09.f90 (+2-2)
  • (added) flang/test/Semantics/OpenMP/allocate10.f90 (+11)
  • (added) flang/test/Semantics/OpenMP/allocate11.f90 (+27)
  • (modified) flang/test/Semantics/OpenMP/allocators01.f90 (+1-1)
  • (removed) flang/test/Semantics/OpenMP/allocators04.f90 (-31)
  • (modified) flang/test/Semantics/OpenMP/allocators05.f90 (+1-1)
  • (modified) flang/test/Semantics/OpenMP/allocators07.f90 (+3-3)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 375790af90b74..4dd5e84f60dfe 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5155,7 +5155,7 @@ struct OpenMPThreadprivate {
 struct OpenMPDeclarativeAllocate {
   TUPLE_CLASS_BOILERPLATE(OpenMPDeclarativeAllocate);
   CharBlock source;
-  std::tuple<Verbatim, OmpObjectList, OmpClauseList> t;
+  std::tuple<Verbatim, std::optional<OmpObjectList>, OmpClauseList> t;
 };
 
 struct OpenMPDeclarativeConstruct {
diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 032944d8be370..14a4f0e93bda5 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -72,6 +72,8 @@ const parser::OmpObject *GetArgumentObject(const parser::OmpArgument &argument);
 bool IsCommonBlock(const Symbol &sym);
 bool IsExtendedListItem(const Symbol &sym);
 bool IsVariableListItem(const Symbol &sym);
+bool IsTypeParamInquiry(const Symbol &sym);
+bool IsStructureComponent(const Symbol &sym);
 bool IsVarOrFunctionRef(const MaybeExpr &expr);
 
 bool IsMapEnteringType(parser::OmpMapType::Value type);
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 4159d2e41b78c..a9de26ea09ff8 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2045,11 +2045,12 @@ TYPE_PARSER(sourced(construct<OpenMPCriticalConstruct>(
     OmpBlockConstructParser{llvm::omp::Directive::OMPD_critical})))
 
 // 2.11.3 Executable Allocate directive
-TYPE_PARSER(
-    sourced(construct<OpenMPExecutableAllocate>(verbatim("ALLOCATE"_tok),
-        maybe(parenthesized(Parser<OmpObjectList>{})), Parser<OmpClauseList>{},
-        maybe(nonemptyList(Parser<OpenMPDeclarativeAllocate>{})) / endOmpLine,
-        statement(allocateStmt))))
+TYPE_PARSER(sourced(construct<OpenMPExecutableAllocate>(
+    verbatim("ALLOCATE"_tok), maybe(parenthesized(Parser<OmpObjectList>{})),
+    Parser<OmpClauseList>{},
+    maybe(nonemptyList(startOmpLine >> Parser<OpenMPDeclarativeAllocate>{})) /
+        endOmpLine,
+    statement(allocateStmt))))
 
 // 2.8.2 Declare Simd construct
 TYPE_PARSER(sourced(construct<OpenMPDeclareSimdConstruct>(
@@ -2079,7 +2080,8 @@ TYPE_PARSER(sourced( //
 // 2.11.3 Declarative Allocate directive
 TYPE_PARSER(
     sourced(construct<OpenMPDeclarativeAllocate>(verbatim("ALLOCATE"_tok),
-        parenthesized(Parser<OmpObjectList>{}), Parser<OmpClauseList>{})) /
+        maybe(parenthesized(Parser<OmpObjectList>{})),
+        Parser<OmpClauseList>{})) /
     lookAhead(endOmpLine / !statement(allocateStmt)))
 
 // Assumes Construct
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 9b38cfc40c5b2..b3a395c4d72e1 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2503,7 +2503,7 @@ class UnparseVisitor {
     BeginOpenMP();
     Word("!$OMP ALLOCATE");
     Put(" (");
-    Walk(std::get<OmpObjectList>(x.t));
+    Walk(std::get<std::optional<OmpObjectList>>(x.t));
     Put(")");
     Walk(std::get<OmpClauseList>(x.t));
     Put("\n");
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index aaaf1ec5d4626..ece179c4a66fc 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -667,49 +667,6 @@ void OmpStructureChecker::HasInvalidTeamsNesting(
   }
 }
 
-void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
-    const parser::CharBlock &source, const parser::Name &name) {
-  if (const auto *symbol{name.symbol}) {
-    const auto *commonBlock{FindCommonBlockContaining(*symbol)};
-    const auto &scope{context_.FindScope(symbol->name())};
-    const Scope &containingScope{GetProgramUnitContaining(scope)};
-    if (!isPredefinedAllocator &&
-        (IsSaved(*symbol) || commonBlock ||
-            containingScope.kind() == Scope::Kind::Module)) {
-      context_.Say(source,
-          "If list items within the %s directive have the "
-          "SAVE attribute, are a common block name, or are "
-          "declared in the scope of a module, then only "
-          "predefined memory allocator parameters can be used "
-          "in the allocator clause"_err_en_US,
-          ContextDirectiveAsFortran());
-    }
-  }
-}
-
-void OmpStructureChecker::CheckPredefinedAllocatorRestriction(
-    const parser::CharBlock &source,
-    const parser::OmpObjectList &ompObjectList) {
-  for (const auto &ompObject : ompObjectList.v) {
-    common::visit(
-        common::visitors{
-            [&](const parser::Designator &designator) {
-              if (const auto *dataRef{
-                      std::get_if<parser::DataRef>(&designator.u)}) {
-                if (const auto *name{std::get_if<parser::Name>(&dataRef->u)}) {
-                  CheckPredefinedAllocatorRestriction(source, *name);
-                }
-              }
-            },
-            [&](const parser::Name &name) {
-              CheckPredefinedAllocatorRestriction(source, name);
-            },
-            [&](const parser::OmpObject::Invalid &invalid) {},
-        },
-        ompObject.u);
-  }
-}
-
 void OmpStructureChecker::Enter(const parser::OmpClause::Hint &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_hint);
   auto &dirCtx{GetContext()};
@@ -1710,12 +1667,51 @@ void OmpStructureChecker::Leave(const parser::OpenMPRequiresConstruct &) {
   dirContext_.pop_back();
 }
 
+static std::pair<const parser::AllocateStmt *, parser::CharBlock>
+getAllocateStmtAndSource(const parser::Statement<parser::AllocateStmt> &stmt) {
+  return {&stmt.statement, stmt.source};
+}
+
+static std::pair<const parser::AllocateStmt *, parser::CharBlock>
+getAllocateStmtAndSource(const parser::ExecutionPartConstruct *epc) {
+  if (SourcedActionStmt as{GetActionStmt(epc)}) {
+    using IndirectionAllocateStmt = common::Indirection<parser::AllocateStmt>;
+    if (auto *indirect{std::get_if<IndirectionAllocateStmt>(&as.stmt->u)}) {
+      return {&indirect->value(), as.source};
+    }
+  }
+  return {nullptr, ""};
+}
+
+// Collect symbols that correspond to non-component objects on the
+// ALLOCATE statement.
+static UnorderedSymbolSet GetNonComponentSymbols(
+    const parser::AllocateStmt &stmt) {
+  UnorderedSymbolSet symbols;
+  for (auto &alloc : std::get<std::list<parser::Allocation>>(stmt.t)) {
+    auto &object{std::get<parser::AllocateObject>(alloc.t)};
+    if (auto *name{std::get_if<parser::Name>(&object.u)}) {
+      if (name->symbol) {
+        symbols.insert(name->symbol->GetUltimate());
+      }
+    }
+  }
+  return symbols;
+}
+
+static const parser::OmpObjectList &GetObjectsOrEmpty(
+    const std::optional<parser::OmpObjectList> &maybeObjects) {
+  static parser::OmpObjectList empty{std::list<parser::OmpObject>{}};
+  if (maybeObjects) {
+    return *maybeObjects;
+  }
+  return empty;
+}
+
 void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
     const parser::OmpObjectList &objects,
     const parser::OmpClauseList &clauses) {
   const Scope &thisScope{context_.FindScope(source)};
-  SymbolSourceMap symbols;
-  GetSymbolsInObjectList(objects, symbols);
 
   auto maybeHasPredefinedAllocator{[&](const parser::OmpClause *calloc) {
     // Return "true" if the ALLOCATOR clause was provided with an argument
@@ -1741,7 +1737,17 @@ void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
     return true;
   }};
 
-  const auto *allocator{FindClause(llvm::omp::Clause::OMPC_allocator)};
+  const auto *allocator{[&]() {
+    // Can't use FindClause in Enter (because clauses haven't been visited
+    // yet).
+    for (const parser::OmpClause &c : clauses.v) {
+      if (c.Id() == llvm::omp::Clause::OMPC_allocator) {
+        return &c;
+      }
+    }
+    return static_cast<const parser::OmpClause *>(nullptr);
+  }()};
+
   if (InTargetRegion()) {
     bool hasDynAllocators{
         HasRequires(llvm::omp::Clause::OMPC_dynamic_allocators)};
@@ -1753,61 +1759,85 @@ void OmpStructureChecker::CheckAllocateDirective(parser::CharBlock source,
 
   auto maybePredefined{maybeHasPredefinedAllocator(allocator)};
 
-  for (auto &[symbol, source] : symbols) {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  std::string condStr{version == 50
+          ? "a named common block, has SAVE attribute or is declared in the "
+            "scope of a module"
+          : "a named common block or has SAVE attribute"};
+
+  auto checkSymbol{[&](const Symbol &symbol, parser::CharBlock source) {
     if (!inExecutableAllocate_) {
-      if (symbol->owner() != thisScope) {
+      // For structure members, the scope is the derived type, which is
+      // never "this" scope. Ignore this check for members, they will be
+      // flagged anyway.
+      if (symbol.owner() != thisScope && !IsStructureComponent(symbol)) {
         context_.Say(source,
             "A list item on a declarative ALLOCATE must be declared in the same scope in which the directive appears"_err_en_US);
       }
-      if (IsPointer(*symbol) || IsAllocatable(*symbol)) {
+      if (IsPointer(symbol) || IsAllocatable(symbol)) {
         context_.Say(source,
             "A list item in a declarative ALLOCATE cannot have the ALLOCATABLE or POINTER attribute"_err_en_US);
       }
     }
-    if (symbol->GetUltimate().has<AssocEntityDetails>()) {
+    if (symbol.GetUltimate().has<AssocEntityDetails>()) {
       context_.Say(source,
           "A list item in a declarative ALLOCATE cannot be an associate name"_err_en_US);
     }
-    if (symbol->attrs().test(Attr::SAVE) || IsCommonBlock(*symbol)) {
+    bool inModule{
+        version == 50 && symbol.owner().kind() == Scope::Kind::Module};
+    if (symbol.attrs().test(Attr::SAVE) || IsCommonBlock(symbol) || inModule) {
       if (!allocator) {
         context_.Say(source,
-            "If a list item is a named common block or has SAVE attribute, an ALLOCATOR clause must be present with a predefined allocator"_err_en_US);
+            "If a list item is %s, an ALLOCATOR clause must be present with a predefined allocator"_err_en_US,
+            condStr);
       } else if (!maybePredefined) {
         context_.Say(source,
-            "If a list item is a named common block or has SAVE attribute, only a predefined allocator may be used on the ALLOCATOR clause"_err_en_US);
+            "If a list item is %s, only a predefined allocator may be used on the ALLOCATOR clause"_err_en_US,
+            condStr);
       }
     }
-    if (FindCommonBlockContaining(*symbol)) {
+    if (FindCommonBlockContaining(symbol)) {
       context_.Say(source,
           "A variable that is part of a common block may not be specified as a list item in an ALLOCATE directive, except implicitly via the named common block"_err_en_US);
     }
+  }};
+
+  for (const parser::OmpObject &object : objects.v) {
+    parser::CharBlock objSource{[&]() {
+      if (auto &&maybeSource{GetObjectSource(object)}) {
+        return *maybeSource;
+      }
+      return source;
+    }()};
+    if (const Symbol *symbol{GetObjectSymbol(object)}) {
+      if (!IsTypeParamInquiry(*symbol)) {
+        checkSymbol(*symbol, objSource);
+      }
+      CheckVarIsNotPartOfAnotherVar(source, object);
+    }
   }
-  CheckVarIsNotPartOfAnotherVar(source, objects);
 }
 
 void OmpStructureChecker::Enter(const parser::OpenMPDeclarativeAllocate &x) {
   const auto &dir{std::get<parser::Verbatim>(x.t)};
   PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_allocate);
-}
 
-void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAllocate &x) {
   if (!inExecutableAllocate_) {
     const auto &dir{std::get<parser::Verbatim>(x.t)};
-    const auto &clauseList{std::get<parser::OmpClauseList>(x.t)};
-    const auto &objectList{std::get<parser::OmpObjectList>(x.t)};
+    const auto &clauses{std::get<parser::OmpClauseList>(x.t)};
+    const auto &objects{
+        GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
 
-    isPredefinedAllocator = true;
-    CheckAllocateDirective(dir.source, objectList, clauseList);
+    CheckAllocateDirective(dir.source, objects, clauses);
   }
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPDeclarativeAllocate &x) {
   dirContext_.pop_back();
 }
 
 void OmpStructureChecker::Enter(const parser::OmpClause::Allocator &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_allocator);
-  // Note: Predefined allocators are stored in ScalarExpr as numbers
-  //   whereas custom allocators are stored as strings, so if the ScalarExpr
-  //   actually has an int value, then it must be a predefined allocator
-  isPredefinedAllocator = GetIntValue(x.v).has_value();
   RequiresPositiveParameter(llvm::omp::Clause::OMPC_allocator, x.v);
 }
 
@@ -1823,16 +1853,6 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Allocate &x) {
             "The alignment value should be a constant positive integer"_err_en_US);
       }
     }
-    // The simple and complex modifiers have the same structure. They only
-    // differ in their syntax.
-    if (auto *alloc{OmpGetUniqueModifier<parser::OmpAllocatorComplexModifier>(
-            modifiers)}) {
-      isPredefinedAllocator = GetIntValue(alloc->v).has_value();
-    }
-    if (auto *alloc{OmpGetUniqueModifier<parser::OmpAllocatorSimpleModifier>(
-            modifiers)}) {
-      isPredefinedAllocator = GetIntValue(alloc->v).has_value();
-    }
   }
 }
 
@@ -2115,44 +2135,6 @@ void OmpStructureChecker::Enter(const parser::OmpClause::At &x) {
   }
 }
 
-// Goes through the names in an OmpObjectList and checks if each name appears
-// in the given allocate statement
-void OmpStructureChecker::CheckAllNamesInAllocateStmt(
-    const parser::CharBlock &source, const parser::OmpObjectList &ompObjectList,
-    const parser::AllocateStmt &allocate) {
-  for (const auto &obj : ompObjectList.v) {
-    if (const auto *d{std::get_if<parser::Designator>(&obj.u)}) {
-      if (const auto *ref{std::get_if<parser::DataRef>(&d->u)}) {
-        if (const auto *n{std::get_if<parser::Name>(&ref->u)}) {
-          CheckNameInAllocateStmt(source, *n, allocate);
-        }
-      }
-    }
-  }
-}
-
-void OmpStructureChecker::CheckNameInAllocateStmt(
-    const parser::CharBlock &source, const parser::Name &name,
-    const parser::AllocateStmt &allocate) {
-  for (const auto &allocation :
-      std::get<std::list<parser::Allocation>>(allocate.t)) {
-    const auto &allocObj = std::get<parser::AllocateObject>(allocation.t);
-    if (const auto *n{std::get_if<parser::Name>(&allocObj.u)}) {
-      if (n->source == name.source) {
-        return;
-      }
-    }
-  }
-  unsigned version{context_.langOptions().OpenMPVersion};
-  context_.Say(source,
-      "Object '%s' in %s directive not "
-      "found in corresponding ALLOCATE statement"_err_en_US,
-      name.ToString(),
-      parser::ToUpperCaseLetters(
-          llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
-              .str()));
-}
-
 void OmpStructureChecker::Enter(const parser::OpenMPExecutableAllocate &x) {
   inExecutableAllocate_ = true;
   const auto &dir{std::get<parser::Verbatim>(x.t)};
@@ -2164,34 +2146,10 @@ void OmpStructureChecker::Enter(const parser::OpenMPExecutableAllocate &x) {
         "The executable form of the OpenMP ALLOCATE directive has been deprecated, please use ALLOCATORS instead"_warn_en_US);
   }
 
-  const auto &allocateStmt =
-      std::get<parser::Statement<parser::AllocateStmt>>(x.t).statement;
-  if (const auto &list{std::get<std::optional<parser::OmpObjectList>>(x.t)}) {
-    CheckAllNamesInAllocateStmt(
-        std::get<parser::Verbatim>(x.t).source, *list, allocateStmt);
-  }
-  if (const auto &subDirs{
-          std::get<std::optional<std::list<parser::OpenMPDeclarativeAllocate>>>(
-              x.t)}) {
-    for (const auto &dalloc : *subDirs) {
-      CheckAllNamesInAllocateStmt(std::get<parser::Verbatim>(dalloc.t).source,
-          std::get<parser::OmpObjectList>(dalloc.t), allocateStmt);
-    }
-  }
-
-  isPredefinedAllocator = true;
-}
-
-void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
-  parser::OmpObjectList empty{std::list<parser::OmpObject>{}};
-  auto &objects{[&]() -> const parser::OmpObjectList & {
-    if (auto &objects{std::get<std::optional<parser::OmpObjectList>>(x.t)}) {
-      return *objects;
-    } else {
-      return empty;
-    }
-  }()};
+  auto &objects{
+      GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
   auto &clauses{std::get<parser::OmpClauseList>(x.t)};
+
   CheckAllocateDirective(
       std::get<parser::Verbatim>(x.t).source, objects, clauses);
 
@@ -2201,82 +2159,171 @@ void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
     for (const auto &dalloc : *subDirs) {
       const auto &dir{std::get<parser::Verbatim>(x.t)};
       const auto &clauses{std::get<parser::OmpClauseList>(dalloc.t)};
-      const auto &objects{std::get<parser::OmpObjectList>(dalloc.t)};
+      const auto &objects{GetObjectsOrEmpty(
+          std::get<std::optional<parser::OmpObjectList>>(dalloc.t))};
       CheckAllocateDirective(dir.source, objects, clauses);
     }
   }
+}
+
+void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
+  auto [allocStmt, allocSource]{getAllocateStmtAndSource(
+      std::get<parser::Statement<parser::AllocateStmt>>(x.t))};
+
+  UnorderedSymbolSet allocateSyms{GetNonComponentSymbols(*allocStmt)};
+  SymbolSourceMap directiveSyms;
+  auto &objects{
+      GetObjectsOrEmpty(std::get<std::optional<parser::OmpObjectList>>(x.t))};
+  auto emptyListCount{static_cast<size_t>(objects.v.empty())};
+  auto checkObjects{[&](const parser::OmpObjectList &objects,
+                        parser::CharBlock dirSource,
+                        parser::CharBlock allocSource) {
+    for (const parser::OmpObject &object : objects.v) {
+      parser::CharBlock objSource{[&]() {
+        if (auto &&maybeSource{GetObjectSource(object)}) {
+          return *maybeSource;
+        }
+        return dirSource;
+      }()};
+      if (auto *sym{GetObjectSymbol(object)}) {
+        // Ignore these checks for structure members. They are not allowed
+        // in the first place, so don't tell the users that they nened to
+        // be specified somewhere,
+        if (IsStructureComponent(*sym)) {
+          continue;
+        }
+        if (auto f{directiveSyms.find(sym)}; f != directiveSyms.end()) {
+          parser::MessageFormattedText txt(
+              "A list item on an executable ALLOCATE may only be specified once"_err_en_US);
+          parser::Message message(objSource, txt);
+          message.Attach(f->second, "The list item was specified here"_en_US);
+          context_.Say(std::move(message));
+        } else {
+          directiveSyms.insert(std::make_pair(sym, objSource));
+        }
+
+        if (auto f{allocateSyms.find(*sym)}; f == allocateSyms.end()) {
+          context_
+              .Say(objSource,
+                  "A list item on an executable ALLOCATE must be specified on the associated ALLOCATE statement"_err_en_US)
+              .Attach(allocSource, "The ALLOCATE statement"_en_US);
+        }
+      }
+    }
+  }};
+
+  checkObjects(objects, std::get<parser::Verbatim>(x.t).source, allocSource);
+
+  const auto &subDirs{
+      std::get<std::optional<std::list<parser::OpenMPDeclarativeAllocate>>>(
+          x.t)};
+  if (!subDirs) {
+    inExecutableAllocate_ = false;
+    dirContext_.pop_back();
+    return;
+  }
+
+  for (const parser::OpenMPDeclarativeAllocate &ompAlloc : *subDirs) {
+    parser::CharBlock dirSource{std::get<parser::Verbatim>(ompAlloc.t).source};
+    auto &objects{GetObjectsOrEmpty(
+        std::get<std::optional<parser::OmpObjectList>>(ompAlloc.t))};
+    if (objects.v.empty()) {
+      // Only show the message once per construct.
+      if (++emptyListCount == 2 && subDirs->size() >= 1) {
+        context_.Say(dirSource,
+            "If multiple directives are present in an executable ALLOCATE directive, at most one of them may specify no list items"_err_en_US);
+      }
+    }
+    checkObjects(objects, dirSource, allocSource);
+  }
 
-  dirContext_.pop_back();
   inExecutableAllocate_ = false;
+  dirContext_.pop_back();
 }
 
 void Om...
[truncated]

@kparzysz kparzysz requested review from Stylie777 and tblah October 30, 2025 13:45
@kparzysz
Copy link
Contributor Author

The executable ALLOCATE is unique in the sense that the construct consists of several OpenMP directives, plus a Fortran ALLOCATE statement. All of the constituent directives have the same syntax, e.g.

!$omp allocate(x, y, z)
!$omp allocate(a, b, c)
allocate(x, y, z, a, b, c)

Internally, OpenMPExecutableAllocate has the list of objects and clauses that correspond to the first directive, while the other directives are stored as OpenMPDeclarativeAllocate's. The latter has a typical structure, i.e. a single directive with arguments and clauses. This means that even though the constituent directives have the same syntax, and are subject to the same restrictions, internally the first one is represented differently from the other ones. This requires two pieces of code to verify the restrictions on the executable ALLOCATE. In this patch, both use CheckAllocateDirective. These checks are done in the Enter function.

Additionally, the executable ALLOCATE imposes restrictions on the construct as a whole: those are checked in the Leave function.

The ALLOCATORS construct is the replacement of the executable ALLOCATE, starting in 5.2. Instead of having several directives, it allows multiple ALLOCATE clauses to convey the information. Similarly to the executable ALLOCATE, the checks for restrictions on individual ALLOCATE clauses are done in the Enter function, while the checks for the whole construct are done in the Leave function. The actual restrictions are different enough from those on ALLOCATE directives that the checks could not be reused.

@kparzysz kparzysz requested a review from ergawy October 30, 2025 16:43
@kparzysz
Copy link
Contributor Author

Copy link
Contributor

@Stylie777 Stylie777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small comment from me.

Copy link
Contributor

@Stylie777 Stylie777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thanks

@kparzysz kparzysz merged commit ab04989 into main Nov 3, 2025
10 checks passed
@kparzysz kparzysz deleted the users/kparzysz/a01-names-in-allocate branch November 3, 2025 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:openmp flang:parser flang:semantics flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants