[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