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

via flang-commits flang-commits at lists.llvm.org
Tue Jul 28 06:32:41 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-semantics

Author: Eugene Epshteyn (eugeneepshteyn)

<details>
<summary>Changes</summary>

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.

Assisted-by: AI

---
Full diff: https://github.com/llvm/llvm-project/pull/212396.diff


2 Files Affected:

- (modified) flang/lib/Semantics/resolve-names.cpp (+33-16) 
- (modified) flang/test/Semantics/stmt-func04.f90 (+92-19) 


``````````diff
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 83d92f253e626..f4e650ab05f33 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10686,13 +10686,38 @@ 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 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)) {
-    if (const Symbol *hostSymbol{currScope().FindSymbol(dummyName.source)}) {
-      const Symbol &ultimate{hostSymbol->GetUltimate()};
+    // 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>() ||
                                       ultimate.has<EntityDetails>()) &&
           !IsNamedConstant(ultimate) && ultimate.Rank() == 0};
@@ -10700,14 +10725,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);
-      }
     }
   }
 
@@ -10740,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()};
@@ -10751,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 2b3c1ff008527..5ed0afbf45e3e 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,74 @@ 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
+
+! 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

``````````

</details>


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


More information about the flang-commits mailing list