[flang-commits] [flang] [flang] Statement function dummy names do not clash with host or unreferenced global names (PR #212396)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Tue Jul 28 06:11:09 PDT 2026


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/212396

>From 79bcfd438a3939bd963eff94140416af3776babc Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Mon, 27 Jul 2026 23:23:53 -0400
Subject: [PATCH 1/3] [flang] Statement function dummy names do not clash with
 host or unreferenced global names

The check added for F2023 19.4 p2 (statement function dummy argument
name may be the same as an accessible name only if that name is a
scalar variable) looked the name up through the whole host scope chain
and, failing that, in the global scope. Both lookups overshoot what is
"accessible" in the scoping unit:

* Per F2023 19.5.1.4 p2 item (11), the appearance of a name as a
  dummy-arg-name in a stmt-function-stmt makes any host entity of that
  name inaccessible by host association throughout the scoping unit,
  so a host entity can never conflict with the dummy.

* A global entity to which the scoping unit makes no reference at all
  is not accessible in it; otherwise conformance would depend on
  whether unrelated program units happen to be compiled in the same
  file.

This caused bogus errors on conforming code such as

  program p
    logical, external :: x
  contains
    subroutine s
      real :: f
      f(x) = x * x  ! 'x' is an implicitly typed real dummy here
    end
  end

where the dummy 'x' must be implicitly typed real (both NAG and
gfortran accept this and type it that way; flang already typed it
correctly, but rejected the program).

Restrict the lookup to names made visible by the scoping unit itself
(declared, referenced, or USE-associated there), matching the lookup
HandleStmtFunction already uses to type the dummies.
---
 flang/lib/Semantics/resolve-names.cpp | 24 +++++------
 flang/test/Semantics/stmt-func04.f90  | 60 ++++++++++++++++++---------
 2 files changed, 51 insertions(+), 33 deletions(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 83d92f253e626..708d11d211f9e 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10686,13 +10686,17 @@ void ResolveNamesVisitor::AnalyzeStmtFunctionStmt(
     return; // error recovery
   }
 
-  // F2023 19.4 p2: a statement-function dummy argument name may be the same
-  // as an accessible name only if that name is a scalar variable.  The lookup
-  // walks the host chain (but not into the global scope), so host-associated
-  // identifiers are also considered.
+  // F2023 19.4 p2: a statement function dummy argument name may be the same
+  // as an accessible name only if that name is a scalar variable.  Only
+  // names made visible by this scoping unit itself (declared, referenced,
+  // or USE-associated here) can conflict: per 19.5.1.4 p2 item (11), the
+  // name's appearance as a statement function dummy argument renders any
+  // host entity of that name inaccessible by host association, and a global
+  // entity to which this scoping unit makes no other reference is not
+  // accessible in it either.
   for (const auto &dummyName : std::get<std::list<parser::Name>>(stmtFunc.t)) {
-    if (const Symbol *hostSymbol{currScope().FindSymbol(dummyName.source)}) {
-      const Symbol &ultimate{hostSymbol->GetUltimate()};
+    if (const Symbol * local{FindInScope(currScope(), dummyName.source)}) {
+      const Symbol &ultimate{local->GetUltimate()};
       const bool isScalarVariable{(ultimate.has<ObjectEntityDetails>() ||
                                       ultimate.has<EntityDetails>()) &&
           !IsNamedConstant(ultimate) && ultimate.Rank() == 0};
@@ -10700,14 +10704,6 @@ void ResolveNamesVisitor::AnalyzeStmtFunctionStmt(
         Say(dummyName.source,
             "The name '%s' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable"_err_en_US);
       }
-    } else {
-      // Also check the global scope, which FindSymbol skips
-      const Scope &globals{context().globalScope()};
-      if (auto it{globals.find(dummyName.source)}; it != globals.end()) {
-        // global function/subroutine — definitely not a scalar variable
-        Say(dummyName.source,
-            "The name '%s' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable"_err_en_US);
-      }
     }
   }
 
diff --git a/flang/test/Semantics/stmt-func04.f90 b/flang/test/Semantics/stmt-func04.f90
index 2b3c1ff008527..ec990b5dc9a92 100644
--- a/flang/test/Semantics/stmt-func04.f90
+++ b/flang/test/Semantics/stmt-func04.f90
@@ -1,7 +1,11 @@
 ! RUN: %python %S/test_errors.py %s %flang_fc1
 ! F2023 19.4 p2: a statement function dummy argument name may be the same as an
 ! accessible global identifier or local identifier of class (1) only if that
-! name is a scalar variable.
+! name is a scalar variable.  Only names made visible by the scoping unit
+! itself can conflict: per 19.5.1.4 p2 item (11), the name's appearance as a
+! statement function dummy argument renders any host entity of that name
+! inaccessible by host association, and a global entity to which the scoping
+! unit makes no other reference is not accessible in it either.
 
 ! Clashes within the statement function's own scoping unit.
 subroutine local_clashes
@@ -22,29 +26,28 @@ subroutine local_clashes
   f5(scalarvar) = scalarvar + 1 ! ok: scalar variable shadowing is permitted
 end subroutine
 
-! Clashes with host-associated identifiers (module scope).
+! No clashes with host entities (module scope): the dummy argument's
+! appearance blocks host association (19.5.1.4 p2 item (11)), so the host
+! entities are inaccessible here and the dummies are implicitly typed.
 module m
   integer :: hostarr(10)
   integer :: hostscalar
   integer, parameter :: hostconst = 3
 contains
-  subroutine host_clashes
-    !ERROR: The name 'hostarr' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
-    g1(hostarr) = hostarr + 1
-    !ERROR: The name 'hostconst' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
-    g2(hostconst) = hostconst + 1
+  subroutine host_no_clashes
+    g1(hostarr) = hostarr + 1 ! ok: host's 'hostarr' is inaccessible here
+    g2(hostconst) = hostconst + 1 ! ok: host's 'hostconst' is inaccessible here
     g3(hostscalar) = hostscalar + 1 ! ok: host scalar variable
   end subroutine
 end module
 
-! Clashes with grandparent-associated identifiers (internal procedure).
+! Same for grandparent host entities (internal procedure).
 program p
   integer :: grandarr(5)
   integer :: grandscalar
 contains
-  subroutine grand_clashes
-    !ERROR: The name 'grandarr' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
-    h1(grandarr) = grandarr + 1
+  subroutine grand_no_clashes
+    h1(grandarr) = grandarr + 1 ! ok: host's 'grandarr' is inaccessible here
     h2(grandscalar) = grandscalar + 1 ! ok: grandparent scalar variable
   end subroutine
 end program
@@ -64,23 +67,22 @@ subroutine use_clashes
   k3(usescalar) = usescalar + 1 ! ok: USE-associated scalar variable
 end subroutine
 
-! Clashes with global-scope program units.
+! No clash with a global-scope program unit to which the scoping unit makes
+! no other reference: that global identifier is not accessible in it.
 real function global_func(x)
   real :: x
   global_func = x
 end function
-subroutine global_clashes
-  !ERROR: The name 'global_func' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
-  p1(global_func) = global_func + 1
+subroutine global_no_clashes
+  p1(global_func) = global_func + 1 ! ok: 'global_func' is not accessible here
 end subroutine
 
-! Clashes with bind(c) global subprogram (exercises global scope path).
+! Likewise for a bind(c) global subprogram.
 real function bindc_global_func() bind(c)
   bindc_global_func = 1.0
 end function
-subroutine bindc_global_clashes
-  !ERROR: The name 'bindc_global_func' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
-  r1(bindc_global_func) = bindc_global_func + 1
+subroutine bindc_global_no_clashes
+  r1(bindc_global_func) = bindc_global_func + 1 ! ok: not accessible here
 end subroutine
 
 ! Clashes with bind(c) external declared via local explicit interface block.
@@ -117,3 +119,23 @@ subroutine bindc_use_proc_clashes
   !ERROR: The name 'c_mod_func' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
   r5(c_mod_func) = c_mod_func + 1
 end subroutine
+
+! A host EXTERNAL declaration does not make the name accessible in an inner
+! subprogram that uses it as a statement function dummy argument; host
+! association of the name is blocked throughout that subprogram (19.5.1.4 p2
+! item (11)), so the dummy is implicitly typed there (default real).  If host
+! association leaked through, 'pred * pred' would be a type error.
+module m_host_external
+  logical, external :: pred
+contains
+  subroutine eval(arg)
+    real :: sq
+    sq(pred) = pred * pred ! ok: 'pred' is an implicitly typed real dummy here
+    print *, sq(arg)
+  end subroutine
+  subroutine other
+    logical :: q
+    q = pred() ! ok: host association of 'pred' is severed only in 'eval'
+    print *, q
+  end subroutine
+end module

>From 3318ac5f1224ebe0c615885ac30753ec6a61b5fe Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Tue, 28 Jul 2026 00:02:30 -0400
Subject: [PATCH 2/3] Apply clang-format

---
 flang/lib/Semantics/resolve-names.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 708d11d211f9e..a0158a41b563f 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10695,7 +10695,7 @@ void ResolveNamesVisitor::AnalyzeStmtFunctionStmt(
   // entity to which this scoping unit makes no other reference is not
   // accessible in it either.
   for (const auto &dummyName : std::get<std::list<parser::Name>>(stmtFunc.t)) {
-    if (const Symbol * local{FindInScope(currScope(), dummyName.source)}) {
+    if (const Symbol *local{FindInScope(currScope(), dummyName.source)}) {
       const Symbol &ultimate{local->GetUltimate()};
       const bool isScalarVariable{(ultimate.has<ObjectEntityDetails>() ||
                                       ultimate.has<EntityDetails>()) &&

>From 8cdbc7f0cad02300022c2674b4c13e65ce1bca9c Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Tue, 28 Jul 2026 08:59:08 -0400
Subject: [PATCH 3/3] [flang] Diagnose statement function dummy arguments that
 hide imported names

Per F2023 C8106, a name that appears as an import-name, or that is
made accessible by IMPORT, ALL, shall not appear in any context
described in 19.5.1.4 that would render the host entity of that name
inaccessible. A statement function dummy argument name is such a
context (19.5.1.4 p2 item (11)), and C8106 has no exception for
scalar variables. The narrowed 19.4 p2 lookup no longer catches these
(and CheckImports() cannot, because statement function dummies never
become current-scope symbols), so diagnose them separately, keyed on
the scope's explicit import names, or IMPORT, ALL with an existing
host entity, rather than on a host-chain lookup: a plain IMPORT
statement tolerates hiding (8.8 p4) and must stay accepted.

Also, per review: reword the 19.4 p2 comment to say what the lookup
actually sees (names present in the current scope after
specification-part resolution) and acknowledge the known gap for
names that become external procedure names only by way of a reference
in the execution part; add boundary tests pinning the still-rejected
enclosing-subprogram-name and ENTRY-name clashes and the accepted
common-block-name carve-out; update stale C8102 comments in
CheckImports to the F2023 numbering, C8106.
---
 flang/lib/Semantics/resolve-names.cpp | 37 ++++++++++++++-----
 flang/test/Semantics/stmt-func04.f90  | 51 +++++++++++++++++++++++++++
 2 files changed, 80 insertions(+), 8 deletions(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index a0158a41b563f..f4e650ab05f33 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10688,13 +10688,34 @@ void ResolveNamesVisitor::AnalyzeStmtFunctionStmt(
 
   // F2023 19.4 p2: a statement function dummy argument name may be the same
   // as an accessible name only if that name is a scalar variable.  Only
-  // names made visible by this scoping unit itself (declared, referenced,
-  // or USE-associated here) can conflict: per 19.5.1.4 p2 item (11), the
-  // name's appearance as a statement function dummy argument renders any
-  // host entity of that name inaccessible by host association, and a global
-  // entity to which this scoping unit makes no other reference is not
-  // accessible in it either.
+  // names present in the current scope after specification-part resolution
+  // can conflict: per 19.5.1.4 p2 item (11), the name's appearance as a
+  // statement function dummy argument renders any host entity of that name
+  // inaccessible by host association, and a global entity to which this
+  // scoping unit makes no other reference is not accessible in it either.
+  // (A name that becomes an external procedure name only by way of a
+  // reference in the execution part is not yet visible here and is not
+  // diagnosed.)
+  const std::set<SourceName> importNames{currScope().importNames()};
+  const common::ImportKind importKind{currScope().GetImportKind()};
   for (const auto &dummyName : std::get<std::list<parser::Name>>(stmtFunc.t)) {
+    // F2023 C8106: an explicitly imported name, or any host name made
+    // accessible by IMPORT, ALL, may not appear in a context that would
+    // render the host entity inaccessible, and a statement function dummy
+    // argument name is such a context (19.5.1.4 p2 item (11)).  There is
+    // no exception for scalar variables.  A plain IMPORT statement does
+    // not protect names from being hidden (8.8 p4).
+    if (importNames.count(dummyName.source) > 0 ||
+        importKind == common::ImportKind::All) {
+      if (const Symbol *host{
+              currScope().parent().FindSymbol(dummyName.source)}) {
+        Say(dummyName.source,
+            "'%s' from host may not be hidden by a statement function dummy argument"_err_en_US,
+            dummyName.source)
+            .Attach(host->name(), "Declaration of '%s'"_en_US, host->name());
+        continue;
+      }
+    }
     if (const Symbol *local{FindInScope(currScope(), dummyName.source)}) {
       const Symbol &ultimate{local->GetUltimate()};
       const bool isScalarVariable{(ultimate.has<ObjectEntityDetails>() ||
@@ -10736,7 +10757,7 @@ void ResolveNamesVisitor::CheckImports() {
   case common::ImportKind::None:
     break;
   case common::ImportKind::All:
-    // C8102: all entities in host must not be hidden
+    // F2023 C8106: all entities in host must not be hidden
     for (const auto &pair : scope.parent()) {
       auto &name{pair.first};
       std::optional<SourceName> scopeName{scope.GetName()};
@@ -10747,7 +10768,7 @@ void ResolveNamesVisitor::CheckImports() {
     break;
   case common::ImportKind::Default:
   case common::ImportKind::Only:
-    // C8102: entities named in IMPORT must not be hidden
+    // F2023 C8106: entities named in IMPORT must not be hidden
     for (auto &name : scope.importNames()) {
       CheckImport(name, name);
     }
diff --git a/flang/test/Semantics/stmt-func04.f90 b/flang/test/Semantics/stmt-func04.f90
index ec990b5dc9a92..5ed0afbf45e3e 100644
--- a/flang/test/Semantics/stmt-func04.f90
+++ b/flang/test/Semantics/stmt-func04.f90
@@ -139,3 +139,54 @@ subroutine other
     print *, q
   end subroutine
 end module
+
+! F2023 C8106: an explicitly imported host name, or any host name made
+! accessible by IMPORT, ALL, may not be hidden, and a statement function
+! dummy argument of that name would hide it.  There is no scalar-variable
+! exception here.  A plain IMPORT does not protect names from being hidden
+! (8.8 p4).
+subroutine import_host
+  real :: val
+contains
+  subroutine only_import
+    import, only: val
+    !ERROR: 'val' from host may not be hidden by a statement function dummy argument
+    w1(val) = val + 1
+  end subroutine
+  subroutine named_import
+    import :: val
+    !ERROR: 'val' from host may not be hidden by a statement function dummy argument
+    w2(val) = val + 1
+  end subroutine
+  subroutine all_import
+    import, all
+    !ERROR: 'val' from host may not be hidden by a statement function dummy argument
+    w3(val) = val + 1
+  end subroutine
+  subroutine plain_import
+    import
+    w4(val) = val + 1 ! ok: plain IMPORT tolerates hiding (8.8 p4)
+  end subroutine
+end subroutine
+
+! Boundary cases that remain errors: the enclosing subprogram's own name and
+! an ENTRY name of the same subprogram are visible in the subprogram itself.
+subroutine self_clash
+  real :: f
+  !ERROR: The name 'self_clash' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
+  f(self_clash) = self_clash + 1
+end subroutine
+subroutine entry_clash
+  real :: g
+  !ERROR: The name 'ent' of a statement function dummy argument may not be the same as an accessible name unless that name is a scalar variable
+  g(ent) = ent + 1
+  return
+entry ent
+end subroutine
+
+! A common block name is explicitly excepted by 19.4 p2.
+subroutine common_carveout
+  real :: y, f
+  common /cb/ y
+  f(cb) = cb + 1 ! ok: common block name carve-out
+end subroutine



More information about the flang-commits mailing list