[flang-commits] [flang] [Flang][OpenMP] Implement OMP 5.2 implicit DECLARE TARGET procedure handling (PR #196826)

via flang-commits flang-commits at lists.llvm.org
Sun May 10 11:59:34 PDT 2026


https://github.com/blazie2004 updated https://github.com/llvm/llvm-project/pull/196826

>From b2915ff63ff8e75e4b464c6757c2b78488b5687a Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Sun, 10 May 2026 13:04:32 -0500
Subject: [PATCH] Implement OMP 5.2 implicit DECLARE TARGET procedure handling

---
 flang/lib/Semantics/check-omp-structure.cpp   |   6 +-
 flang/lib/Semantics/openmp-utils.cpp          |   4 +-
 flang/lib/Semantics/resolve-directives.cpp    |  14 +++
 flang/lib/Semantics/resolve-names.cpp         | 112 ++++++++++--------
 .../OpenMP/declare-target-implicit-proc.f90   |  22 ++++
 5 files changed, 108 insertions(+), 50 deletions(-)
 create mode 100644 flang/test/Semantics/OpenMP/declare-target-implicit-proc.f90

diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 33ea727343cf4..4792aa6b36bbf 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -1297,11 +1297,13 @@ void OmpStructureChecker::CheckThreadprivateOrDeclareTargetVar(
   llvm::omp::Directive directive{GetContext().directive};
 
   if (name->symbol->GetUltimate().IsSubprogram()) {
-    if (directive == llvm::omp::Directive::OMPD_threadprivate)
+    if (directive == llvm::omp::Directive::OMPD_threadprivate) {
       context_.Say(name->source,
           "The procedure name cannot be in a %s directive"_err_en_US,
           ContextDirectiveAsFortran());
-    // TODO: Check for procedure name in declare target directive.
+    }
+    // OMP 5.2 7.8.2 p10: a procedure name in DECLARE TARGET is valid
+    // (treated as external subroutine if not otherwise specified).
   } else if (name->symbol->attrs().test(Attr::PARAMETER)) {
     if (directive == llvm::omp::Directive::OMPD_threadprivate)
       context_.Say(name->source,
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 58aefe2e1fc52..167d4dc936d7e 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -190,7 +190,9 @@ bool IsVariableListItem(const Symbol &sym) {
 }
 
 bool IsExtendedListItem(const Symbol &sym) {
-  return IsVariableListItem(sym) || sym.IsSubprogram();
+  // Extended-list item: variable, procedure, or procedure pointer
+  return IsVariableListItem(sym) || sym.IsSubprogram() ||
+      sym.has<ProcEntityDetails>();
 }
 
 bool IsTypeParamInquiry(const Symbol &sym) {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index b97f7ce58a1c0..c96b66f4d5907 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2972,6 +2972,20 @@ void OmpAttributeVisitor::ResolveOmpDesignator(
           name->symbol = func;
         }
       }
+      // OMP 5.2 §7.8.2 ¶10: If a name appears in a declare target directive
+      // and has not been explicitly typed as a variable or procedure, and
+      // no other declaration gives it a procedure property, treat it as an
+      // external subroutine.
+      if (symbol->has<EntityDetails>() &&
+          !symbol->attrs().test(Attr::EXTERNAL) &&
+          !symbol->test(Symbol::Flag::Function) &&
+          !symbol->test(Symbol::Flag::Subroutine)) {
+        // Symbol is still unspecialized EntityDetails - promote to external
+        // proc
+        symbol->attrs().set(Attr::EXTERNAL);
+        symbol->set_details(ProcEntityDetails{});
+        symbol->set(Symbol::Flag::Subroutine);
+      }
     }
     if (directive == llvm::omp::Directive::OMPD_target_data) {
       checkExclusivelists(symbol, Symbol::Flag::OmpUseDevicePtr, symbol,
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 4f824ef1321e9..2c9b5a4bd7b18 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -2441,7 +2441,7 @@ void AttrsVisitor::SetBindNameOn(Symbol &symbol) {
   }
   symbol.SetBindName(std::move(*label));
   if (!oldBindName.empty()) {
-    if (const std::string * newBindName{symbol.GetBindName()}) {
+    if (const std::string *newBindName{symbol.GetBindName()}) {
       if (oldBindName != *newBindName) {
         Say(symbol.name(),
             "The entity '%s' has multiple BIND names ('%s' and '%s')"_err_en_US,
@@ -2567,7 +2567,7 @@ void DeclTypeSpecVisitor::Post(const parser::TypeSpec &typeSpec) {
   // expression semantics if the DeclTypeSpec is a valid TypeSpec.
   // The grammar ensures that it's an intrinsic or derived type spec,
   // not TYPE(*) or CLASS(*) or CLASS(T).
-  if (const DeclTypeSpec * spec{state_.declTypeSpec}) {
+  if (const DeclTypeSpec *spec{state_.declTypeSpec}) {
     switch (spec->category()) {
     case DeclTypeSpec::Numeric:
     case DeclTypeSpec::Logical:
@@ -2575,7 +2575,7 @@ void DeclTypeSpecVisitor::Post(const parser::TypeSpec &typeSpec) {
       typeSpec.declTypeSpec = spec;
       break;
     case DeclTypeSpec::TypeDerived:
-      if (const DerivedTypeSpec * derived{spec->AsDerived()}) {
+      if (const DerivedTypeSpec *derived{spec->AsDerived()}) {
         CheckForAbstractType(derived->typeSymbol()); // C703
         typeSpec.declTypeSpec = spec;
       }
@@ -3187,8 +3187,8 @@ Symbol &ScopeHandler::MakeSymbol(const parser::Name &name, Attrs attrs) {
 Symbol &ScopeHandler::MakeHostAssocSymbol(
     const parser::Name &name, const Symbol &hostSymbol) {
   Symbol &symbol{*NonDerivedTypeScope()
-                      .try_emplace(name.source, HostAssocDetails{hostSymbol})
-                      .first->second};
+          .try_emplace(name.source, HostAssocDetails{hostSymbol})
+          .first->second};
   name.symbol = &symbol;
   symbol.attrs() = hostSymbol.attrs(); // TODO: except PRIVATE, PUBLIC?
   // These attributes can be redundantly reapplied without error
@@ -3276,7 +3276,7 @@ void ScopeHandler::ApplyImplicitRules(
   if (context().HasError(symbol) || !NeedsType(symbol)) {
     return;
   }
-  if (const DeclTypeSpec * type{GetImplicitType(symbol)}) {
+  if (const DeclTypeSpec *type{GetImplicitType(symbol)}) {
     if (!skipImplicitTyping_) {
       symbol.set(Symbol::Flag::Implicit);
       symbol.SetType(*type);
@@ -3376,7 +3376,7 @@ const DeclTypeSpec *ScopeHandler::GetImplicitType(
   const auto *type{implicitRulesMap_->at(scope).GetType(
       symbol.name(), respectImplicitNoneType)};
   if (type) {
-    if (const DerivedTypeSpec * derived{type->AsDerived()}) {
+    if (const DerivedTypeSpec *derived{type->AsDerived()}) {
       // Resolve any forward-referenced derived type; a quick no-op else.
       auto &instantiatable{*const_cast<DerivedTypeSpec *>(derived)};
       instantiatable.Instantiate(currScope());
@@ -4397,7 +4397,7 @@ Scope *ModuleVisitor::FindModule(const parser::Name &name,
   if (scope) {
     if (DoesScopeContain(scope, currScope())) { // 14.2.2(1)
       std::optional<SourceName> submoduleName;
-      if (const Scope * container{FindModuleOrSubmoduleContaining(currScope())};
+      if (const Scope *container{FindModuleOrSubmoduleContaining(currScope())};
           container && container->IsSubmodule()) {
         submoduleName = container->GetName();
       }
@@ -4502,7 +4502,7 @@ bool InterfaceVisitor::isAbstract() const {
 
 void InterfaceVisitor::AddSpecificProcs(
     const std::list<parser::Name> &names, ProcedureKind kind) {
-  if (Symbol * symbol{GetGenericInfo().symbol};
+  if (Symbol *symbol{GetGenericInfo().symbol};
       symbol && symbol->has<GenericDetails>()) {
     for (const auto &name : names) {
       specificsForGenericProcs_.emplace(symbol, std::make_pair(&name, kind));
@@ -4602,7 +4602,7 @@ void GenericHandler::DeclaredPossibleSpecificProc(Symbol &proc) {
 }
 
 void InterfaceVisitor::ResolveNewSpecifics() {
-  if (Symbol * generic{genericInfo_.top().symbol};
+  if (Symbol *generic{genericInfo_.top().symbol};
       generic && generic->has<GenericDetails>()) {
     ResolveSpecificsInGeneric(*generic, false);
   }
@@ -4692,7 +4692,7 @@ bool SubprogramVisitor::HandleStmtFunction(const parser::StmtFunctionStmt &x) {
       // re-resolves to the new local SubprogramDetails.
       name.symbol = nullptr;
     } else if (auto *entity{ultimate.detailsIf<EntityDetails>()};
-               entity && !ultimate.has<ProcEntityDetails>()) {
+        entity && !ultimate.has<ProcEntityDetails>()) {
       resultType = entity->type();
       ultimate.details() = UnknownDetails{}; // will be replaced below
     } else {
@@ -5291,7 +5291,7 @@ Symbol *ScopeHandler::FindSeparateModuleProcedureInterface(
       symbol = generic->specific();
     }
   }
-  if (const Symbol * defnIface{FindSeparateModuleSubprogramInterface(symbol)}) {
+  if (const Symbol *defnIface{FindSeparateModuleSubprogramInterface(symbol)}) {
     // Error recovery in case of multiple definitions
     symbol = const_cast<Symbol *>(defnIface);
   }
@@ -5431,8 +5431,8 @@ bool SubprogramVisitor::HandlePreviousCalls(
     return generic->specific() &&
         HandlePreviousCalls(name, *generic->specific(), subpFlag);
   } else if (const auto *proc{symbol.detailsIf<ProcEntityDetails>()}; proc &&
-             !proc->isDummy() &&
-             !symbol.attrs().HasAny(Attrs{Attr::INTRINSIC, Attr::POINTER})) {
+      !proc->isDummy() &&
+      !symbol.attrs().HasAny(Attrs{Attr::INTRINSIC, Attr::POINTER})) {
     // There's a symbol created for previous calls to this subprogram or
     // ENTRY's name.  We have to replace that symbol in situ to avoid the
     // obligation to rewrite symbol pointers in the parse tree.
@@ -5474,7 +5474,7 @@ const Symbol *SubprogramVisitor::CheckExtantProc(
   if (prev) {
     if (IsDummy(*prev)) {
     } else if (auto *entity{prev->detailsIf<EntityDetails>()};
-               IsPointer(*prev) && entity && !entity->type()) {
+        IsPointer(*prev) && entity && !entity->type()) {
       // POINTER attribute set before interface
     } else if (inInterfaceBlock() && currScope() != prev->owner()) {
       // Procedures in an INTERFACE block do not resolve to symbols
@@ -5546,7 +5546,7 @@ Symbol *SubprogramVisitor::PushSubprogramScope(const parser::Name &name,
     }
     set_inheritFromParent(false); // interfaces don't inherit, even if MODULE
   }
-  if (Symbol * found{FindSymbol(name)};
+  if (Symbol *found{FindSymbol(name)};
       found && found->has<HostAssocDetails>()) {
     found->set(subpFlag); // PushScope() created symbol
   }
@@ -6402,9 +6402,9 @@ void DeclarationVisitor::Post(const parser::VectorTypeSpec &x) {
     vectorDerivedType.CookParameters(GetFoldingContext());
   }
 
-  if (const DeclTypeSpec *
-      extant{ppcBuiltinTypesScope->FindInstantiatedDerivedType(
-          vectorDerivedType, DeclTypeSpec::Category::TypeDerived)}) {
+  if (const DeclTypeSpec *extant{
+          ppcBuiltinTypesScope->FindInstantiatedDerivedType(
+              vectorDerivedType, DeclTypeSpec::Category::TypeDerived)}) {
     // This derived type and parameter expressions (if any) are already present
     // in the __ppc_intrinsics scope.
     SetDeclTypeSpec(*extant);
@@ -6426,7 +6426,7 @@ bool DeclarationVisitor::Pre(const parser::DeclarationTypeSpec::Type &) {
 
 void DeclarationVisitor::Post(const parser::DeclarationTypeSpec::Type &type) {
   const parser::Name &derivedName{std::get<parser::Name>(type.v.t)};
-  if (const Symbol * derivedSymbol{derivedName.symbol}) {
+  if (const Symbol *derivedSymbol{derivedName.symbol}) {
     CheckForAbstractType(*derivedSymbol); // C706
   }
 }
@@ -6495,8 +6495,8 @@ void DeclarationVisitor::Post(const parser::DerivedTypeSpec &x) {
   if (!spec->MightBeParameterized()) {
     spec->EvaluateParameters(context());
   }
-  if (const DeclTypeSpec *
-      extant{currScope().FindInstantiatedDerivedType(*spec, category)}) {
+  if (const DeclTypeSpec *extant{
+          currScope().FindInstantiatedDerivedType(*spec, category)}) {
     // This derived type and parameter expressions (if any) are already present
     // in this scope.
     SetDeclTypeSpec(*extant);
@@ -6527,8 +6527,7 @@ void DeclarationVisitor::Post(const parser::DeclarationTypeSpec::Record &rec) {
   if (auto spec{ResolveDerivedType(typeName)}) {
     spec->CookParameters(GetFoldingContext());
     spec->EvaluateParameters(context());
-    if (const DeclTypeSpec *
-        extant{currScope().FindInstantiatedDerivedType(
+    if (const DeclTypeSpec *extant{currScope().FindInstantiatedDerivedType(
             *spec, DeclTypeSpec::TypeDerived)}) {
       SetDeclTypeSpec(*extant);
     } else {
@@ -7553,7 +7552,7 @@ bool DeclarationVisitor::PassesLocalityChecks(
         "Coarray '%s' not allowed in a %s locality-spec"_err_en_US, specName);
     return false;
   }
-  if (const DeclTypeSpec * type{symbol.GetType()}) {
+  if (const DeclTypeSpec *type{symbol.GetType()}) {
     if (type->IsPolymorphic() && IsDummy(symbol) && !IsPointer(symbol) &&
         !isReduce) { // F'2023 C1130
       SayWithDecl(name, symbol,
@@ -7787,7 +7786,7 @@ Symbol *DeclarationVisitor::NoteInterfaceName(const parser::Name &name) {
 }
 
 void DeclarationVisitor::CheckExplicitInterface(const parser::Name &name) {
-  if (const Symbol * symbol{name.symbol}) {
+  if (const Symbol *symbol{name.symbol}) {
     const Symbol &ultimate{symbol->GetUltimate()};
     if (!context().HasError(*symbol) && !context().HasError(ultimate) &&
         !BypassGeneric(ultimate).HasExplicitInterface()) {
@@ -8107,7 +8106,7 @@ bool ConstructVisitor::Pre(const parser::DataStmtValue &x) {
   auto &mutableData{const_cast<parser::DataStmtConstant &>(data)};
   if (auto *elem{parser::Unwrap<parser::ArrayElement>(mutableData)}) {
     if (const auto *name{std::get_if<parser::Name>(&elem->Base().u)}) {
-      if (const Symbol * symbol{FindSymbol(*name)};
+      if (const Symbol *symbol{FindSymbol(*name)};
           symbol && symbol->GetUltimate().has<DerivedTypeDetails>()) {
         mutableData.u = elem->ConvertToStructureConstructor(
             DerivedTypeSpec{name->source, *symbol});
@@ -8253,15 +8252,15 @@ void ConstructVisitor::Post(const parser::SelectTypeStmt &x) {
       }
     }
   } else {
-    if (const Symbol *
-        whole{UnwrapWholeSymbolDataRef(association.selector.expr)}) {
+    if (const Symbol *whole{
+            UnwrapWholeSymbolDataRef(association.selector.expr)}) {
       ConvertToObjectEntity(const_cast<Symbol &>(*whole));
       if (!IsVariableName(*whole)) {
         Say(association.selector.source, // C901
             "Selector is not a variable"_err_en_US);
         association = {};
       }
-      if (const DeclTypeSpec * type{whole->GetType()}) {
+      if (const DeclTypeSpec *type{whole->GetType()}) {
         if (!type->IsPolymorphic()) { // C1159
           Say(association.selector.source,
               "Selector '%s' in SELECT TYPE statement must be "
@@ -8401,8 +8400,8 @@ Symbol *ConstructVisitor::MakeAssocEntity() {
           "The associate name '%s' is already used in this associate statement"_err_en_US);
       return nullptr;
     }
-  } else if (const Symbol *
-      whole{UnwrapWholeSymbolDataRef(association.selector.expr)}) {
+  } else if (const Symbol *whole{
+                 UnwrapWholeSymbolDataRef(association.selector.expr)}) {
     symbol = &MakeSymbol(whole->name());
   } else {
     return nullptr;
@@ -9025,7 +9024,7 @@ bool DeclarationVisitor::CheckForHostAssociatedImplicit(
   if (name.symbol) {
     ApplyImplicitRules(*name.symbol, true);
   }
-  if (Scope * host{GetHostProcedure()}; host && !isImplicitNoneType(*host)) {
+  if (Scope *host{GetHostProcedure()}; host && !isImplicitNoneType(*host)) {
     Symbol *hostSymbol{nullptr};
     if (!name.symbol) {
       if (currScope().CanImport(name.source)) {
@@ -9096,7 +9095,7 @@ const parser::Name *DeclarationVisitor::FindComponent(
   if (!type) {
     return nullptr; // should have already reported error
   }
-  if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) {
+  if (const IntrinsicTypeSpec *intrinsic{type->AsIntrinsic()}) {
     auto category{intrinsic->category()};
     MiscDetails::Kind miscKind{MiscDetails::Kind::None};
     if (component.source == "kind") {
@@ -9118,7 +9117,7 @@ const parser::Name *DeclarationVisitor::FindComponent(
     }
   } else if (DerivedTypeSpec * derived{type->AsDerived()}) {
     derived->Instantiate(currScope()); // in case of forward referenced type
-    if (const Scope * scope{derived->scope()}) {
+    if (const Scope *scope{derived->scope()}) {
       if (Resolve(component, scope->FindComponent(component.source))) {
         if (auto msg{CheckAccessibleSymbol(currScope(), *component.symbol)}) {
           context().Say(component.source, *msg);
@@ -9165,18 +9164,37 @@ bool DeclarationVisitor::FindAndMarkDeclareTargetSymbol(
               return true;
             }
             // if we find a symbol that is not a function or subroutine, we
-            // currently escape without doing anything.
-            break;
+            // currently escape without doing anything. Do NOT fall through
+            // to create an implicit external procedure.
+            return false;
           }
 
           // This is our loop exit condition, as parent() has an inbuilt assert
           // if you call it on a top level scope, rather than returning a null
           // value.
           if (scope->IsTopLevel()) {
-            return false;
+            break;
           }
         }
       }
+
+      // OpenMP 5.2, 7.8.2 p10: a procedure name in DECLARE TARGET with no
+      // explicit data/procedure properties is treated as an external
+      // subroutine. Only apply this at program/module scope level, not inside
+      // subprograms where local variables could be forward-declared.
+      if (!isImplicitNoneType() &&
+          currScope().kind() != Scope::Kind::Subprogram &&
+          currScope().kind() != Scope::Kind::BlockConstruct) {
+        auto [it, inserted]{
+            currScope().try_emplace(name.source, Attrs{}, ProcEntityDetails{})};
+        Symbol &symbol{*it->second};
+        if (inserted || symbol.has<ProcEntityDetails>()) {
+          name.symbol = &symbol;
+          symbol.set(Symbol::Flag::Subroutine);
+          SetImplicitAttr(symbol, Attr::EXTERNAL);
+          return true;
+        }
+      }
     }
   }
   return false;
@@ -9275,8 +9293,8 @@ void DeclarationVisitor::PointerInitialization(
             if (evaluate::IsNullProcedurePointer(&*expr)) {
               CHECK(!details->init());
               details->set_init(nullptr);
-            } else if (const Symbol *
-                targetSymbol{evaluate::UnwrapWholeSymbolDataRef(*expr)}) {
+            } else if (const Symbol *targetSymbol{
+                           evaluate::UnwrapWholeSymbolDataRef(*expr)}) {
               CHECK(!details->init());
               details->set_init(*targetSymbol);
             } else {
@@ -9885,7 +9903,7 @@ void ResolveNamesVisitor::EarlyDummyTypeDeclaration(
       for (const auto &ent : entities) {
         const auto &objName{std::get<parser::ObjectName>(ent.t)};
         Resolve(objName, FindInScope(currScope(), objName));
-        if (Symbol * symbol{objName.symbol};
+        if (Symbol *symbol{objName.symbol};
             symbol && IsDummy(*symbol) && NeedsType(*symbol)) {
           if (!type) {
             type = ProcessTypeSpec(declTypeSpec);
@@ -10027,7 +10045,7 @@ void ResolveNamesVisitor::FinishSpecificationPart(
     if (auto *proc{symbol.detailsIf<ProcEntityDetails>()}; proc &&
         !proc->isDummy() && !IsPointer(symbol) &&
         !symbol.attrs().test(Attr::BIND_C)) {
-      if (const Symbol * iface{proc->procInterface()};
+      if (const Symbol *iface{proc->procInterface()};
           iface && IsBindCProcedure(*iface)) {
         SetImplicitAttr(symbol, Attr::BIND_C);
         SetBindNameOn(symbol);
@@ -10167,7 +10185,7 @@ bool ResolveNamesVisitor::Pre(const parser::PointerAssignmentStmt &x) {
   Symbol *ptrSymbol{parser::GetLastName(dataRef).symbol};
   Walk(bounds);
   // Resolve unrestricted specific intrinsic procedures as in "p => cos".
-  if (const parser::Name * name{parser::Unwrap<parser::Name>(expr)}) {
+  if (const parser::Name *name{parser::Unwrap<parser::Name>(expr)}) {
     if (NameIsKnownOrIntrinsic(*name)) {
       if (Symbol * symbol{name->symbol}) {
         if (IsProcedurePointer(ptrSymbol) &&
@@ -10630,8 +10648,8 @@ void ResolveNamesVisitor::ResolveSpecificationParts(ProgramTree &node) {
     // implied SAVE so that evaluate::IsSaved() will return true.
     if (node.scope()->kind() == Scope::Kind::MainProgram) {
       if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
-        if (const DeclTypeSpec * type{object->type()}) {
-          if (const DerivedTypeSpec * derived{type->AsDerived()}) {
+        if (const DeclTypeSpec *type{object->type()}) {
+          if (const DerivedTypeSpec *derived{type->AsDerived()}) {
             if (!IsSaved(symbol) && FindCoarrayPotentialComponent(*derived)) {
               SetImplicitAttr(symbol, Attr::SAVE);
             }
@@ -10891,7 +10909,7 @@ void ResolveNamesVisitor::FinishDerivedTypeInstantiation(Scope &scope) {
   if (DerivedTypeSpec * spec{scope.derivedTypeSpec()}) {
     spec->Instantiate(currScope());
     const Symbol &origTypeSymbol{spec->typeSymbol()};
-    if (const Scope * origTypeScope{origTypeSymbol.scope()}) {
+    if (const Scope *origTypeScope{origTypeSymbol.scope()}) {
       CHECK(origTypeScope->IsDerivedType() &&
           origTypeScope->symbol() == &origTypeSymbol);
       auto &foldingContext{GetFoldingContext()};
@@ -10902,7 +10920,7 @@ void ResolveNamesVisitor::FinishDerivedTypeInstantiation(Scope &scope) {
         if (IsPointer(comp)) {
           if (auto *details{comp.detailsIf<ObjectEntityDetails>()}) {
             auto origDetails{origComp.get<ObjectEntityDetails>()};
-            if (const MaybeExpr & init{origDetails.init()}) {
+            if (const MaybeExpr &init{origDetails.init()}) {
               SomeExpr newInit{*init};
               MaybeExpr folded{FoldExpr(std::move(newInit))};
               details->set_init(std::move(folded));
diff --git a/flang/test/Semantics/OpenMP/declare-target-implicit-proc.f90 b/flang/test/Semantics/OpenMP/declare-target-implicit-proc.f90
new file mode 100644
index 0000000000000..203a94419594b
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/declare-target-implicit-proc.f90
@@ -0,0 +1,22 @@
+! RUN: %flang_fc1 -fopenmp -fopenmp-version=52 -fsyntax-only %s 2>&1 | FileCheck %s
+
+! Test OpenMP 5.2 §7.8.2 ¶10: implicit external procedure in DECLARE TARGET
+! A name in DECLARE TARGET with no explicit type is treated as external subroutine
+
+program test_implicit_declare_target
+  integer :: n = 10
+  ! ext_sub should be implicitly treated as an external subroutine
+  !$omp declare target(ext_sub)
+
+  !$omp target
+    call ext_sub(n)
+  !$omp end target
+end program
+
+subroutine ext_sub(x)
+  !$omp declare target
+  integer, intent(in) :: x
+  print *, "Called with:", x
+end subroutine
+
+! CHECK-NOT: error:
\ No newline at end of file



More information about the flang-commits mailing list