[flang-commits] [flang] [flang] Fix exposed "free" instances of ac-implied-do indices (PR #178516)
Peter Klausler via flang-commits
flang-commits at lists.llvm.org
Thu Jan 29 06:06:33 PST 2026
https://github.com/klausler updated https://github.com/llvm/llvm-project/pull/178516
>From ddef00aa2e159acaace3463fa99b68d8799b5b08 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Wed, 28 Jan 2026 09:43:50 -0800
Subject: [PATCH] [flang] Fix exposed "free" instances of ac-implied-do indices
Tweak the implementations of IsConstantExpr, IsInitialDataTarget,
and related utilities so that "free" instances of array constructor
implied DO indices are not treated as constant expressions when
the surrounding context (if any) doesn't contain their bounds.
This fixes a current bug in which a "free" implied DO index
in a structure constructor got wrapped up an a Constant<SomeDerived>,
which led to a crash in lowering.
---
.../include/flang/Evaluate/check-expression.h | 36 ++++--
flang/include/flang/Semantics/expression.h | 2 +-
flang/lib/Evaluate/check-expression.cpp | 106 ++++++++++++------
flang/lib/Evaluate/fold-implementation.h | 2 -
flang/lib/Evaluate/fold-integer.cpp | 6 +-
flang/lib/Evaluate/fold.cpp | 2 +-
flang/lib/Evaluate/intrinsics.cpp | 2 +-
flang/lib/Evaluate/shape.cpp | 4 +-
flang/lib/Semantics/check-data.cpp | 3 +-
flang/lib/Semantics/expression.cpp | 4 +-
flang/lib/Semantics/pointer-assignment.cpp | 4 +-
flang/test/Semantics/ac-impl-do-data-ptr.f90 | 9 ++
12 files changed, 121 insertions(+), 59 deletions(-)
create mode 100644 flang/test/Semantics/ac-impl-do-data-ptr.f90
diff --git a/flang/include/flang/Evaluate/check-expression.h b/flang/include/flang/Evaluate/check-expression.h
index d11fe22c0be7b..41a98a4bea6a2 100644
--- a/flang/include/flang/Evaluate/check-expression.h
+++ b/flang/include/flang/Evaluate/check-expression.h
@@ -28,20 +28,34 @@ namespace Fortran::evaluate {
// Predicate: true when an expression is a constant expression (in the
// strict sense of the Fortran standard); it may not (yet) be a hard
// constant value.
-template <typename A> bool IsConstantExpr(const A &);
-extern template bool IsConstantExpr(const Expr<SomeType> &);
-extern template bool IsConstantExpr(const Expr<SomeInteger> &);
-extern template bool IsConstantExpr(const Expr<SubscriptInteger> &);
-extern template bool IsConstantExpr(const StructureConstructor &);
+// The FoldingContext pointer, if not null, prevents a "free" implied
+// DO index from being allowed in a constant expression or initial data
+// target if it is not registered in the FoldingContext. (Array constructor
+// implied DO indices are always allowed when the implied DO is within
+// the expression.)
+template <typename A>
+bool IsConstantExpr(const A &, const FoldingContext * = nullptr);
+extern template bool IsConstantExpr(
+ const Expr<SomeType> &, const FoldingContext *);
+extern template bool IsConstantExpr(
+ const Expr<SomeInteger> &, const FoldingContext *);
+extern template bool IsConstantExpr(
+ const Expr<SubscriptInteger> &, const FoldingContext *);
+extern template bool IsConstantExpr(
+ const StructureConstructor &, const FoldingContext *);
// Predicate: true when an expression is a constant expression (in the
// strict sense of the Fortran standard) or a dummy argument with
// INTENT(IN) and no VALUE. This is useful for representing explicit
// shapes of other dummy arguments.
-template <typename A> bool IsScopeInvariantExpr(const A &);
-extern template bool IsScopeInvariantExpr(const Expr<SomeType> &);
-extern template bool IsScopeInvariantExpr(const Expr<SomeInteger> &);
-extern template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &);
+template <typename A>
+bool IsScopeInvariantExpr(const A &, const FoldingContext * = nullptr);
+extern template bool IsScopeInvariantExpr(
+ const Expr<SomeType> &, const FoldingContext *);
+extern template bool IsScopeInvariantExpr(
+ const Expr<SomeInteger> &, const FoldingContext *);
+extern template bool IsScopeInvariantExpr(
+ const Expr<SubscriptInteger> &, const FoldingContext *);
// Predicate: true when an expression actually is a typed Constant<T>,
// perhaps with parentheses and wrapping around it. False for all typeless
@@ -57,8 +71,8 @@ extern template bool IsActuallyConstant(
// constant addressing and no vector-valued subscript.
// If a non-null ContextualMessages pointer is passed, an error message
// will be generated if and only if the result of the function is false.
-bool IsInitialDataTarget(
- const Expr<SomeType> &, parser::ContextualMessages * = nullptr);
+bool IsInitialDataTarget(const Expr<SomeType> &,
+ parser::ContextualMessages * = nullptr, const FoldingContext * = nullptr);
bool IsInitialProcedureTarget(const Symbol &);
bool IsInitialProcedureTarget(const ProcedureDesignator &);
diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index 50f75b2304d95..c7d0dc06afedb 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -200,7 +200,7 @@ class ExpressionAnalyzer {
auto result{Analyze(x.thing)};
if (result) {
*result = Fold(std::move(*result));
- if (!IsConstantExpr(*result)) { // C886, C887, C713
+ if (!IsConstantExpr(*result, &foldingContext_)) { // C886, C887, C713
SayAt(x, "Must be a constant value"_err_en_US);
ResetExpr(x);
return std::nullopt;
diff --git a/flang/lib/Evaluate/check-expression.cpp b/flang/lib/Evaluate/check-expression.cpp
index f7636ecacfb78..07650e96ed691 100644
--- a/flang/lib/Evaluate/check-expression.cpp
+++ b/flang/lib/Evaluate/check-expression.cpp
@@ -34,7 +34,8 @@ class IsConstantExprHelper
: public AllTraverse<IsConstantExprHelper<INVARIANT>, true> {
public:
using Base = AllTraverse<IsConstantExprHelper, true>;
- IsConstantExprHelper() : Base{*this} {}
+ explicit IsConstantExprHelper(const FoldingContext *c)
+ : Base{*this}, context_{c} {}
using Base::operator();
// A missing expression is not considered to be constant.
@@ -91,10 +92,33 @@ class IsConstantExprHelper
!sym.attrs().test(semantics::Attr::VALUE)));
}
+ bool operator()(const ImpliedDoIndex &ido) const {
+ return acImpliedDos_.find(ido.name) != acImpliedDos_.end() || !context_ ||
+ context_->GetImpliedDo(ido.name).has_value();
+ }
+ template <typename T> bool operator()(const ImpliedDo<T> &ido) {
+ if (!(*this)(ido.lower()) || !(*this)(ido.upper()) ||
+ !(*this)(ido.stride())) {
+ return false;
+ }
+ bool deleteAfter{acImpliedDos_.insert(ido.name()).second};
+ bool result{true};
+ for (const auto &vals : ido.values()) {
+ result &= (*this)(vals);
+ }
+ if (deleteAfter) {
+ acImpliedDos_.erase(ido.name());
+ }
+ return result;
+ }
+
private:
bool IsConstantStructureConstructorComponent(
const Symbol &, const Expr<SomeType> &) const;
bool IsConstantExprShape(const Shape &) const;
+
+ std::set<parser::CharBlock> acImpliedDos_;
+ const FoldingContext *context_{nullptr};
};
template <bool INVARIANT>
@@ -103,7 +127,8 @@ bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent(
if (IsAllocatable(component)) {
return IsNullObjectPointer(&expr);
} else if (IsPointer(component)) {
- return IsNullPointerOrAllocatable(&expr) || IsInitialDataTarget(expr) ||
+ return IsNullPointerOrAllocatable(&expr) ||
+ IsInitialDataTarget(expr, /*messages=*/nullptr, context_) ||
IsInitialProcedureTarget(expr);
} else {
return (*this)(expr);
@@ -175,21 +200,27 @@ bool IsConstantExprHelper<INVARIANT>::IsConstantExprShape(
return true;
}
-template <typename A> bool IsConstantExpr(const A &x) {
- return IsConstantExprHelper<false>{}(x);
+template <typename A> bool IsConstantExpr(const A &x, const FoldingContext *c) {
+ return IsConstantExprHelper<false>{c}(x);
}
-template bool IsConstantExpr(const Expr<SomeType> &);
-template bool IsConstantExpr(const Expr<SomeInteger> &);
-template bool IsConstantExpr(const Expr<SubscriptInteger> &);
-template bool IsConstantExpr(const StructureConstructor &);
+template bool IsConstantExpr(const Expr<SomeType> &, const FoldingContext *);
+template bool IsConstantExpr(const Expr<SomeInteger> &, const FoldingContext *);
+template bool IsConstantExpr(
+ const Expr<SubscriptInteger> &, const FoldingContext *);
+template bool IsConstantExpr(
+ const StructureConstructor &, const FoldingContext *);
// IsScopeInvariantExpr()
-template <typename A> bool IsScopeInvariantExpr(const A &x) {
- return IsConstantExprHelper<true>{}(x);
+template <typename A>
+bool IsScopeInvariantExpr(const A &x, const FoldingContext *c) {
+ return IsConstantExprHelper<true>{c}(x);
}
-template bool IsScopeInvariantExpr(const Expr<SomeType> &);
-template bool IsScopeInvariantExpr(const Expr<SomeInteger> &);
-template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &);
+template bool IsScopeInvariantExpr(
+ const Expr<SomeType> &, const FoldingContext *);
+template bool IsScopeInvariantExpr(
+ const Expr<SomeInteger> &, const FoldingContext *);
+template bool IsScopeInvariantExpr(
+ const Expr<SubscriptInteger> &, const FoldingContext *);
// IsActuallyConstant()
struct IsActuallyConstantHelper {
@@ -207,13 +238,16 @@ struct IsActuallyConstantHelper {
bool operator()(const StructureConstructor &x) {
for (const auto &pair : x) {
const Expr<SomeType> &y{pair.second.value()};
- const auto sym{pair.first};
- const bool compIsConstant{(*this)(y)};
// If an allocatable component is initialized by a constant,
// the structure constructor is not a constant.
- if ((!compIsConstant && !IsNullPointerOrAllocatable(&y)) ||
- (compIsConstant && IsAllocatable(sym))) {
- return false;
+ if ((*this)(y)) {
+ if (IsAllocatable(pair.first)) {
+ return false;
+ }
+ } else {
+ if (!IsNullPointerOrAllocatable(&y)) {
+ return false;
+ }
}
}
return true;
@@ -241,8 +275,9 @@ class IsInitialDataTargetHelper
public:
using Base = AllTraverse<IsInitialDataTargetHelper, true>;
using Base::operator();
- explicit IsInitialDataTargetHelper(parser::ContextualMessages *m)
- : Base{*this}, messages_{m} {}
+ explicit IsInitialDataTargetHelper(
+ parser::ContextualMessages *m, const FoldingContext *c)
+ : Base{*this}, messages_{m}, context_{c} {}
bool emittedMessage() const { return emittedMessage_; }
@@ -292,15 +327,16 @@ class IsInitialDataTargetHelper
bool operator()(const StaticDataObject &) const { return false; }
bool operator()(const TypeParamInquiry &) const { return false; }
bool operator()(const Triplet &x) const {
- return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
- IsConstantExpr(x.stride());
+ return IsConstantExpr(x.lower(), context_) &&
+ IsConstantExpr(x.upper(), context_) &&
+ IsConstantExpr(x.stride(), context_);
}
bool operator()(const Subscript &x) const {
return common::visit(common::visitors{
[&](const Triplet &t) { return (*this)(t); },
[&](const auto &y) {
return y.value().Rank() == 0 &&
- IsConstantExpr(y.value());
+ IsConstantExpr(y.value(), context_);
},
},
x.u);
@@ -310,8 +346,8 @@ class IsInitialDataTargetHelper
return CheckVarOrComponent(x.GetLastSymbol()) && (*this)(x.base());
}
bool operator()(const Substring &x) const {
- return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
- (*this)(x.parent());
+ return IsConstantExpr(x.lower(), context_) &&
+ IsConstantExpr(x.upper(), context_) && (*this)(x.parent());
}
bool operator()(const DescriptorInquiry &) const { return false; }
template <typename T> bool operator()(const ArrayConstructor<T> &) const {
@@ -358,13 +394,15 @@ class IsInitialDataTargetHelper
return false;
}
- parser::ContextualMessages *messages_;
+ parser::ContextualMessages *messages_{nullptr};
+ const FoldingContext *context_{nullptr};
bool emittedMessage_{false};
+ std::set<parser::CharBlock> acImpliedDos_;
};
-bool IsInitialDataTarget(
- const Expr<SomeType> &x, parser::ContextualMessages *messages) {
- IsInitialDataTargetHelper helper{messages};
+bool IsInitialDataTarget(const Expr<SomeType> &x,
+ parser::ContextualMessages *messages, const FoldingContext *context) {
+ IsInitialDataTargetHelper helper{messages, context};
bool result{helper(x)};
if (!result && messages && !helper.emittedMessage()) {
messages->Say(
@@ -732,7 +770,7 @@ class CheckSpecificationExprHelper
x.base().GetFirstSymbol(), x.base().GetLastSymbol(), x.field())) {
auto restorer{common::ScopedSet(inInquiry_, true)};
return (*this)(x.base());
- } else if (IsConstantExpr(x)) {
+ } else if (IsConstantExpr(x, &context_)) {
return std::nullopt;
} else {
return "non-constant descriptor inquiry not allowed for local object";
@@ -741,7 +779,7 @@ class CheckSpecificationExprHelper
Result operator()(const TypeParamInquiry &inq) const {
if (scope_.IsDerivedType()) {
- if (!IsConstantExpr(inq) &&
+ if (!IsConstantExpr(inq, &context_) &&
inq.base() /* X%T, not local T */) { // C750, C754
return "non-constant reference to a type parameter inquiry not allowed "
"for derived type components or type parameter values";
@@ -750,7 +788,7 @@ class CheckSpecificationExprHelper
IsInquiryAlwaysPermissible(inq.base()->GetFirstSymbol())) {
auto restorer{common::ScopedSet(inInquiry_, true)};
return (*this)(inq.base());
- } else if (!IsConstantExpr(inq)) {
+ } else if (!IsConstantExpr(inq, &context_)) {
return "non-constant type parameter inquiry not allowed for local object";
}
return std::nullopt;
@@ -802,7 +840,7 @@ class CheckSpecificationExprHelper
"' not allowed for derived type components or type parameter"
" values";
}
- if (inInquiry && !IsConstantExpr(x)) {
+ if (inInquiry && !IsConstantExpr(x, &context_)) {
return "non-constant reference to inquiry intrinsic '"s +
intrin.name +
"' not allowed for derived type components or type"
@@ -814,7 +852,7 @@ class CheckSpecificationExprHelper
// DescriptorInquiry operations (LBOUND) are checked elsewhere. If a
// call that makes it to here satisfies the requirements of a constant
// expression (as Fortran defines it), it's fine.
- if (IsConstantExpr(x)) {
+ if (IsConstantExpr(x, &context_)) {
return std::nullopt;
}
if (intrin.name == "present") {
diff --git a/flang/lib/Evaluate/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h
index 52ea627d0bbe4..529e3a9ad5a08 100644
--- a/flang/lib/Evaluate/fold-implementation.h
+++ b/flang/lib/Evaluate/fold-implementation.h
@@ -1295,8 +1295,6 @@ Expr<T> FoldOperation(FoldingContext &context, FunctionRef<T> &&funcRef) {
return Expr<T>{std::move(funcRef)};
}
-Expr<ImpliedDoIndex::Result> FoldOperation(FoldingContext &, ImpliedDoIndex &&);
-
// Array constructor folding
template <typename T> class ArrayConstructorFolder {
public:
diff --git a/flang/lib/Evaluate/fold-integer.cpp b/flang/lib/Evaluate/fold-integer.cpp
index 3628497531ef1..9f2bb94a9213f 100644
--- a/flang/lib/Evaluate/fold-integer.cpp
+++ b/flang/lib/Evaluate/fold-integer.cpp
@@ -1187,7 +1187,7 @@ Expr<Type<TypeCategory::Integer, KIND>> FoldIntrinsicFunction(
return common::visit(
[&](auto &kx) {
if (auto len{kx.LEN()}) {
- if (IsScopeInvariantExpr(*len)) {
+ if (IsScopeInvariantExpr(*len, &context)) {
return Fold(context, ConvertToType<T>(*std::move(len)));
} else {
return Expr<T>{std::move(funcRef)};
@@ -1509,7 +1509,7 @@ Expr<TypeParamInquiry::Result> FoldOperation(
paramValue{
declType->derivedTypeSpec().FindParameter(parameterName)}) {
const semantics::MaybeIntExpr ¶mExpr{paramValue->GetExplicit()};
- if (paramExpr && IsConstantExpr(*paramExpr)) {
+ if (paramExpr && IsConstantExpr(*paramExpr, &context)) {
Expr<SomeInteger> intExpr{*paramExpr};
return Fold(context,
ConvertToType<TypeParamInquiry::Result>(std::move(intExpr)));
@@ -1530,7 +1530,7 @@ Expr<TypeParamInquiry::Result> FoldOperation(
if (details) {
isLen = details->attr() == common::TypeParamAttr::Len;
const semantics::MaybeIntExpr &initExpr{details->init()};
- if (initExpr && IsConstantExpr(*initExpr) &&
+ if (initExpr && IsConstantExpr(*initExpr, &context) &&
(!isLen || ToInt64(*initExpr))) {
Expr<SomeInteger> expr{*initExpr};
return Fold(context,
diff --git a/flang/lib/Evaluate/fold.cpp b/flang/lib/Evaluate/fold.cpp
index 3c4f3ecc61996..a2f6bc0f2ae51 100644
--- a/flang/lib/Evaluate/fold.cpp
+++ b/flang/lib/Evaluate/fold.cpp
@@ -80,7 +80,7 @@ Expr<SomeDerived> FoldOperation(
} else if (IsProcedure(symbol)) {
isConstant &= IsInitialProcedureTarget(expr);
} else {
- isConstant &= IsInitialDataTarget(expr);
+ isConstant &= IsInitialDataTarget(expr, /*messages=*/nullptr, &context);
}
} else if (IsAllocatable(symbol)) {
// F2023: 10.1.12 (3)(a)
diff --git a/flang/lib/Evaluate/intrinsics.cpp b/flang/lib/Evaluate/intrinsics.cpp
index 72ac9e2f68758..c17f8b0dd0efa 100644
--- a/flang/lib/Evaluate/intrinsics.cpp
+++ b/flang/lib/Evaluate/intrinsics.cpp
@@ -2564,7 +2564,7 @@ std::optional<SpecificCall> IntrinsicInterface::Match(
}
}
if (context.analyzingPDTComponentKindSelector() && expr &&
- IsConstantExpr(*expr)) {
+ IsConstantExpr(*expr, &context)) {
// Don't emit an error about a KIND= actual argument value when
// processing a kind selector in a PDT component declaration before
// it is instantianted, so long as it's a constant expression.
diff --git a/flang/lib/Evaluate/shape.cpp b/flang/lib/Evaluate/shape.cpp
index 07bff1034f288..27913c3559c71 100644
--- a/flang/lib/Evaluate/shape.cpp
+++ b/flang/lib/Evaluate/shape.cpp
@@ -268,7 +268,7 @@ class GetLowerBoundHelper
semantics::IsAssumedSizeArray(symbol)) {
// last dimension of assumed-size dummy array: don't worry
// about handling an empty dimension
- ok = !invariantOnly_ || IsScopeInvariantExpr(*lbound);
+ ok = !invariantOnly_ || IsScopeInvariantExpr(*lbound, context_);
} else if (lbValue.value_or(0) == 1) {
// Lower bound is 1, regardless of extent
ok = true;
@@ -651,7 +651,7 @@ static MaybeExtentExpr GetExplicitUBOUND(FoldingContext *context,
const semantics::ShapeSpec &shapeSpec, bool invariantOnly) {
const auto &ubound{shapeSpec.ubound().GetExplicit()};
if (ubound && ubound->Rank() == 0 &&
- (!invariantOnly || IsScopeInvariantExpr(*ubound))) {
+ (!invariantOnly || IsScopeInvariantExpr(*ubound, context))) {
if (auto extent{GetNonNegativeExtent(shapeSpec, invariantOnly)}) {
if (auto cstExtent{ToInt64(
context ? Fold(*context, std::move(*extent)) : *extent)}) {
diff --git a/flang/lib/Semantics/check-data.cpp b/flang/lib/Semantics/check-data.cpp
index adbcc3776b763..9dbbc163d85b3 100644
--- a/flang/lib/Semantics/check-data.cpp
+++ b/flang/lib/Semantics/check-data.cpp
@@ -185,7 +185,8 @@ class DataVarChecker : public evaluate::AllTraverse<DataVarChecker, true> {
}
bool CheckSubscriptExpr(
const evaluate::Expr<evaluate::SubscriptInteger> &expr) const {
- if (!evaluate::IsConstantExpr(expr)) { // C875,C881
+ if (!evaluate::IsConstantExpr(expr, /*context=*/
+ nullptr /* to accept unbound implied DO indices */)) { // C875,C881
context_.Say(
source_, "Data object must have constant subscripts"_err_en_US);
return false;
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index e20ce698abc65..213e3d27f21f1 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -1731,7 +1731,9 @@ class ArrayConstructorContext {
if (type_) {
auto len{type_->LEN()};
if (explicitType_ ||
- (len && IsConstantExpr(*len) && !ContainsAnyImpliedDoIndex(*len))) {
+ (len &&
+ IsConstantExpr(
+ *len, &exprAnalyzer_.context().foldingContext()))) {
return len;
}
}
diff --git a/flang/lib/Semantics/pointer-assignment.cpp b/flang/lib/Semantics/pointer-assignment.cpp
index 5508ba8378949..c1c0b28789cab 100644
--- a/flang/lib/Semantics/pointer-assignment.cpp
+++ b/flang/lib/Semantics/pointer-assignment.cpp
@@ -614,8 +614,8 @@ bool CheckPointerAssignment(SemanticsContext &context, parser::CharBlock source,
bool CheckInitialDataPointerTarget(SemanticsContext &context,
const SomeExpr &pointer, const SomeExpr &init, const Scope &scope) {
- return evaluate::IsInitialDataTarget(
- init, &context.foldingContext().messages()) &&
+ return evaluate::IsInitialDataTarget(init,
+ &context.foldingContext().messages(), &context.foldingContext()) &&
CheckPointerAssignment(context, pointer, init, scope,
/*isBoundsRemapping=*/false,
/*isAssumedRank=*/false);
diff --git a/flang/test/Semantics/ac-impl-do-data-ptr.f90 b/flang/test/Semantics/ac-impl-do-data-ptr.f90
new file mode 100644
index 0000000000000..4886ee3784985
--- /dev/null
+++ b/flang/test/Semantics/ac-impl-do-data-ptr.f90
@@ -0,0 +1,9 @@
+!RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s
+type child
+ integer, pointer :: id
+end type
+integer, parameter :: n = 5
+integer, save, target :: t1(n)
+type(child) :: t2(n) = [(child(t1(i)), i=1,n)]
+!CHECK: TYPE(child) :: t2(5_4) = [child::child(id=t1(1_8)),child(id=t1(2_8)),child(id=t1(3_8)),child(id=t1(4_8)),child(id=t1(5_8))]
+end
More information about the flang-commits
mailing list