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

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
This commit is contained in:
Eugene Epshteyn 2026-07-29 13:21:15 -04:00 committed by GitHub
parent e24eb10725
commit 32eae3a394
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 35 deletions

View file

@ -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);
}

View file

@ -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