[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
Thu Aug 6 22:46:21 PDT 2026


================
@@ -1255,6 +1255,117 @@ template <int KIND>
 Expr<Type<TypeCategory::Logical, KIND>> FoldIntrinsicFunction(
     FoldingContext &context, FunctionRef<Type<TypeCategory::Logical, KIND>> &&);
 
+// Fold NEXT(enum) / PREVIOUS(enum) to a constant enumerator (scalar or array)
+// when the argument is a constant enumeration value.  Returns the folded
+// constant, or the original reference left unfolded (e.g. at a boundary,
+// where error termination is deferred to run time).
+static inline Expr<SomeDerived> FoldEnumerationNextOrPrevious(
+    FoldingContext &context, FunctionRef<SomeDerived> &&funcRef, bool isNext) {
+  ActualArguments &args{funcRef.arguments()};
+  // Don't fold if STAT is present — STAT assignment is a side effect
+  if (args.size() >= 2 && args[1]) {
+    return Expr<SomeDerived>{std::move(funcRef)};
+  }
+  auto *expr{args.size() >= 1 && args[0]
+          ? UnwrapExpr<Expr<SomeDerived>>(args[0])
+          : nullptr};
+  if (!expr) {
+    return Expr<SomeDerived>{std::move(funcRef)};
+  }
+  const auto *derived{GetEnumerationTypeSpec(expr->GetType())};
+  const semantics::Scope *scope{derived ? derived->GetScope() : nullptr};
+  if (!scope) {
+    return Expr<SomeDerived>{std::move(funcRef)};
+  }
+  auto ordIter{scope->find(
+      semantics::SourceName{semantics::DerivedTypeDetails::ordinalComponentName,
+          sizeof(semantics::DerivedTypeDetails::ordinalComponentName) - 1})};
+  if (ordIter == scope->end()) {
+    return Expr<SomeDerived>{std::move(funcRef)};
+  }
+  const semantics::Symbol &ordSym{*ordIter->second};
+  int count{derived->typeSymbol()
+          .GetUltimate()
+          .get<semantics::DerivedTypeDetails>()
+          .enumeratorCount()};
+  auto *constant{UnwrapConstantValue<SomeDerived>(*expr)};
+  if (!constant) {
+    return Expr<SomeDerived>{std::move(funcRef)};
+  }
+  // A boundary hit (NEXT() of the last enumerator or PREVIOUS() of the first)
+  // without STAT= is, in the final design, a runtime error termination.  In a
+  // required-constant context that value cannot be deferred, so it is
+  // diagnosed as out of range.  Outside a constant context the reference would
+  // otherwise be left unfolded and deferred to run time — but lowering has no
+  // NEXT/PREVIOUS support yet (IntrinsicCall.cpp aborts), so a constant
+  // boundary argument is temporarily gated here, mirroring the STAT= and
+  // non-constant guards in intrinsics.cpp, until the lowering handler lands.
+  auto handleBoundary{[&]() -> Expr<SomeDerived> {
+    if (context.inConstantContext()) {
----------------
MattPD wrote:

Gating on `inConstantContext()` removes the abort, which is the right call while lowering is missing. It also makes the guarded branch unreachable.

Analysis folds every `parser::Expr` once, outside a constant context. That fold runs first and returns `MakeInvalidIntrinsic`. The later fold that runs when a constant is required then finds no matching intrinsic name. Both of these declarations report "not yet supported" rather than "out of range":

```fortran
logical, parameter :: nb = next(blue) == green
type(color) :: c = next(blue)
```

This PR also updates the out-of-range test to expect the temporary message. The out-of-range diagnostic is then left with no reachable caller and no coverage, and PR 4 of 5 would restore it untested.

Would it work to report the out-of-range error in both cases, and attach the "not yet supported" text as a note? Removing the attachment in PR 4 of 5 would then be the whole change, and the out-of-range test could stay as it was.

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


More information about the flang-commits mailing list