[flang-commits] [flang] [flang] Update PUBLIC and PRIVATE accessibility to Fortran 2018 (PR #177596)
via flang-commits
flang-commits at lists.llvm.org
Mon Feb 2 05:25:07 PST 2026
https://github.com/tmjbios updated https://github.com/llvm/llvm-project/pull/177596
>From 2da2a7c2416a6a9cb8e7a768f35679bc70f0f477 Mon Sep 17 00:00:00 2001
From: "Ted M. Johnson" <tedmjohnson at protonmail.com>
Date: Wed, 21 Jan 2026 20:38:03 -0700
Subject: [PATCH] [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/docs/FortranStandardsSupport.md | 5 +-
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 +++++++
7 files changed, 181 insertions(+), 2 deletions(-)
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/docs/FortranStandardsSupport.md b/flang/docs/FortranStandardsSupport.md
index 97363dbd048a3..1b2a9b63dcf5e 100644
--- a/flang/docs/FortranStandardsSupport.md
+++ b/flang/docs/FortranStandardsSupport.md
@@ -67,6 +67,7 @@ the multi-image execution. The table entries are based on the document [The new
| Feature | Status | Comments |
|------------------------------------------------------------|--------|---------------------------------------------------------|
| Asynchronous communication | P | Syntax is accepted |
+| Default Accessibility | Y | |
| Teams | N | Multi-image/Coarray feature |
| Image failure | P | Multi-image/Coarray feature. stat_failed_image is added |
| Form team statement | N | Multi-image/Coarray feature |
@@ -89,7 +90,7 @@ the multi-image execution. The table entries are based on the document [The new
| Intrinsic function coshape | N | Multi-image/Coarray feature |
## Fortran 2008
-All features except those listed in the following table are supported.
+All features except those listed in the following table are supported. The new features available in Fortran 2008 are summarized in the document [The New Features in Fortran 2008](https://wg5-fortran.org/N1851-N1900/N1891.pdf)
| Feature | Status | Comments |
|------------------------------------------------------------|--------|---------------------------------------------------------|
@@ -98,7 +99,7 @@ All features except those listed in the following table are supported.
| Internal procedure as an actual argument or pointer target | Y | Current implementation requires stack to be executable. See [FAQ](FAQ.md#why-do-i-get-a-warning-or-an-error-about-an-executable-stack) and [Proposal](InternalProcedureTrampolines.md) |
## Fortran 2003
-All features except those listed in the following table are supported.
+All features except those listed in the following table are supported. The new features available in Fortran 2003 are summarized in the document [The New Features of Fortran 2003](https://wg5-fortran.org/N1551-N1600/N1579.pdf)
| Feature | Status | Comments |
|------------------------------------------------------------|--------|---------------------------------------------------------|
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 057fa1db239c1..4f491e225e0eb 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -840,6 +840,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 {
@@ -3866,6 +3874,7 @@ void ModuleVisitor::DoAddUse(SourceName location, SourceName localName,
localSymbol->implicitAttrs() =
localSymbol->attrs() & Attrs{Attr::ASYNCHRONOUS, Attr::VOLATILE};
localSymbol->flags() = useSymbol.flags();
+ ApplyModuleAccessibility(*localSymbol);
return;
}
}
@@ -4108,6 +4117,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()) {
@@ -4164,6 +4175,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()};
@@ -4206,6 +4219,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);
@@ -9575,6 +9589,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{});
}
@@ -9904,6 +9953,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
More information about the flang-commits
mailing list