[flang-commits] [flang] [flang][Semantics] Break recursion on illegal recursive type in I/O check (PR #194284)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Fri May 1 07:50:22 PDT 2026


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/194284

>From 48eb2606a942dec00e1f88cac9964ea0418d342b Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:15:06 -0400
Subject: [PATCH 01/10] [flang][Semantics] Break recursion on illegal recursive
 type in I/O check

When an illegal recursive derived type (a non-POINTER/non-ALLOCATABLE
component whose type is the enclosing type itself, prohibited by F2023
C749) is used in an I/O list, the offending component is left in the
symbol table after the error is diagnosed. The component-walking helpers
FindUnsafeIoDirectComponent and FindInaccessibleComponent then recurse
through it forever and the compiler loops.

Track the derived types currently on the recursion path in an
inProgress set and bail out when a type is reached again. The set is
threaded through the recursive call and seeded by a thin wrapper at
each entry point so call sites are unchanged.

Fixes #192387

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 37 ++++++++++++++++++++++++++------
 flang/test/Semantics/io12.f90    | 12 +++++++++++
 2 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 46abd3d298d02..0073f1fc6c505 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -15,6 +15,7 @@
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/expression.h"
 #include "flang/Semantics/tools.h"
+#include <set>
 #include <unordered_map>
 
 namespace Fortran::semantics {
@@ -1118,12 +1119,19 @@ void IoChecker::CheckForUselessIomsg() const {
 
 // Seeks out an allocatable or pointer ultimate component that is not
 // nested in a nonallocatable/nonpointer component with a specific
-// defined I/O procedure.
+// defined I/O procedure.  The inProgress set tracks derived types
+// currently on the recursion path to break cycles caused by an illegal
+// recursive type definition (F2023 C749) that has already been
+// diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
-    const DerivedTypeSpec &derived, const Scope &scope) {
+    const DerivedTypeSpec &derived, const Scope &scope,
+    std::set<const Symbol *> &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
+  if (!inProgress.insert(&derived.typeSymbol()).second) {
+    return nullptr;
+  }
   if (const Scope * dtScope{derived.scope()}) {
     for (const auto &pair : *dtScope) {
       const Symbol &symbol{*pair.second};
@@ -1136,7 +1144,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
             const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
             if (const Symbol *
                 bad{FindUnsafeIoDirectComponent(
-                    which, componentDerived, scope)}) {
+                    which, componentDerived, scope, inProgress)}) {
               return bad;
             }
           }
@@ -1147,11 +1155,22 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
   return nullptr;
 }
 
+static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
+    const DerivedTypeSpec &derived, const Scope &scope) {
+  std::set<const Symbol *> inProgress;
+  return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
+}
+
 // For a type that does not have a defined I/O subroutine, finds a direct
 // component that is a witness to an accessibility violation outside the module
