[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