[flang-commits] [flang] [flang] Update PUBLIC and PRIVATE accessibility to Fortran 2018 (PR #177596)
via flang-commits
flang-commits at lists.llvm.org
Tue May 5 14:02:40 PDT 2026
https://github.com/tmjbios updated https://github.com/llvm/llvm-project/pull/177596
>From 7f0798d56c63d814cc6a6cc2f0a1ad73b748645a Mon Sep 17 00:00:00 2001
From: "Ted M. Johnson" <tedmjohnson at protonmail.com>
Date: Thu, 23 Apr 2026 17:15:29 -0600
Subject: [PATCH 1/2] [flang] Update PUBLIC and PRIVATE accessibility to
Fortran 2018
This patch enforces PUBLIC and PRIVATE accessibility as defined
starting in F2018, and passed initially as paper J3/13-327r3;
the brief description below is from "What's New In Fortran 2018"
by John Reid:
If a module A uses module B, the default accessibility for entities
it accesses from B is that of A. Specifying another accessibility
for each entity is awkward and error prone. It is now possible
for the name of a module to be included in the list of names of
entities made public or private on a public or private statement.
This sets the default for all entities accessed from that module.
In the F2023 standard, this is clause 8.6.1.
---
flang/include/flang/Semantics/scope.h | 14 ++++
flang/lib/Semantics/resolve-names.cpp | 72 +++++++++++++++++++
.../Semantics/modfile-accessibility01.f90 | 27 +++++++
.../Semantics/modfile-accessibility02.f90 | 26 +++++++
.../Semantics/modfile-accessibility03.f90 | 14 ++++
.../Semantics/modfile-accessibility04.f90 | 25 +++++++
6 files changed, 178 insertions(+)
create mode 100644 flang/test/Semantics/modfile-accessibility01.f90
create mode 100644 flang/test/Semantics/modfile-accessibility02.f90
create mode 100644 flang/test/Semantics/modfile-accessibility03.f90
create mode 100644 flang/test/Semantics/modfile-accessibility04.f90
diff --git a/flang/include/flang/Semantics/scope.h b/flang/include/flang/Semantics/scope.h
index 16553fe692e79..c48cde86cfef2 100644
--- a/flang/include/flang/Semantics/scope.h
+++ b/flang/include/flang/Semantics/scope.h
@@ -260,6 +260,17 @@ class Scope {
void add_importName(const SourceName &);
+ // When a module name appears in a PUBLIC or PRIVATE <access-stmt>, all
+ // entities accessed from that module inherit the accessibility attribute.
+ std::optional<Attr> GetModuleAccessibility(const Symbol &module) const {
+ auto it = useModuleAccessibility_.find(&module);
+ return it != useModuleAccessibility_.end() ? std::optional{it->second}
+ : std::nullopt;
+ }
+ bool SetModuleAccessibility(const Symbol &module, Attr access) {
+ return useModuleAccessibility_.emplace(&module, access).second;
+ }
+
// These members pertain to instantiations of parameterized derived types.
const DerivedTypeSpec *derivedTypeSpec() const { return derivedTypeSpec_; }
DerivedTypeSpec *derivedTypeSpec() { return derivedTypeSpec_; }
@@ -330,6 +341,9 @@ class Scope {
const DeclTypeSpec &MakeLengthlessType(DeclTypeSpec &&);
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Scope &);
+
+ // <access-stmt> Map for explicit PUBLIC/PRIVATE module USE
+ std::map<const Symbol *, Attr> useModuleAccessibility_;
};
// Inline so that it can be called from Evaluate without a link-time dependency.
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 839f8edeceb97..0f4fd78a92b46 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -846,6 +846,14 @@ class ModuleVisitor : public virtual ScopeHandler {
std::set<SourceName> intrinsicUses_;
std::set<SourceName> nonIntrinsicUses_;
+ void ApplyModuleAccessibility(Symbol &symbol) {
+ if (useModuleScope_ && useModuleScope_->symbol()) {
+ if (auto accessibility{
+ currScope().GetModuleAccessibility(*useModuleScope_->symbol())}) {
+ symbol.attrs().set(*accessibility);
+ }
+ }
+ }
Symbol &SetAccess(const SourceName &, Attr attr, Symbol * = nullptr);
// A rename in a USE statement: local => use
struct SymbolRename {
@@ -4081,6 +4089,7 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
localSymbol->implicitAttrs() =
localSymbol->attrs() & Attrs{Attr::ASYNCHRONOUS, Attr::VOLATILE};
localSymbol->flags() = useSymbol.flags();
+ ApplyModuleAccessibility(*localSymbol);
return;
}
}
@@ -4323,6 +4332,8 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
useUltimate.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE},
UseDetails{localName, useUltimate})};
newSymbol.flags() = useSymbol.flags();
+ // Apply module accessibility if specified
+ ApplyModuleAccessibility(newSymbol);
return;
} else {
for (const auto &ref : useGeneric->specificProcs()) {
@@ -4379,6 +4390,8 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
localSymbol->attrs() =
useSymbol.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE};
localSymbol->flags() = useSymbol.flags();
+ // Apply module accessibility if specified
+ ApplyModuleAccessibility(*localSymbol);
AddGenericUse(*localGeneric, localName, useUltimate);
// Don't duplicate specific procedures.
std::size_t originalLocalSpecifics{localGeneric->specificProcs().size()};
@@ -4421,6 +4434,7 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
useUltimate.attrs() & ~Attrs{Attr::PUBLIC, Attr::PRIVATE},
std::move(generic))};
newSymbol.flags() = useUltimate.flags();
+ ApplyModuleAccessibility(newSymbol);
auto &newUseGeneric{newSymbol.get<GenericDetails>()};
AddGenericUse(newUseGeneric, localName, useUltimate);
newUseGeneric.AddUse(*localSymbol);
@@ -9810,6 +9824,41 @@ bool ModuleVisitor::Pre(const parser::AccessStmt &x) {
for (const auto &accessId : accessIds) {
GenericSpecInfo info{accessId.v.value()};
auto *symbol{FindInScope(info.symbolName())};
+ // An access-spec may have an access-name (module) in order to set
+ // the default accessibility for everything USE-associated from
+ // that module. The module name won't be in the current scope,
+ // so we need to check the global scope.
+ if (info.kind().IsName()) {
+ const Symbol *moduleSymbol{nullptr};
+ Symbol *resolveSymbol{nullptr};
+ if (symbol) {
+ // Check if symbol in current scope is USE-associated from a module
+ const Symbol &ultimate{symbol->GetUltimate()};
+ if (ultimate.has<ModuleDetails>()) {
+ moduleSymbol = &ultimate;
+ resolveSymbol = symbol;
+ }
+ } else {
+ // Look for a module with this name in the global scope
+ if (auto it{context().globalScope().find(info.symbolName())};
+ it != context().globalScope().end()) {
+ Symbol &globalSymbol{*it->second};
+ if (globalSymbol.has<ModuleDetails>()) {
+ moduleSymbol = &globalSymbol;
+ resolveSymbol = &globalSymbol;
+ }
+ }
+ }
+ if (moduleSymbol) {
+ if (!currScope().SetModuleAccessibility(*moduleSymbol, accessAttr)) {
+ Say(info.symbolName(),
+ "The accessibility of entities from module '%s' has already been specified"_err_en_US,
+ moduleSymbol->name());
+ }
+ info.Resolve(resolveSymbol);
+ continue;
+ }
+ }
if (!symbol && !info.kind().IsName()) {
symbol = &MakeSymbol(info.symbolName(), Attrs{}, GenericDetails{});
}
@@ -10139,6 +10188,29 @@ void ResolveNamesVisitor::FinishSpecificationPart(
if (inInterfaceBlock()) {
FinishNamelists(); // NAMELIST is useless in an interface, but allowed
}
+
+ // Apply deferred module accessibility: F2023: Clause 8.6.1
+ // An <access-spec> with an <access-id> of a module name sets the
+ // accessibility for all entities USE-associated from that module.
+ for (auto &pair : currScope()) {
+ auto &symbol{*pair.second};
+ if (const auto *useDetails{symbol.detailsIf<UseDetails>()}) {
+ // If symbol already has explicit accessibility, skip it
+ if (symbol.attrs().HasAny({Attr::PUBLIC, Attr::PRIVATE})) {
+ continue;
+ }
+ // Find the module that this symbol came from
+ const Symbol &useSymbol{useDetails->symbol()};
+ const Scope *moduleScope{FindModuleContaining(useSymbol.owner())};
+ if (moduleScope && moduleScope->symbol()) {
+ if (auto accessibility{
+ currScope().GetModuleAccessibility(*moduleScope->symbol())}) {
+ symbol.attrs().set(*accessibility);
+ }
+ }
+ }
+ }
+
for (auto &pair : currScope()) {
auto &symbol{*pair.second};
if (inInterfaceBlock()) {
diff --git a/flang/test/Semantics/modfile-accessibility01.f90 b/flang/test/Semantics/modfile-accessibility01.f90
new file mode 100644
index 0000000000000..ae7b0fba62701
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility01.f90
@@ -0,0 +1,27 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Test that entities from a module with PRIVATE modulename are not accessible from further USE
+module basemod
+ implicit none
+ type :: base_type
+ integer :: x
+ end type
+ integer :: base_var = 42
+contains
+ subroutine base_sub()
+ end subroutine
+end module
+
+module middlemod
+ use basemod
+ implicit none
+ private basemod ! Make all entities from basemod private
+ integer :: middle_var = 100
+end module
+
+program main
+ ! ERROR: 'base_var' is PRIVATE in 'middlemod'
+ use middlemod, only: base_var
+ ! ERROR: 'base_sub' is PRIVATE in 'middlemod'
+ use middlemod, only: base_sub
+ implicit none
+end
diff --git a/flang/test/Semantics/modfile-accessibility02.f90 b/flang/test/Semantics/modfile-accessibility02.f90
new file mode 100644
index 0000000000000..8361fd9434a29
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility02.f90
@@ -0,0 +1,26 @@
+! RUN: %flang_fc1 -fsyntax-only %s
+! Test that entities from a module with PUBLIC modulename remain accessible
+module basemod
+ implicit none
+ private !-- Default is private
+ integer, public :: base_public_var = 42
+ integer :: base_private_var = 99
+end module
+
+module middlemod
+ use basemod
+ implicit none
+ private !-- Default is private
+ public basemod !-- But entities from basemod should remain public
+ integer :: middle_private_var = 100
+end module
+
+program main
+ use middlemod
+ implicit none
+ integer :: x
+ ! base_public_var should be accessible because of "public basemod"
+ x = base_public_var
+ ! This should compile without errors
+ print *, x
+end program
diff --git a/flang/test/Semantics/modfile-accessibility03.f90 b/flang/test/Semantics/modfile-accessibility03.f90
new file mode 100644
index 0000000000000..b35763cadf356
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility03.f90
@@ -0,0 +1,14 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Test error when same module is given conflicting accessibility
+module basemod
+ implicit none
+ integer :: base_var = 42
+end module
+
+module testmod
+ use basemod
+ implicit none
+ private basemod
+ !ERROR: The accessibility of entities from module 'basemod' has already been specified
+ public basemod
+end module
diff --git a/flang/test/Semantics/modfile-accessibility04.f90 b/flang/test/Semantics/modfile-accessibility04.f90
new file mode 100644
index 0000000000000..4fc02156b9a00
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility04.f90
@@ -0,0 +1,25 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Test that explicit PUBLIC/PRIVATE on individual symbols overrides module-level accessibility
+module basemod
+ implicit none
+ integer :: var1 = 1
+ integer :: var2 = 2
+ integer :: var3 = 3
+end module
+
+module middlemod
+ use basemod
+ implicit none
+ private basemod ! Make all entities from basemod private by default
+ public :: var2 ! But explicitly make var2 public
+end module
+
+program main
+ ! var2 should be accessible because of explicit "public :: var2"
+ use middlemod, only: var2
+ ! ERROR: 'var1' is PRIVATE in 'middlemod'
+ use middlemod, only: var1
+ ! ERROR: 'var3' is PRIVATE in 'middlemod'
+ use middlemod, only: var3
+ implicit none
+end
>From 1f168dd433f837b5ad3c7efbd3d145f480d8d134 Mon Sep 17 00:00:00 2001
From: Ted Johnson <tedmjohnson at protonmail.com>
Date: Tue, 5 May 2026 11:43:59 -0500
Subject: [PATCH 2/2] [flang] Pre-scan approach, without Scope-level state
Replace Symbol-keyed scope storage with a SourceName-keyed map in
SpecificationPartState. Eliminate FinishSpecificationPart pass
with a pre-scan of ACCESS statements before the USE walk. Use a
cleaner implementation for processing PUBLIC/PRIVATE updates.
Add two new tests for re-naming and re-exporting.
Co-authored-by: Kevin Wyatt <kwyatt at hpe.com>
---
flang/include/flang/Semantics/scope.h | 14 --
flang/lib/Semantics/resolve-names.cpp | 168 +++++++++++++-----
.../Semantics/modfile-accessibility03.f90 | 2 +-
.../Semantics/modfile-accessibility05.f90 | 103 +++++++++++
.../Semantics/modfile-accessibility06.f90 | 151 ++++++++++++++++
5 files changed, 374 insertions(+), 64 deletions(-)
create mode 100644 flang/test/Semantics/modfile-accessibility05.f90
create mode 100644 flang/test/Semantics/modfile-accessibility06.f90
diff --git a/flang/include/flang/Semantics/scope.h b/flang/include/flang/Semantics/scope.h
index c48cde86cfef2..16553fe692e79 100644
--- a/flang/include/flang/Semantics/scope.h
+++ b/flang/include/flang/Semantics/scope.h
@@ -260,17 +260,6 @@ class Scope {
void add_importName(const SourceName &);
- // When a module name appears in a PUBLIC or PRIVATE <access-stmt>, all
- // entities accessed from that module inherit the accessibility attribute.
- std::optional<Attr> GetModuleAccessibility(const Symbol &module) const {
- auto it = useModuleAccessibility_.find(&module);
- return it != useModuleAccessibility_.end() ? std::optional{it->second}
- : std::nullopt;
- }
- bool SetModuleAccessibility(const Symbol &module, Attr access) {
- return useModuleAccessibility_.emplace(&module, access).second;
- }
-
// These members pertain to instantiations of parameterized derived types.
const DerivedTypeSpec *derivedTypeSpec() const { return derivedTypeSpec_; }
DerivedTypeSpec *derivedTypeSpec() { return derivedTypeSpec_; }
@@ -341,9 +330,6 @@ class Scope {
const DeclTypeSpec &MakeLengthlessType(DeclTypeSpec &&);
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Scope &);
-
- // <access-stmt> Map for explicit PUBLIC/PRIVATE module USE
- std::map<const Symbol *, Attr> useModuleAccessibility_;
};
// Inline so that it can be called from Evaluate without a link-time dependency.
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 0f4fd78a92b46..56a791daf56d9 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -770,6 +770,13 @@ class ScopeHandler : public ImplicitRulesVisitor {
std::set<SourceName> entities; // names of entities with save attr
std::set<SourceName> commons; // names of common blocks with save attr
} saveInfo;
+ // F2023 8.6.1: module-name access-ids from PUBLIC/PRIVATE statements;
+ std::map<SourceName, Attr> moduleAccessibility;
+ // F2023 8.6.1: source positions of duplicate module-name access-ids
+ std::set<const char *> duplicateModuleAccessibility;
+ // F2023 8.6.1: for each USE-associated local symbol name, the set of
+ // module names that contributed it via USE association.
+ std::map<SourceName, std::set<SourceName>> symbolUseModules;
} specPartState_;
// Some declaration processing can and should be deferred to
@@ -825,6 +832,8 @@ class ModuleVisitor : public virtual ScopeHandler {
Symbol &AddGenericUse(GenericDetails &, const SourceName &, const Symbol &);
void AddAndCheckModuleUse(SourceName, bool isIntrinsic);
void CollectUseRenames(const parser::UseStmt &);
+ void CollectModuleAccessibility(
+ const std::list<parser::DeclarationConstruct> &);
void ClearUseRenames() { useRenames_.clear(); }
void ClearUseOnly() { useOnly_.clear(); }
void ClearModuleUses() {
@@ -847,11 +856,12 @@ class ModuleVisitor : public virtual ScopeHandler {
std::set<SourceName> nonIntrinsicUses_;
void ApplyModuleAccessibility(Symbol &symbol) {
+ // F2023 8.6.1: Record that useModuleScope_ contributed this symbol.
+ // The actual accessibility is computed in FinishSpecificationPart()
+ // once all contributing modules are known.
if (useModuleScope_ && useModuleScope_->symbol()) {
- if (auto accessibility{
- currScope().GetModuleAccessibility(*useModuleScope_->symbol())}) {
- symbol.attrs().set(*accessibility);
- }
+ specPartState_.symbolUseModules[symbol.name()].insert(
+ useModuleScope_->symbol()->name());
}
}
Symbol &SetAccess(const SourceName &, Attr attr, Symbol * = nullptr);
@@ -3786,6 +3796,47 @@ void ModuleVisitor::CollectUseRenames(const parser::UseStmt &useStmt) {
useStmt.u);
}
+// F2023 8.6.1: Pre-scan PUBLIC/PRIVATE statements for plain name access-ids,
+// recording (name, attr) into specPartState_.moduleAccessibility before USE
+// statements are walked. The scan is purely syntactic - all Name access-ids
+// are inserted regardless of whether they name modules or local entities.
+void ModuleVisitor::CollectModuleAccessibility(
+ const std::list<parser::DeclarationConstruct> &decls) {
+ auto &map{specPartState_.moduleAccessibility};
+ for (const auto &decl : decls) {
+ const auto *specConst{std::get_if<parser::SpecificationConstruct>(&decl.u)};
+ if (!specConst) {
+ continue;
+ }
+ const auto *otherStmt{
+ std::get_if<parser::Statement<parser::OtherSpecificationStmt>>(
+ &specConst->u)};
+ if (!otherStmt) {
+ continue;
+ }
+ const auto *accessInd{std::get_if<common::Indirection<parser::AccessStmt>>(
+ &otherStmt->statement.u)};
+ if (!accessInd) {
+ continue;
+ }
+ const auto &[accessSpec, accessIds] = accessInd->value().t;
+ Attr attr{accessSpec.v == parser::AccessSpec::Kind::Private ? Attr::PRIVATE
+ : Attr::PUBLIC};
+ for (const auto &accessId : accessIds) {
+ const auto &spec{accessId.v.value()};
+ const auto *name{std::get_if<parser::Name>(&spec.u)};
+ if (!name) {
+ continue;
+ }
+ auto [it, inserted]{map.emplace(name->source, attr)};
+ if (!inserted) {
+ specPartState_.duplicateModuleAccessibility.insert(
+ name->source.begin());
+ }
+ }
+ }
+}
+
bool ModuleVisitor::Pre(const parser::Rename::Names &x) {
const auto &localName{std::get<0>(x.t)};
const auto &useName{std::get<1>(x.t)};
@@ -4096,7 +4147,9 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
Symbol &localUltimate{localSymbol->GetUltimate()};
if (&localUltimate == &useUltimate) {
- // use-associating the same symbol again -- ok
+ // use-associating the same symbol again -- ok, but record the
+ // contributing module for multi-source accessibility (F2023 8.6.1).
+ ApplyModuleAccessibility(*localSymbol);
return;
}
if (useUltimate.owner().IsModule() && localUltimate.owner().IsSubmodule() &&
@@ -9824,36 +9877,30 @@ bool ModuleVisitor::Pre(const parser::AccessStmt &x) {
for (const auto &accessId : accessIds) {
GenericSpecInfo info{accessId.v.value()};
auto *symbol{FindInScope(info.symbolName())};
- // An access-spec may have an access-name (module) in order to set
- // the default accessibility for everything USE-associated from
- // that module. The module name won't be in the current scope,
- // so we need to check the global scope.
+ // F2023 8.6.1: a module-name access-id sets default accessibility for
+ // entities USE associated from that module. The (name, attr) pair was
+ // already recorded by CollectModuleAccessibility(); here we just verify
+ // that it resolves to a module, and skip the per-entity SetAccess path.
if (info.kind().IsName()) {
const Symbol *moduleSymbol{nullptr};
Symbol *resolveSymbol{nullptr};
- if (symbol) {
- // Check if symbol in current scope is USE-associated from a module
- const Symbol &ultimate{symbol->GetUltimate()};
- if (ultimate.has<ModuleDetails>()) {
- moduleSymbol = &ultimate;
- resolveSymbol = symbol;
- }
- } else {
- // Look for a module with this name in the global scope
+ if (!symbol) {
if (auto it{context().globalScope().find(info.symbolName())};
- it != context().globalScope().end()) {
- Symbol &globalSymbol{*it->second};
- if (globalSymbol.has<ModuleDetails>()) {
- moduleSymbol = &globalSymbol;
- resolveSymbol = &globalSymbol;
- }
+ it != context().globalScope().end() &&
+ it->second->has<ModuleDetails>()) {
+ moduleSymbol = &*it->second;
+ resolveSymbol = &*it->second;
}
+ } else if (symbol->GetUltimate().has<ModuleDetails>()) {
+ moduleSymbol = &symbol->GetUltimate();
+ resolveSymbol = symbol;
}
if (moduleSymbol) {
- if (!currScope().SetModuleAccessibility(*moduleSymbol, accessAttr)) {
+ if (specPartState_.duplicateModuleAccessibility.count(
+ info.symbolName().begin())) {
Say(info.symbolName(),
- "The accessibility of entities from module '%s' has already been specified"_err_en_US,
- moduleSymbol->name());
+ "The name of module '%s' shall appear at most once in all of the ACCESS statements in a module"_err_en_US,
+ moduleSymbol->name()); // F2023 8.6.1: (C876)
}
info.Resolve(resolveSymbol);
continue;
@@ -9977,6 +10024,11 @@ bool ResolveNamesVisitor::Pre(const parser::SpecificationPart &x) {
Walk(accDecls);
Walk(ompDecls);
Walk(compilerDirectives);
+ // F2023 8.6.1: collect module-name access-ids before USE walk so that
+ // DoAddUse() can record contributing modules regardless of statement order.
+ if (currScope().IsModule()) {
+ CollectModuleAccessibility(decls);
+ }
for (const auto &useStmt : useStmts) {
CollectUseRenames(useStmt.statement.value());
}
@@ -10183,34 +10235,52 @@ void ResolveNamesVisitor::CreateGeneric(const parser::GenericSpec &x) {
void ResolveNamesVisitor::FinishSpecificationPart(
const std::list<parser::DeclarationConstruct> &decls) {
misparsedStmtFuncFound_ = false;
- funcResultStack().CompleteFunctionResultType();
- CheckImports();
- if (inInterfaceBlock()) {
- FinishNamelists(); // NAMELIST is useless in an interface, but allowed
- }
-
- // Apply deferred module accessibility: F2023: Clause 8.6.1
- // An <access-spec> with an <access-id> of a module name sets the
- // accessibility for all entities USE-associated from that module.
- for (auto &pair : currScope()) {
- auto &symbol{*pair.second};
- if (const auto *useDetails{symbol.detailsIf<UseDetails>()}) {
- // If symbol already has explicit accessibility, skip it
- if (symbol.attrs().HasAny({Attr::PUBLIC, Attr::PRIVATE})) {
+ // F2023 8.6.1: apply module-name accessibility to USE associated symbols
+ // now that all contributing modules are known. Skip symbols which already
+ // carry PUBLIC or PRIVATE (from per-entity access-ids). The rule
+ // applies only when every contributing module is named in an access-stmt:
+ // * any PUBLIC => PUBLIC
+ // * all in map && all PRIVATE => PRIVATE
+ // * not all in map => skip (leave unset)
+ if (!specPartState_.symbolUseModules.empty() &&
+ !specPartState_.moduleAccessibility.empty()) {
+ const auto &accessMap{specPartState_.moduleAccessibility};
+ for (auto &[symName, symbolRef] : currScope()) {
+ Symbol &symbol{*symbolRef};
+ auto symIter{specPartState_.symbolUseModules.find(symbol.name())};
+ if (symIter == specPartState_.symbolUseModules.end()) {
continue;
}
- // Find the module that this symbol came from
- const Symbol &useSymbol{useDetails->symbol()};
- const Scope *moduleScope{FindModuleContaining(useSymbol.owner())};
- if (moduleScope && moduleScope->symbol()) {
- if (auto accessibility{
- currScope().GetModuleAccessibility(*moduleScope->symbol())}) {
- symbol.attrs().set(*accessibility);
+ if (symbol.attrs().HasAny({Attr::PUBLIC, Attr::PRIVATE})) {
+ continue; // per-entity access-id already applied
+ }
+ // F2023 8.6.1: the module-name accessibility rule applies only when
+ // "the name of every module from which it is accessed appears in an
+ // access-stmt in the scoping unit".
+ bool allInMap{true}, anyPublic{false};
+ for (const SourceName &moduleName : symIter->second) {
+ auto modIter{accessMap.find(moduleName)};
+ if (modIter == accessMap.end()) {
+ allInMap = false;
+ break;
+ }
+ if (modIter->second == Attr::PUBLIC) {
+ anyPublic = true;
}
}
+ if (!allInMap) {
+ continue; // not every contributing module named in access-stmts
+ }
+ // All contributing modules are named: any PUBLIC => PUBLIC, else all
+ // are PRIVATE => PRIVATE.
+ symbol.attrs().set(anyPublic ? Attr::PUBLIC : Attr::PRIVATE);
}
}
-
+ funcResultStack().CompleteFunctionResultType();
+ CheckImports();
+ if (inInterfaceBlock()) {
+ FinishNamelists(); // NAMELIST is useless in an interface, but allowed
+ }
for (auto &pair : currScope()) {
auto &symbol{*pair.second};
if (inInterfaceBlock()) {
diff --git a/flang/test/Semantics/modfile-accessibility03.f90 b/flang/test/Semantics/modfile-accessibility03.f90
index b35763cadf356..82f4a51caecd1 100644
--- a/flang/test/Semantics/modfile-accessibility03.f90
+++ b/flang/test/Semantics/modfile-accessibility03.f90
@@ -9,6 +9,6 @@ module testmod
use basemod
implicit none
private basemod
- !ERROR: The accessibility of entities from module 'basemod' has already been specified
+ !ERROR: The name of module 'basemod' shall appear at most once in all of the ACCESS statements in a module
public basemod
end module
diff --git a/flang/test/Semantics/modfile-accessibility05.f90 b/flang/test/Semantics/modfile-accessibility05.f90
new file mode 100644
index 0000000000000..6c00cb7a434a9
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility05.f90
@@ -0,0 +1,103 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Tests F2023 8.6.1 p2: module-name access-ids when the same entity is
+! accessible via more than one USE-associated module.
+! - all source modules PRIVATE -> entity is PRIVATE
+! - any source module PUBLIC -> entity is PUBLIC (PUBLIC wins)
+! - not all source modules named in access-stmts -> module default applies
+module base
+ implicit none
+ integer :: x = 10
+ integer :: y = 20
+end module
+
+! Both re-export x and y from base (default PUBLIC).
+module re_a
+ use base
+end module
+
+module re_b
+ use base
+end module
+
+! Both source modules marked PRIVATE: x and y are PRIVATE.
+module both_private
+ use re_a
+ use re_b
+ implicit none
+ private re_a
+ private re_b
+end module
+
+! One source module PRIVATE, one PUBLIC: PUBLIC wins; x and y are PUBLIC.
+module mixed_access
+ use re_a
+ use re_b
+ implicit none
+ private re_a
+ public re_b
+end module
+
+! Only re_a has an access-stmt; re_b does not.
+! F2023 8.6.1 requires ALL source modules to be named for the rule to apply;
+! x and y fall back to the module default (PUBLIC).
+module partial_stmt
+ use re_a
+ use re_b
+ implicit none
+ private re_a
+end module
+
+! Case 4: merged generics from two modules with mixed access-stmts.
+! The generic interface 'op' should be PUBLIC (because gen_re_b is PUBLIC).
+module gen_base
+ implicit none
+ interface op
+ module procedure op_int
+ end interface
+contains
+ integer function op_int(a)
+ integer, intent(in) :: a
+ op_int = a
+ end function
+end module
+
+module gen_re_a
+ use gen_base
+end module
+
+module gen_re_b
+ use gen_base
+end module
+
+module gen_mixed
+ use gen_re_a
+ use gen_re_b
+ implicit none
+ private gen_re_a
+ public gen_re_b
+end module
+
+program test
+ implicit none
+ ! Case 1: both source modules PRIVATE -> entities are PRIVATE.
+ block
+ ! ERROR: 'x' is PRIVATE in 'both_private'
+ use both_private, only: x
+ end block
+ block
+ ! ERROR: 'y' is PRIVATE in 'both_private'
+ use both_private, only: y
+ end block
+ ! Case 2: PUBLIC wins over PRIVATE -> x and y are accessible (no error).
+ block
+ use mixed_access, only: x, y
+ end block
+ ! Case 3: partial access-stmt -> module default PUBLIC applies (no error).
+ block
+ use partial_stmt, only: x, y
+ end block
+ ! Case 4: merged generic PUBLIC via gen_re_b -> accessible (no error).
+ block
+ use gen_mixed, only: op
+ end block
+end program
diff --git a/flang/test/Semantics/modfile-accessibility06.f90 b/flang/test/Semantics/modfile-accessibility06.f90
new file mode 100644
index 0000000000000..8b49ed3f92a31
--- /dev/null
+++ b/flang/test/Semantics/modfile-accessibility06.f90
@@ -0,0 +1,151 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+! Tests F2023 8.6.1 p2: module-name accessibility with USE renames.
+! Exercises rename at the re-export layer and at the consuming USE site.
+
+module origin
+ implicit none
+ integer :: velocity = 100
+ integer :: altitude = 200
+end module
+
+! relay_alpha re-exports origin's symbols under their original names.
+module relay_alpha
+ use origin
+end module
+
+! relay_beta re-exports origin::velocity under the name 'speed'.
+module relay_beta
+ use origin, only: speed => velocity
+end module
+
+! relay_gamma re-exports origin's symbols under their original names
+! (same as relay_alpha, for multi-source tests).
+module relay_gamma
+ use origin
+end module
+
+! Case 1: Same ultimate via rename -- both PRIVATE.
+! relay_alpha exports 'velocity', relay_beta exports 'speed'.
+! These are different local names, so no collision; each gets its own
+! module's accessibility independently.
+module rename_both_private
+ use relay_alpha, only: velocity
+ use relay_beta, only: speed
+ implicit none
+ private relay_alpha
+ private relay_beta
+end module
+
+! Case 2: Same ultimate via rename -- mixed access on separate names.
+! velocity comes from relay_alpha (PRIVATE), speed comes from relay_beta
+! (PUBLIC). No collision; each reflects its source module's access.
+module rename_mixed_separate
+ use relay_alpha, only: velocity
+ use relay_beta, only: speed
+ implicit none
+ private relay_alpha
+ public relay_beta
+end module
+
+! Case 3: Consumer-side rename merges two sources under one local name.
+! Both relay_alpha::velocity and relay_beta::speed have the same ultimate
+! (origin::velocity). The consumer renames speed back to 'velocity',
+! so both contribute to local name 'velocity'.
+! relay_alpha is PRIVATE, relay_gamma is PUBLIC -> PUBLIC wins.
+module rename_consumer_merge
+ use relay_alpha, only: velocity
+ use relay_gamma, only: velocity
+ implicit none
+ private relay_alpha
+ public relay_gamma
+end module
+
+! Case 4: Consumer-side rename merges two sources, both PRIVATE.
+module rename_consumer_both_private
+ use relay_alpha, only: velocity
+ use relay_gamma, only: velocity
+ implicit none
+ private relay_alpha
+ private relay_gamma
+end module
+
+! Case 5: Consumer-side rename with partial access-stmt.
+! relay_alpha is PRIVATE, relay_gamma has no access-stmt.
+! Not all contributing modules are named -> default PUBLIC applies.
+module rename_consumer_partial
+ use relay_alpha, only: velocity
+ use relay_gamma, only: velocity
+ implicit none
+ private relay_alpha
+end module
+
+! Case 6: Re-export rename brought into consumer under yet another name.
+! relay_beta exports 'speed' (renamed from origin::velocity).
+! Consumer renames 'speed' to 'rate'. Both PRIVATE -> PRIVATE.
+module rename_chain_private
+ use relay_alpha, only: rate => velocity
+ use relay_beta, only: rate => speed
+ implicit none
+ private relay_alpha
+ private relay_beta
+end module
+
+! Case 7: Re-export rename chain, mixed access -> PUBLIC wins.
+module rename_chain_mixed
+ use relay_alpha, only: rate => velocity
+ use relay_beta, only: rate => speed
+ implicit none
+ private relay_alpha
+ public relay_beta
+end module
+
+program test_renames
+ implicit none
+
+ ! Case 1: separate names, both PRIVATE.
+ block
+ !ERROR: 'velocity' is PRIVATE in 'rename_both_private'
+ use rename_both_private, only: velocity
+ end block
+ block
+ !ERROR: 'speed' is PRIVATE in 'rename_both_private'
+ use rename_both_private, only: speed
+ end block
+
+ ! Case 2: separate names, mixed access.
+ ! velocity from relay_alpha (PRIVATE), speed from relay_beta (PUBLIC).
+ block
+ !ERROR: 'velocity' is PRIVATE in 'rename_mixed_separate'
+ use rename_mixed_separate, only: velocity
+ end block
+ block
+ use rename_mixed_separate, only: speed ! PUBLIC, no error
+ end block
+
+ ! Case 3: consumer merge under one name, PUBLIC wins (no error).
+ block
+ use rename_consumer_merge, only: velocity
+ end block
+
+ ! Case 4: consumer merge under one name, both PRIVATE.
+ block
+ !ERROR: 'velocity' is PRIVATE in 'rename_consumer_both_private'
+ use rename_consumer_both_private, only: velocity
+ end block
+
+ ! Case 5: consumer merge, partial access-stmt -> default PUBLIC (no error).
+ block
+ use rename_consumer_partial, only: velocity
+ end block
+
+ ! Case 6: rename chain, both PRIVATE.
+ block
+ !ERROR: 'rate' is PRIVATE in 'rename_chain_private'
+ use rename_chain_private, only: rate
+ end block
+
+ ! Case 7: rename chain, mixed -> PUBLIC wins (no error).
+ block
+ use rename_chain_mixed, only: rate
+ end block
+end program
More information about the flang-commits
mailing list