-// in which the type was defined.
+// in which the type was defined.  See FindUnsafeIoDirectComponent for the
+// purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
-    const DerivedTypeSpec &derived, const Scope &scope) {
+    const DerivedTypeSpec &derived, const Scope &scope,
+    std::set<const Symbol *> &inProgress) {
+  if (!inProgress.insert(&derived.typeSymbol()).second) {
+    return nullptr;
+  }
   if (const Scope * dtScope{derived.scope()}) {
     if (const Scope * module{FindModuleContaining(*dtScope)}) {
       for (const auto &pair : *dtScope) {
@@ -1179,7 +1198,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
           if (componentDerived) {
             if (const Symbol *
                 bad{FindInaccessibleComponent(
-                    which, *componentDerived, scope)}) {
+                    which, *componentDerived, scope, inProgress)}) {
               return bad;
             }
           }
@@ -1190,6 +1209,12 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
   return nullptr;
 }
 
+static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
+    const DerivedTypeSpec &derived, const Scope &scope) {
+  std::set<const Symbol *> inProgress;
+  return FindInaccessibleComponent(which, derived, scope, inProgress);
+}
+
 // Fortran 2018, 12.6.3 paragraphs 5 & 7
 parser::Message *IoChecker::CheckForBadIoType(const evaluate::DynamicType &type,
     common::DefinedIo which, parser::CharBlock where) const {
diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index 474b07c044512..92d0f7f2741be 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -74,3 +74,15 @@ subroutine test4(u)
   end subroutine
 end module
 
+! Regression test: an illegal recursive derived-type component used to cause
+! infinite recursion in FindUnsafeIoDirectComponent when the object appeared
+! in an I/O list (https://github.com/llvm/llvm-project/issues/192387).
+subroutine test_recursive_io
+  type t1
+    !ERROR: Recursive use of the derived type requires POINTER or ALLOCATABLE
+    type(t1) :: b
+  end type t1
+  type(t1) :: obj
+  print *, obj
+end subroutine
+

>From 4ae6f936d47afccfd7db89c59395798ddc058d93 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:17:51 -0400
Subject: [PATCH 02/10] clang-format

---
 flang/lib/Semantics/check-io.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 0073f1fc6c505..27851d1798ead 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1142,8 +1142,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
         if (const DeclTypeSpec * type{details->type()}) {
           if (type->category() == DeclTypeSpec::Category::TypeDerived) {
             const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
-            if (const Symbol *
-                bad{FindUnsafeIoDirectComponent(
+            if (const Symbol *bad{FindUnsafeIoDirectComponent(
                     which, componentDerived, scope, inProgress)}) {
               return bad;
             }
@@ -1196,8 +1195,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
             }
           }
           if (componentDerived) {
-            if (const Symbol *
-                bad{FindInaccessibleComponent(
+            if (const Symbol *bad{FindInaccessibleComponent(
                     which, *componentDerived, scope, inProgress)}) {
               return bad;
             }

>From 3e4bf01c8d1a73aa0f807def9d2f7dc5e0482cd2 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:22:22 -0400
Subject: [PATCH 03/10] [flang][Semantics] Use std::unordered_set for
 inProgress

Replace std::set<const Symbol *> with std::unordered_set<const Symbol *>
for the recursion-tracking set. Order is irrelevant for membership
checking, and unordered_set gives O(1) lookup/insert vs O(log n) for
std::set.

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 27851d1798ead..e7bb41e5b1631 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -15,8 +15,8 @@
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/expression.h"
 #include "flang/Semantics/tools.h"
-#include <set>
 #include <unordered_map>
+#include <unordered_set>
 
 namespace Fortran::semantics {
 
@@ -1125,7 +1125,7 @@ void IoChecker::CheckForUselessIomsg() const {
 // diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::set<const Symbol *> &inProgress) {
+    std::unordered_set<const Symbol *> &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
@@ -1156,7 +1156,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::set<const Symbol *> inProgress;
+  std::unordered_set<const Symbol *> inProgress;
   return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
 }
 
@@ -1166,7 +1166,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 // purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::set<const Symbol *> &inProgress) {
+    std::unordered_set<const Symbol *> &inProgress) {
   if (!inProgress.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
@@ -1209,7 +1209,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
 
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::set<const Symbol *> inProgress;
+  std::unordered_set<const Symbol *> inProgress;
   return FindInaccessibleComponent(which, derived, scope, inProgress);
 }
 

>From f8edf8b37bba614bea0216d9c809db1d67375757 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:27:49 -0400
Subject: [PATCH 04/10] [flang][Semantics] Introduce VisitedSymbolSet alias

Localize the container type used by FindUnsafeIoDirectComponent and
FindInaccessibleComponent for tracking derived types on the recursion
path. With the alias in place the container can be swapped without
touching the recursive functions or their wrappers.

Co-authored-by: AI
---
 flang/lib/Semantics/check-io.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index e7bb41e5b1631..8ba0defa9ba6f 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1117,6 +1117,11 @@ void IoChecker::CheckForUselessIomsg() const {
   }
 }
 
+// Set of derived-type symbols already visited on the current recursion
+// path of the component walks below.  Localized here so the underlying
+// container can be swapped without touching call sites.
+using VisitedSymbolSet = std::unordered_set<const Symbol *>;
+
 // Seeks out an allocatable or pointer ultimate component that is not
 // nested in a nonallocatable/nonpointer component with a specific
 // defined I/O procedure.  The inProgress set tracks derived types
@@ -1125,7 +1130,7 @@ void IoChecker::CheckForUselessIomsg() const {
 // diagnosed but whose offending component is still in the symbol table.
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::unordered_set<const Symbol *> &inProgress) {
+    VisitedSymbolSet &inProgress) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
@@ -1156,7 +1161,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::unordered_set<const Symbol *> inProgress;
+  VisitedSymbolSet inProgress;
   return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
 }
 
@@ -1166,7 +1171,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 // purpose of inProgress.
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    std::unordered_set<const Symbol *> &inProgress) {
+    VisitedSymbolSet &inProgress) {
   if (!inProgress.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
@@ -1209,7 +1214,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
 
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  std::unordered_set<const Symbol *> inProgress;
+  VisitedSymbolSet inProgress;
   return FindInaccessibleComponent(which, derived, scope, inProgress);
 }
 

>From b171ff6e659e4f089bc18777ca3c046b724b8ae5 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:29:39 -0400
Subject: [PATCH 05/10] Updated comment

---
 flang/lib/Semantics/check-io.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 8ba0defa9ba6f..3c66380548d0d 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1118,8 +1118,7 @@ void IoChecker::CheckForUselessIomsg() const {
 }
 
 // Set of derived-type symbols already visited on the current recursion
-// path of the component walks below.  Localized here so the underlying
-// container can be swapped without touching call sites.
+// path of the component walks below.
 using VisitedSymbolSet = std::unordered_set<const Symbol *>;
 
 // Seeks out an allocatable or pointer ultimate component that is not

>From b21598773bf15d5a032a565f85a752b8844c884e Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:46:09 -0400
Subject: [PATCH 06/10] Updated test comment

---
 flang/test/Semantics/io12.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index 92d0f7f2741be..be272d6b47a82 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -76,7 +76,7 @@ subroutine test4(u)
 
 ! Regression test: an illegal recursive derived-type component used to cause
 ! infinite recursion in FindUnsafeIoDirectComponent when the object appeared
-! in an I/O list (https://github.com/llvm/llvm-project/issues/192387).
+! in an I/O list (issue #192387).
 subroutine test_recursive_io
   type t1
     !ERROR: Recursive use of the derived type requires POINTER or ALLOCATABLE

>From 19e0bfbb856f9a59f10c97bfae66c1a74bdcb6ac Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Sun, 26 Apr 2026 23:59:07 -0400
Subject: [PATCH 07/10] [flang][Semantics] Cover FindInaccessibleComponent
 cycle path

The earlier regression test exercises FindUnsafeIoDirectComponent's
cycle break, but FindInaccessibleComponent skips the component walk
unless the derived type is defined in a module (FindModuleContaining
returns non-null for the type's scope).  Add a second case where the
recursive type lives in a module and the I/O is performed outside it
so the recursive component traversal in FindInaccessibleComponent is
also reached.
---
 flang/test/Semantics/io12.f90 | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index be272d6b47a82..4a966ff5edee4 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -86,3 +86,18 @@ subroutine test_recursive_io
   print *, obj
 end subroutine
 
+! Same regression covering the FindInaccessibleComponent walk: the type
+! must be defined in a module and used in I/O outside that module so the
+! recursive component traversal in FindInaccessibleComponent is reached.
+module m_recursive
+  type t2
+    !ERROR: Recursive use of the derived type requires POINTER or ALLOCATABLE
+    type(t2) :: b
+  end type t2
+end module
+subroutine test_recursive_io_module
+  use m_recursive
+  type(t2) :: obj
+  print *, obj
+end subroutine
+

>From 097051845a85108e52122a93e4fab740956c3469 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Mon, 27 Apr 2026 06:52:08 -0400
Subject: [PATCH 08/10] [flang][Semantics] Add positive tests for recursive
 POINTER/ALLOCATABLE types

Recursive derived types are legal when the recursive component is
POINTER or ALLOCATABLE.  With defined I/O attached to the type, an
I/O list item of such a type must be accepted without diagnostics.
Add two cases (one POINTER, one ALLOCATABLE) that exercise this
no-error path.

Co-authored-by: AI
---
 flang/test/Semantics/io12.f90 | 52 +++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index 4a966ff5edee4..c19e1f3c0a22f 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -101,3 +101,55 @@ subroutine test_recursive_io_module
   print *, obj
 end subroutine
 
+! Positive cases: a recursive type is legal when the recursive component
+! is POINTER or ALLOCATABLE.  With defined I/O, an I/O list item of such
+! a type is accepted without diagnostics, and the cycle-break in the
+! component walk is reached and exited cleanly.
+module m_recursive_pointer
+  type :: rp
+    integer :: x
+    type(rp), pointer :: next => null()
+   contains
+    procedure :: wuf_rp
+    generic :: write(unformatted) => wuf_rp
+  end type
+ contains
+  subroutine wuf_rp(dtv, unit, iostat, iomsg)
+    class(rp), intent(in) :: dtv
+    integer, intent(in) :: unit
+    integer, intent(out) :: iostat
+    character(*), intent(in out) :: iomsg
+    write(unit) dtv%x
+  end subroutine
+end module
+subroutine test_recursive_pointer_io(u)
+  use m_recursive_pointer
+  integer, intent(in) :: u
+  type(rp) :: obj
+  write(u) obj ! ok: defined I/O
+end subroutine
+
+module m_recursive_allocatable
+  type :: ra
+    integer :: x
+    type(ra), allocatable :: next
+   contains
+    procedure :: wuf_ra
+    generic :: write(unformatted) => wuf_ra
+  end type
+ contains
+  subroutine wuf_ra(dtv, unit, iostat, iomsg)
+    class(ra), intent(in) :: dtv
+    integer, intent(in) :: unit
+    integer, intent(out) :: iostat
+    character(*), intent(in out) :: iomsg
+    write(unit) dtv%x
+  end subroutine
+end module
+subroutine test_recursive_allocatable_io(u)
+  use m_recursive_allocatable
+  integer, intent(in) :: u
+  type(ra) :: obj
+  write(u) obj ! ok: defined I/O
+end subroutine
+

>From 71d9235194435866e60c644bbd89ac57a151cbf7 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Mon, 27 Apr 2026 09:42:45 -0400
Subject: [PATCH 09/10] Variable rename, comment update

---
 flang/lib/Semantics/check-io.cpp | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp
index 3c66380548d0d..1e31d66b4bd89 100644
--- a/flang/lib/Semantics/check-io.cpp
+++ b/flang/lib/Semantics/check-io.cpp
@@ -1122,18 +1122,16 @@ void IoChecker::CheckForUselessIomsg() const {
 using VisitedSymbolSet = std::unordered_set<const Symbol *>;
 
 // Seeks out an allocatable or pointer ultimate component that is not
-// nested in a nonallocatable/nonpointer component with a specific
-// defined I/O procedure.  The inProgress set tracks derived types
-// currently on the recursion path to break cycles caused by an illegal
-// recursive type definition (F2023 C749) that has already been
-// diagnosed but whose offending component is still in the symbol table.
+// nested in a nonallocatable/nonpointer component with a specific defined I/O
+// procedure. The 'visited' set tracks derived types to break cycles caused by
+// an illegal recursive type definition (F2023 C749).
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    VisitedSymbolSet &inProgress) {
+    VisitedSymbolSet &visited) {
   if (HasDefinedIo(which, derived, &scope)) {
     return nullptr;
   }
-  if (!inProgress.insert(&derived.typeSymbol()).second) {
+  if (!visited.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
   if (const Scope * dtScope{derived.scope()}) {
@@ -1147,7 +1145,7 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
           if (type->category() == DeclTypeSpec::Category::TypeDerived) {
             const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
             if (const Symbol *bad{FindUnsafeIoDirectComponent(
-                    which, componentDerived, scope, inProgress)}) {
+                    which, componentDerived, scope, visited)}) {
               return bad;
             }
           }
@@ -1160,18 +1158,18 @@ static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
 
 static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  VisitedSymbolSet inProgress;
-  return FindUnsafeIoDirectComponent(which, derived, scope, inProgress);
+  VisitedSymbolSet visited;
+  return FindUnsafeIoDirectComponent(which, derived, scope, visited);
 }
 
 // For a type that does not have a defined I/O subroutine, finds a direct
 // component that is a witness to an accessibility violation outside the module
-// in which the type was defined.  See FindUnsafeIoDirectComponent for the
-// purpose of inProgress.
+// in which the type was defined.  The 'visited' set tracks derived types to
+// break cycles caused by an illegal recursive type definition (F2023 C749).
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope,
-    VisitedSymbolSet &inProgress) {
-  if (!inProgress.insert(&derived.typeSymbol()).second) {
+    VisitedSymbolSet &visited) {
+  if (!visited.insert(&derived.typeSymbol()).second) {
     return nullptr;
   }
   if (const Scope * dtScope{derived.scope()}) {
@@ -1200,7 +1198,7 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
           }
           if (componentDerived) {
             if (const Symbol *bad{FindInaccessibleComponent(
-                    which, *componentDerived, scope, inProgress)}) {
+                    which, *componentDerived, scope, visited)}) {
               return bad;
             }
           }
@@ -1213,8 +1211,8 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
 
 static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
     const DerivedTypeSpec &derived, const Scope &scope) {
-  VisitedSymbolSet inProgress;
-  return FindInaccessibleComponent(which, derived, scope, inProgress);
+  VisitedSymbolSet visited;
+  return FindInaccessibleComponent(which, derived, scope, visited);
 }
 
 // Fortran 2018, 12.6.3 paragraphs 5 & 7

>From 414bacad73e66e6058e86767239c622be2f63e75 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Mon, 27 Apr 2026 10:07:32 -0400
Subject: [PATCH 10/10] Tweaked the comment

---
 flang/test/Semantics/io12.f90 | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/flang/test/Semantics/io12.f90 b/flang/test/Semantics/io12.f90
index c19e1f3c0a22f..86091e1fd8828 100644
--- a/flang/test/Semantics/io12.f90
+++ b/flang/test/Semantics/io12.f90
@@ -103,8 +103,7 @@ subroutine test_recursive_io_module
 
 ! Positive cases: a recursive type is legal when the recursive component
 ! is POINTER or ALLOCATABLE.  With defined I/O, an I/O list item of such
-! a type is accepted without diagnostics, and the cycle-break in the
-! component walk is reached and exited cleanly.
+! a type is accepted without diagnostics.
 module m_recursive_pointer
   type :: rp
     integer :: x



More information about the flang-commits mailing list