[flang-commits] [flang] [flang][Semantics] Preserve ambiguous USE-error through whole-module USE (PR #229707)

via flang-commits flang-commits at lists.llvm.org
Wed Oct 7 15:59:07 PDT 2026


https://github.com/mleair updated https://github.com/llvm/llvm-project/pull/229707

>From d95dff929ca66a89ccd84d84db55a3f353ef013d Mon Sep 17 00:00:00 2001
From: Mark Leair <leairmark at gmail.com>
Date: Tue, 6 Oct 2026 16:01:25 -0700
Subject: [PATCH 1/3] [flang][Semantics] Preserve ambiguous USE-error through
 whole-module USE

Per F2023 14.2.2 p8, an accessed entity may be associated with two
or more distinct ultimate entities as long as its identifier is
never referenced; this is recorded internally as UseErrorDetails, a
poison-pill symbol that triggers an error if the name is ever used.

When a module scope contains such an ambiguous (and never
referenced) name, a whole-module USE of that module resolved the
ambiguous entity into an ordinary UseDetails pointing at the origin
module's own (unwritten) UseErrorDetails symbol. Since the mod-file
writer omits UseErrorDetails symbols from the module file it
produces, the re-exporting module's .mod file ended up with a
'use ...,only:name' line naming an entity the origin module's own
.mod file does not actually provide, so compiling a client of the
re-exporting module failed with "'name' not found in module".

Propagate the ambiguity instead of resolving it: give the local
symbol a UseErrorDetails too, so it is (like the origin symbol)
omitted from the module file, keeping the round trip consistent.

The added test exercises this through three independent paths into
ModuleVisitor::DoAddUse: a bare whole-module USE, an explicit
only-list USE, a renaming only-list USE, and a second, independent
way of creating the ambiguous symbol (two distinct derived types of
the same name) handled by a different branch of DoAddUse.

Assisted-by: AI
---
 flang/lib/Semantics/resolve-names.cpp |  12 +++
 flang/test/Semantics/modfile89.f90    | 111 ++++++++++++++++++++++++++
 2 files changed, 123 insertions(+)
 create mode 100644 flang/test/Semantics/modfile89.f90

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 67690d6f47d067..37789a69c38299 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -4421,6 +4421,18 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
       // be copied.
       localSymbol->set_details(GenericDetails{});
       localSymbol->get<GenericDetails>().set_kind(useGeneric->kind());
+    } else if (const auto *useError{useSymbol.detailsIf<UseErrorDetails>()}) {
+      // The module being use-associated wholesale already has an ambiguous
+      // name at this position (e.g. two distinct USEs of the same name that
+      // are themselves never referenced there, which F2023 14.2.2 p8 allows).
+      // Propagate that ambiguity to the local symbol rather than silently
+      // resolving it to one of the ambiguous module's own USE details; the
+      // latter would produce a local UseDetails whose target module file
+      // omits the name, so it would not survive a module file round trip.
+      localSymbol->set_details(UseErrorDetails{*useError});
+      localSymbol->attrs() =
+          useSymbol.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE, Attr::SAVE};
+      return;
     } else { // just create UseDetails
       localSymbol->set_details(UseDetails{localName, useSymbol});
       localSymbol->attrs() =
diff --git a/flang/test/Semantics/modfile89.f90 b/flang/test/Semantics/modfile89.f90
new file mode 100644
index 00000000000000..bd051de92e525e
--- /dev/null
+++ b/flang/test/Semantics/modfile89.f90
@@ -0,0 +1,111 @@
+! RUN: split-file %s %t
+! RUN: %flang_fc1 -fsyntax-only -module-dir %t %t/defs.f90
+! RUN: %flang_fc1 -fsyntax-only -module-dir %t %t/use.f90
+! RUN: FileCheck --check-prefix=PK --input-file=%t/pk.mod %s
+! RUN: FileCheck --check-prefix=VM --input-file=%t/vm.mod %s
+! RUN: FileCheck --check-prefix=MIDDLE --input-file=%t/middle.mod %s
+! RUN: FileCheck --check-prefix=DOWN --input-file=%t/down.mod %s
+! RUN: FileCheck --check-prefix=ONLYMOD --input-file=%t/down_only.mod %s
+! RUN: FileCheck --check-prefix=RENMOD --input-file=%t/down_rename.mod %s
+! RUN: FileCheck --check-prefix=DTMID --input-file=%t/dtmiddle.mod %s
+! RUN: FileCheck --check-prefix=DTDOWN --input-file=%t/dtdown.mod %s
+
+! Regression test: a module that re-exports another module's ambiguous
+! (never-referenced, and therefore legal per F2023 14.2.2 p8) USE-associated
+! name must not name that entity in its own module file, because the
+! originating module's module file does not provide it.
+!
+! MIDDLE combines two distinct USEs of the same name (JPRB) that is never
+! itself referenced within MIDDLE, so MIDDLE's module file correctly omits
+! the ambiguous name.  DOWN re-exports MIDDLE via a whole-module USE; its
+! module file must likewise omit JPRB.  Before the fix, compiling use.f90
+! failed with "'jprb' not found in module 'middle'".
+!
+! DOWN_ONLY and DOWN_RENAME cover the same ambiguity reached through
+! use middle,only:jprb and use middle,only:myjprb=>jprb, which resolve
+! through ModuleVisitor::AddUse rather than the whole-module
+! AddUseForPublicSymbols path that DOWN exercises.
+!
+! DTMIDDLE/DTDOWN cover a second, independent way of creating the
+! ambiguous poison-pill symbol: two distinct derived types of the same
+! name, handled by the "many possible combinations" tail of DoAddUse
+! rather than its early-return whole-module path.  DTMIDDLE only pins
+! the precondition (its module file omits the ambiguous name either way);
+! DTDOWN is the one that catches a regression.
+
+!--- defs.f90
+module pk
+  integer jprb
+end module
+module vm
+  integer jprb
+end module
+module middle
+  use pk
+  use vm
+end module
+module down
+  use middle
+end module
+module down_only
+  use middle, only: jprb
+end module
+module down_rename
+  use middle, only: myjprb => jprb
+end module
+module dtpk
+  type :: dt
+    integer a
+  end type
+end module
+module dtvm
+  type :: dt
+    real b
+  end type
+end module
+module dtmiddle
+  use dtpk
+  use dtvm
+end module
+module dtdown
+  use dtmiddle
+end module
+
+!--- use.f90
+use down
+use down_only
+use down_rename
+use dtdown
+end
+
+! PK: module pk
+! PK: jprb
+! PK: end
+
+! VM: module vm
+! VM: jprb
+! VM: end
+
+! MIDDLE: module middle
+! MIDDLE-NOT: jprb
+! MIDDLE: end
+
+! DOWN: module down
+! DOWN-NOT: jprb
+! DOWN: end
+
+! ONLYMOD: module down_only
+! ONLYMOD-NOT: jprb
+! ONLYMOD: end
+
+! RENMOD: module down_rename
+! RENMOD-NOT: jprb
+! RENMOD: end
+
+! DTMID: module dtmiddle
+! DTMID-NOT: dt
+! DTMID: end
+
+! DTDOWN: module dtdown
+! DTDOWN-NOT: dt
+! DTDOWN: end

>From a9b9569d07c0511255b5d4b111d1f3b136dd22c0 Mon Sep 17 00:00:00 2001
From: Mark Leair <leairmark at gmail.com>
Date: Wed, 7 Oct 2026 15:58:44 -0700
Subject: [PATCH 2/3] [flang][Semantics] Factor out attrs mask; fix VOLATILE
 bookkeeping

Per review: hoist the PUBLIC/PRIVATE/SAVE exclusion mask into a
named constexpr shared by both the UseErrorDetails and UseDetails
branches, instead of repeating the literal set.

Also set implicitAttrs() for inherited ASYNCHRONOUS/VOLATILE in the
new UseErrorDetails branch, matching the neighboring UseDetails
branch; without it, re-declaring an inherited VOLATILE/ASYNCHRONOUS
attribute on an ambiguous name downstream produced a confusing
'attribute was already specified' diagnostic instead of leaving
that case for proper ambiguity diagnosis. (A full diagnostic for
that specific case is a separate follow-up; this only restores
correct bookkeeping.)

Assisted-By: AI
---
 flang/lib/Semantics/resolve-names.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 37789a69c38299..d88968cbb5c3d7 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -4399,6 +4399,11 @@ FindIntrinsicModuleUseAssociationRule(
 
 void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
     Symbol &originalLocal, const Symbol &useSymbol) {
+  // These attributes are never inherited from the use-associated symbol:
+  // PUBLIC/PRIVATE accessibility and SAVE are properties of the local
+  // declaration, not of the entity being use-associated.
+  static constexpr Attrs notInheritedFromUseAttrs{
+      Attr::PUBLIC, Attr::PRIVATE, Attr::SAVE};
   Symbol *localSymbol{&originalLocal};
   if (auto *details{localSymbol->detailsIf<UseErrorDetails>()}) {
     details->add_occurrence(location, useSymbol);
@@ -4430,13 +4435,13 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
       // latter would produce a local UseDetails whose target module file
       // omits the name, so it would not survive a module file round trip.
       localSymbol->set_details(UseErrorDetails{*useError});
-      localSymbol->attrs() =
-          useSymbol.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE, Attr::SAVE};
+      localSymbol->attrs() = useSymbol.attrs() & ~notInheritedFromUseAttrs;
+      localSymbol->implicitAttrs() =
+          localSymbol->attrs() & Attrs{Attr::ASYNCHRONOUS, Attr::VOLATILE};
       return;
     } else { // just create UseDetails
       localSymbol->set_details(UseDetails{localName, useSymbol});
-      localSymbol->attrs() =
-          useSymbol.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE, Attr::SAVE};
+      localSymbol->attrs() = useSymbol.attrs() & ~notInheritedFromUseAttrs;
       localSymbol->implicitAttrs() =
           localSymbol->attrs() & Attrs{Attr::ASYNCHRONOUS, Attr::VOLATILE};
       localSymbol->flags() = useSymbol.flags();

>From 12334db4db94cc27e22af2a8528b1fee327450a3 Mon Sep 17 00:00:00 2001
From: Mark Leair <leairmark at gmail.com>
Date: Wed, 7 Oct 2026 15:58:45 -0700
Subject: [PATCH 3/3] [flang][Semantics][test] Add direct-reference and
 generic-merge tests

resolve133.f90 checks that referencing (rather than merely
re-exporting) an ambiguous name produced through a chain of
whole-module and renaming USEs is still diagnosed as ambiguous
within a single compilation.

modfile89.f90 gains GPK/GVM/GMIDDLE/GDOWN, a positive counterpart
to the existing cases: two modules that legally merge into a single
generic interface (not a poison pill) must continue to work,
including being renamed by an only-list USE and called through both
merged specifics, across the module file boundary.

Assisted-By: AI
---
 flang/test/Semantics/modfile89.f90  | 36 +++++++++++++++++++++++++++++
 flang/test/Semantics/resolve133.f90 | 34 +++++++++++++++++++++++++++
 2 files changed, 70 insertions(+)
 create mode 100644 flang/test/Semantics/resolve133.f90

diff --git a/flang/test/Semantics/modfile89.f90 b/flang/test/Semantics/modfile89.f90
index bd051de92e525e..e4bd99bc7d4f1f 100644
--- a/flang/test/Semantics/modfile89.f90
+++ b/flang/test/Semantics/modfile89.f90
@@ -32,6 +32,13 @@
 ! rather than its early-return whole-module path.  DTMIDDLE only pins
 ! the precondition (its module file omits the ambiguous name either way);
 ! DTDOWN is the one that catches a regression.
+!
+! GDOWN is the opposite case, as a check that the fix does not overreach:
+! GPK and GVM each contribute one specific procedure to a generic named F,
+! which is a legal (non-ambiguous) merge, not a poison pill, so it must
+! continue to work -- including being renamed by an only-list USE and
+! actually called through both merged specifics -- across the module file
+! boundary.
 
 !--- defs.f90
 module pk
@@ -70,12 +77,41 @@ module dtmiddle
 module dtdown
   use dtmiddle
 end module
+module gpk
+  interface f
+    module procedure fi
+  end interface
+contains
+  integer function fi(x)
+    integer :: x
+    fi = x
+  end function
+end module
+module gvm
+  interface f
+    module procedure fr
+  end interface
+contains
+  real function fr(x)
+    real :: x
+    fr = x
+  end function
+end module
+module gmiddle
+  use gpk
+  use gvm
+end module
+module gdown
+  use gmiddle, only: g => f
+end module
 
 !--- use.f90
 use down
 use down_only
 use down_rename
 use dtdown
+use gdown
+print *, g(1), g(1.0)
 end
 
 ! PK: module pk
diff --git a/flang/test/Semantics/resolve133.f90 b/flang/test/Semantics/resolve133.f90
new file mode 100644
index 00000000000000..afd34cc148f3ff
--- /dev/null
+++ b/flang/test/Semantics/resolve133.f90
@@ -0,0 +1,34 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+! A module that re-exports an ambiguous (never-referenced, and therefore
+! legal per F2023 14.2.2 p8) USE-associated name must still diagnose a
+! reference to that name as ambiguous, including through a renaming
+! only-list USE, within a single compilation (as opposed to across
+! separately compiled module files, where the ambiguous name is omitted
+! from the producing module's own module file; see modfile89.f90).
+module pk133
+  integer :: x
+end module
+module vm133
+  integer :: x
+end module
+module middle133
+  use pk133
+  use vm133
+end module
+module down133
+  use middle133
+contains
+  subroutine check
+    ! ERROR: Reference to 'x' is ambiguous
+    print *, x
+  end subroutine
+end module
+module down133_rename
+  use middle133, only: y => x
+contains
+  subroutine check
+    ! ERROR: Reference to 'y' is ambiguous
+    print *, y
+  end subroutine
+end module



More information about the flang-commits mailing list