[flang-commits] [flang] [llvm] [flang] Enumeration Type: (PR 3/5) Intrinsics + I/O + Modules (PR #193235)

via flang-commits flang-commits at lists.llvm.org
Sat Aug 1 00:52:27 PDT 2026


================
@@ -1216,6 +1276,72 @@ static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
   return FindInaccessibleComponent(which, derived, scope, visited);
 }
 
+// Finds a direct (effective) component whose type is an enumeration type,
+// expanding a derived-type list item into its components per F2023 12.6.3.  A
+// component that is itself processed by defined I/O is treated as a single
+// value and is not expanded, so its subtree is skipped (based off of
+// FindInaccessibleComponent).
+//
+// The 'visited' set must be *path-scoped*: a type symbol is inserted on entry
+// and erased on unwind, so it only prunes recursion when it names a true
+// ancestor on the current path (a real F2023 C749 recursive-type cycle).  This
+// matters for parameterized derived types, where two instantiations share one
+// type symbol but their defined-I/O shielding is decided per-instantiation
+// (HasDefinedIo).
+static const Symbol *FindEnumerationTypeComponent(common::DefinedIo which,
+    const DerivedTypeSpec &derived, const Scope &scope,
+    VisitedSymbolSet &visited) {
+  if (!visited.insert(&derived.typeSymbol()).second) {
+    return nullptr;
+  }
+  const Symbol *result{nullptr};
+  if (const Scope *dtScope{derived.scope()}) {
+    for (const auto &pair : *dtScope) {
+      const Symbol &symbol{*pair.second};
+      if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
+        const DerivedTypeSpec *componentDerived{nullptr};
+        if (const DeclTypeSpec *type{details->type()}) {
+          if (type->category() == DeclTypeSpec::Category::TypeDerived) {
+            componentDerived = &type->derivedTypeSpec();
+          }
+        }
+        if (!componentDerived) {
+          continue;
+        }
+        // The component's type is itself an enumeration type: this is the
+        // enumeration effective item we are looking for.
+        if (const auto *compDetails{
+                componentDerived->typeSymbol().detailsIf<DerivedTypeDetails>()};
+            compDetails && compDetails->isEnumerationType()) {
+          result = &symbol;
+          break;
+        }
+        // The component is processed by defined I/O. It is treated as a single
+        // value and does not expand into its components.
+        if (HasDefinedIo(which, *componentDerived, &scope)) {
+          continue;
+        }
+        // Otherwise the component expands into its own components; recurse to
+        // look for an enumeration effective item nested within it.
+        if (const Symbol *bad{FindEnumerationTypeComponent(
+                which, *componentDerived, scope, visited)}) {
+          result = bad;
+          break;
+        }
+      }
+    }
+  }
+  // Erase on unwind so 'visited' tracks only the current recursion path.
+  visited.erase(&derived.typeSymbol());
----------------
MattPD wrote:

Erasing the type symbol as the recursion returns fixes the order-dependent miss, but it also drops the memoization the old set provided. A type is no longer walked once per top-level call. It is walked again on every path that reaches it, so the cost grows as fanout^depth.

Two things make that reachable from a small file. None of the four call sites is gated on `-fenumeration-type` (the list-directed input and output items, the unformatted check, and the namelist check), and the walk itself never checks whether the program declares an enumeration type, so a source with none still pays the full traversal. And unlike `FindInaccessibleComponent`, this walk does not skip pointer and allocatable components, so the path count is independent of how large the object is.

Measured on this commit, with no `-fenumeration-type` and no enumeration type in the source. Two files that differ by one statement, over a chain of 14 types where each level has four pointer components of the level below:

```
type(u14) :: v                    ! declarations only:   0.4s
type(u14) :: v ; print *, v       ! plus one print:     50s
```

The growth is about 4x per level. Extrapolating, depth 16 would be minutes and depth 18 hours, though I did not run those. In the second file the whole cost is paid before `FindUnsafeIoDirectComponent` reports the pointer-component error, so the program is rejected either way.

Would caching each `derived.scope()` once its subtree is fully walked work here? The instantiated scope is what separates `leaf(1)` from `leaf(2)`, so I think it would restore the linear cost without reopening the order-dependent miss, though you would know better whether that identity is stable. Would skipping pointer and allocatable components, as `FindInaccessibleComponent` does, also be safe?

https://github.com/llvm/llvm-project/pull/193235


More information about the flang-commits mailing list