Skip to content

Conversation

@tmjbios
Copy link
Contributor

@tmjbios tmjbios commented Jan 23, 2026

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.

Other minor nit: The Standard Support markdown doc had the "Whats new" links for
'23 and '18, but not for '08 or '03, so I added them when adding Default Accessibility to
the '18 table.

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.
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:semantics labels Jan 23, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 23, 2026

@llvm/pr-subscribers-flang-semantics

Author: tedj (tmjbios)

Changes

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.

Other minor nit: The Standard Support markdown doc had the "Whats new" links for
'23 and '18, but not for '08 or '03, so I added them when adding Default Accessibility to
the '18 table.


Full diff: https://github.com/llvm/llvm-project/pull/177596.diff

7 Files Affected:

  • (modified) flang/docs/FortranStandardsSupport.md (+3-2)
  • (modified) flang/include/flang/Semantics/scope.h (+14)
  • (modified) flang/lib/Semantics/resolve-names.cpp (+72)
  • (added) flang/test/Semantics/modfile-accessibility01.f90 (+27)
  • (added) flang/test/Semantics/modfile-accessibility02.f90 (+26)
  • (added) flang/test/Semantics/modfile-accessibility03.f90 (+14)
  • (added) flang/test/Semantics/modfile-accessibility04.f90 (+25)
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

@tmjbios
Copy link
Contributor Author

tmjbios commented Jan 23, 2026

This passes check-flang and check-flang-rt, but gfortran does not implement this new 2018 feature, so there is a failure in one of llvm-test-suite's gfortran tests (public_private_module.f90).

I have a PR ready for disabling this now-invalid test in llvm-test-suite once this is merged.

const Symbol *moduleSymbol{nullptr};
Symbol *resolveSymbol{nullptr};
if (symbol) {
// Check if symbol in current scope is USE-associated from a module
Copy link
Contributor

@eugeneepshteyn eugeneepshteyn Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this handle the following 3-level module use:

module A
  integer :: x = 1
end module A

module B
  use A
  public :: x ! Re-export x
end module B

module C
  use B
  implicit none
  private ! Everything is private, except what's re-exported from B below
  public :: B
end module C

program main
  use C
  implicit none
  print *, x
end program

(This program works with ifx.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I believe that's one of the test cases.

$ cat eugene.f90 
module A
  integer :: x = 1
end module A

module B
  use A
  public :: x ! Re-export x
end module B

module C
  use B
  implicit none
  private
  public :: B
end module C

program main
  use C
  implicit none
  print *, x
end program

$ flang eugene.f90 

$ ./a.out
 1
$ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:semantics flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants