[flang-commits] [flang] [flang][semantic] Implement semantic checks and new data structure for explicit-shape-bounds-spec (PR #203030)
via flang-commits
flang-commits at lists.llvm.org
Wed Aug 5 11:49:37 PDT 2026
https://github.com/ivanrodriguez3753 updated https://github.com/llvm/llvm-project/pull/203030
>From 90c7312b4fa125ba84bea14b6a6f943cb6d172c7 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 10 Jun 2026 12:19:57 -0500
Subject: [PATCH 01/13] [flang][semantic] Implement semantic checks and new
data structure that scalarizes rank-1 integer array bounds.
Since Lower and large parts of Semantics depend on ShapeSpec being a scalar bound, implement a class RankOneBoundElement that does just this. Since there is no syntactic representation for this node kind, have both unparse.cpp and mod-file.cpp emit the original rank-1 integer array expression instead of scalarized versions.
Analyze does semantic checks, then repackages into an array of scalarized ShapeSpec pairs instead of a pair of arrays.
Lower implementation in follow-up stack PR.
---
flang/include/flang/Evaluate/expression.h | 5 +-
flang/include/flang/Evaluate/shape.h | 1 +
flang/include/flang/Evaluate/traverse.h | 3 +
flang/include/flang/Evaluate/variable.h | 28 ++++
flang/include/flang/Semantics/dump-expr.h | 1 +
flang/lib/Evaluate/check-expression.cpp | 13 ++
flang/lib/Evaluate/fold-implementation.h | 2 +
flang/lib/Evaluate/fold-integer.cpp | 14 ++
flang/lib/Evaluate/formatting.cpp | 5 +
flang/lib/Evaluate/variable.cpp | 3 +
flang/lib/Lower/ConvertExprToHLFIR.cpp | 7 +-
flang/lib/Lower/Support/Utils.cpp | 9 ++
flang/lib/Parser/unparse.cpp | 4 +-
flang/lib/Semantics/dump-expr.cpp | 6 +
flang/lib/Semantics/mod-file.cpp | 60 +++++++-
flang/lib/Semantics/resolve-names-utils.cpp | 142 +++++++++++++++++-
.../declaration-explicit-array-bounds.f90 | 88 +++++++----
.../modfile-explicit-shape-bounds.f90 | 52 +++++++
.../unparse-explicit-array-bounds.f90 | 42 ++++++
19 files changed, 440 insertions(+), 45 deletions(-)
create mode 100644 flang/test/Semantics/modfile-explicit-shape-bounds.f90
create mode 100644 flang/test/Semantics/unparse-explicit-array-bounds.f90
diff --git a/flang/include/flang/Evaluate/expression.h b/flang/include/flang/Evaluate/expression.h
index a729b1535e191..48a6b635f6350 100644
--- a/flang/include/flang/Evaluate/expression.h
+++ b/flang/include/flang/Evaluate/expression.h
@@ -585,12 +585,15 @@ class Expr<Type<TypeCategory::Integer, KIND>>
using DescriptorInquiries =
std::conditional_t<KIND == DescriptorInquiry::Result::kind,
std::tuple<DescriptorInquiry>, std::tuple<>>;
+ using RankOneBoundElements =
+ std::conditional_t<KIND == RankOneBoundElement::Result::kind,
+ std::tuple<RankOneBoundElement>, std::tuple<>>;
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
Designator<Result>, FunctionRef<Result>>;
public:
common::TupleToVariant<common::CombineTuples<Operations, Conversions, Indices,
- TypeParamInquiries, DescriptorInquiries, Others>>
+ TypeParamInquiries, DescriptorInquiries, RankOneBoundElements, Others>>
u;
};
diff --git a/flang/include/flang/Evaluate/shape.h b/flang/include/flang/Evaluate/shape.h
index 2b3a12be78a20..e82401dcfebd8 100644
--- a/flang/include/flang/Evaluate/shape.h
+++ b/flang/include/flang/Evaluate/shape.h
@@ -163,6 +163,7 @@ class GetShapeHelper
Result operator()(const ImpliedDoIndex &) const { return ScalarShape(); }
Result operator()(const DescriptorInquiry &) const { return ScalarShape(); }
+ Result operator()(const RankOneBoundElement &) const { return ScalarShape(); }
Result operator()(const TypeParamInquiry &) const { return ScalarShape(); }
Result operator()(const BOZLiteralConstant &) const { return ScalarShape(); }
Result operator()(const StaticDataObject::Pointer &) const {
diff --git a/flang/include/flang/Evaluate/traverse.h b/flang/include/flang/Evaluate/traverse.h
index d786fa9b5a5a4..2a12527a04536 100644
--- a/flang/include/flang/Evaluate/traverse.h
+++ b/flang/include/flang/Evaluate/traverse.h
@@ -159,6 +159,9 @@ class Traverse {
Result operator()(const DescriptorInquiry &x) const {
return visitor_(x.base());
}
+ Result operator()(const RankOneBoundElement &x) const {
+ return visitor_(x.base());
+ }
// Calls
Result operator()(const SpecificIntrinsic &) const {
diff --git a/flang/include/flang/Evaluate/variable.h b/flang/include/flang/Evaluate/variable.h
index 4f64ede3d407d..f510873ec2fe2 100644
--- a/flang/include/flang/Evaluate/variable.h
+++ b/flang/include/flang/Evaluate/variable.h
@@ -435,6 +435,34 @@ class DescriptorInquiry {
int dimension_{0}; // zero-based
};
+// Represents the extraction of a single scalar element from a rank-1
+// integer array expression used as an explicit-shape array bound (F2023).
+// The inner expression is rank-1; this node is scalar (Rank() == 0).
+// dimension_ is zero-based.
+class RankOneBoundElement {
+public:
+ using Result = SubscriptInteger;
+ CLASS_BOILERPLATE(RankOneBoundElement)
+ RankOneBoundElement(
+ common::CopyableIndirection<Expr<SubscriptInteger>> &&e, int dim)
+ : base_{std::move(e)}, dimension_{dim} {}
+ RankOneBoundElement(Expr<SubscriptInteger> &&e, int dim)
+ : base_{std::move(e)}, dimension_{dim} {}
+
+ const Expr<SubscriptInteger> &base() const { return base_.value(); }
+ Expr<SubscriptInteger> &base() { return base_.value(); }
+ int dimension() const { return dimension_; }
+
+ static constexpr int Rank() { return 0; } // always scalar
+ static constexpr int Corank() { return 0; }
+ bool operator==(const RankOneBoundElement &) const;
+ llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
+
+private:
+ common::CopyableIndirection<Expr<SubscriptInteger>> base_;
+ int dimension_{0}; // zero-based
+};
+
#define INSTANTIATE_VARIABLE_TEMPLATES \
FOR_EACH_SPECIFIC_TYPE(template class Designator, )
} // namespace Fortran::evaluate
diff --git a/flang/include/flang/Semantics/dump-expr.h b/flang/include/flang/Semantics/dump-expr.h
index d79a294258ff1..868b64c64e60a 100644
--- a/flang/include/flang/Semantics/dump-expr.h
+++ b/flang/include/flang/Semantics/dump-expr.h
@@ -149,6 +149,7 @@ class DumpEvaluateExpr {
Outdent();
}
void Show(const evaluate::DescriptorInquiry &x);
+ void Show(const evaluate::RankOneBoundElement &x);
void Show(const evaluate::SpecificIntrinsic &);
void Show(const evaluate::ProcedureDesignator &x);
void Show(const evaluate::ActualArgument &x);
diff --git a/flang/lib/Evaluate/check-expression.cpp b/flang/lib/Evaluate/check-expression.cpp
index 62c93e5d20737..737502a504d61 100644
--- a/flang/lib/Evaluate/check-expression.cpp
+++ b/flang/lib/Evaluate/check-expression.cpp
@@ -91,6 +91,9 @@ class IsConstantExprHelper
(IsIntentIn(sym) && !IsOptional(sym) &&
!sym.attrs().test(semantics::Attr::VALUE)));
}
+ bool operator()(const RankOneBoundElement &x) const {
+ return (*this)(x.base());
+ }
bool operator()(const ImpliedDoIndex &ido) const {
return acImpliedDos_.find(ido.name) != acImpliedDos_.end() || !context_ ||
@@ -363,6 +366,9 @@ class IsInitialDataTargetHelper
IsConstantExpr(x.upper(), context_) && (*this)(x.parent());
}
bool operator()(const DescriptorInquiry &) const { return false; }
+ bool operator()(const RankOneBoundElement &x) const {
+ return false;
+ } // unreachable
template <typename T> bool operator()(const ArrayConstructor<T> &) const {
return false;
}
@@ -798,6 +804,10 @@ class CheckSpecificationExprHelper
}
}
+ Result operator()(const RankOneBoundElement &x) const {
+ return (*this)(x.base());
+ }
+
Result operator()(const TypeParamInquiry &inq) const {
if (scope_.IsDerivedType()) {
if (!IsConstantExpr(inq, &context_) &&
@@ -1797,6 +1807,9 @@ class CollectUsedSymbolValuesHelper
Result operator()(const DescriptorInquiry &) const {
return {}; // doesn't count as a use
}
+ Result operator()(const RankOneBoundElement &x) const {
+ return {}; // unreachable
+ }
template <typename T> Result operator()(const ConditionalExpr<T> &condExpr) {
auto restorer{common::ScopedSet(isDefinition_, false)};
diff --git a/flang/lib/Evaluate/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h
index 918d20da8b880..467bc6f0f7005 100644
--- a/flang/lib/Evaluate/fold-implementation.h
+++ b/flang/lib/Evaluate/fold-implementation.h
@@ -136,6 +136,8 @@ Expr<T> FoldOperation(FoldingContext &context, Designator<T> &&designator) {
}
Expr<TypeParamInquiry::Result> FoldOperation(
FoldingContext &, TypeParamInquiry &&);
+Expr<RankOneBoundElement::Result> FoldOperation(
+ FoldingContext &, RankOneBoundElement &&);
Expr<ImpliedDoIndex::Result> FoldOperation(
FoldingContext &context, ImpliedDoIndex &&);
template <typename T>
diff --git a/flang/lib/Evaluate/fold-integer.cpp b/flang/lib/Evaluate/fold-integer.cpp
index 6619453148dba..c7db4069e3e28 100644
--- a/flang/lib/Evaluate/fold-integer.cpp
+++ b/flang/lib/Evaluate/fold-integer.cpp
@@ -1570,6 +1570,20 @@ Expr<TypeParamInquiry::Result> FoldOperation(
return AsExpr(std::move(inquiry));
}
+Expr<RankOneBoundElement::Result> FoldOperation(
+ FoldingContext &context, RankOneBoundElement &&x) {
+ using ResultType = RankOneBoundElement::Result;
+ auto folded{Fold(context, Expr<ResultType>{x.base()})};
+ if (auto *c{UnwrapConstantValue<ResultType>(folded)}) {
+ // Base is a constant array; extract the element at dimension_ (0-based).
+ ConstantSubscripts at{c->lbounds()};
+ at[0] = c->lbounds()[0] + x.dimension();
+ return Expr<ResultType>{Constant<ResultType>{c->At(at)}};
+ }
+ return Expr<ResultType>{
+ RankOneBoundElement{std::move(folded), x.dimension()}};
+}
+
std::optional<std::int64_t> ToInt64(const Expr<SomeInteger> &expr) {
return common::visit(
[](const auto &kindExpr) { return ToInt64(kindExpr); }, expr.u);
diff --git a/flang/lib/Evaluate/formatting.cpp b/flang/lib/Evaluate/formatting.cpp
index 00bd897e27651..9eb461fe82aa6 100644
--- a/flang/lib/Evaluate/formatting.cpp
+++ b/flang/lib/Evaluate/formatting.cpp
@@ -894,6 +894,11 @@ llvm::raw_ostream &DescriptorInquiry::AsFortran(llvm::raw_ostream &o) const {
return o << ",kind=" << DescriptorInquiry::Result::kind << ")";
}
+llvm::raw_ostream &RankOneBoundElement::AsFortran(llvm::raw_ostream &o) const {
+ llvm_unreachable("RankOneBoundElement has no Fortran representation");
+ return o;
+}
+
llvm::raw_ostream &Assignment::AsFortran(llvm::raw_ostream &o) const {
common::visit(
common::visitors{
diff --git a/flang/lib/Evaluate/variable.cpp b/flang/lib/Evaluate/variable.cpp
index 4133c9556dd61..409fd66f81c2b 100644
--- a/flang/lib/Evaluate/variable.cpp
+++ b/flang/lib/Evaluate/variable.cpp
@@ -762,6 +762,9 @@ bool DescriptorInquiry::operator==(const DescriptorInquiry &that) const {
return field_ == that.field_ && base_ == that.base_ &&
dimension_ == that.dimension_;
}
+bool RankOneBoundElement::operator==(const RankOneBoundElement &that) const {
+ return dimension_ == that.dimension_ && base_ == that.base_;
+}
#ifdef _MSC_VER // disable bogus warning about missing definitions
#pragma warning(disable : 4661)
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index b30a2dfcba90e..4df1b58c0710c 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1851,13 +1851,18 @@ class HlfirBuilder {
llvm_unreachable("unknown descriptor inquiry");
}
+ hlfir::EntityWithAttributes
+ gen(const Fortran::evaluate::RankOneBoundElement &x) {
+ TODO(getLoc(), "rank-1 bound element lowering");
+ }
+
/// Generate a conditional expression as an hlfir.conditional op whose
/// regions yield the then/else values. Materialization into memory is
/// deferred to the bufferization pass.
template <typename T>
hlfir::Entity
genConditionalOp(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
- mlir::Type elementType, bool isPolymorphic) {
+ mlir::Type elementType, bool isPolymorphic) {
const mlir::Location loc{getLoc()};
fir::FirOpBuilder &builder{getBuilder()};
// Lower the condition to i1.
diff --git a/flang/lib/Lower/Support/Utils.cpp b/flang/lib/Lower/Support/Utils.cpp
index 63f508237d4b6..9431daaddf1aa 100644
--- a/flang/lib/Lower/Support/Utils.cpp
+++ b/flang/lib/Lower/Support/Utils.cpp
@@ -242,6 +242,11 @@ class HashEvaluateExpr {
static_cast<unsigned>(x.dimension());
}
static unsigned
+ getHashValue(const Fortran::evaluate::RankOneBoundElement &x) {
+ return getHashValue(x.base()) * 141u +
+ static_cast<unsigned>(x.dimension()) * 17u;
+ }
+ static unsigned
getHashValue(const Fortran::evaluate::StructureConstructor &x) {
// FIXME: hash the contents.
return 149u;
@@ -547,6 +552,10 @@ class IsEqualEvaluateExpr {
return isEqual(x.base(), y.base()) && x.field() == y.field() &&
x.dimension() == y.dimension();
}
+ static bool isEqual(const Fortran::evaluate::RankOneBoundElement &x,
+ const Fortran::evaluate::RankOneBoundElement &y) {
+ return x.dimension() == y.dimension() && isEqual(x.base(), y.base());
+ }
static bool isEqual(const Fortran::evaluate::StructureConstructor &x,
const Fortran::evaluate::StructureConstructor &y) {
const auto &xValues = x.values();
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 23d04f2e3ea42..cde24b91fd246 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -592,8 +592,8 @@ class UnparseVisitor {
common::visitors{
[&](const std::list<ExplicitShapeSpec> &y) { Walk(y, ","); },
[&](const ExplicitShapeBoundsSpec &y) {
- llvm_unreachable(
- "Unparse for ExplicitShapeBoundsSpec should not be reached");
+ Walk(std::get<std::optional<IntExpr>>(y.t), ":");
+ Walk(std::get<IntExpr>(y.t));
},
[&](const std::list<AssumedShapeSpec> &y) { Walk(y, ","); },
[&](const AssumedShapeBoundsSpec &y) {
diff --git a/flang/lib/Semantics/dump-expr.cpp b/flang/lib/Semantics/dump-expr.cpp
index 8d354cf65b61e..44c7d5a4058cf 100644
--- a/flang/lib/Semantics/dump-expr.cpp
+++ b/flang/lib/Semantics/dump-expr.cpp
@@ -195,6 +195,12 @@ void DumpEvaluateExpr::Show(const evaluate::DescriptorInquiry &x) {
Outdent();
}
+void DumpEvaluateExpr::Show(const evaluate::RankOneBoundElement &x) {
+ Indent(("rank-1 bound element [" + llvm::Twine(x.dimension()) + "]").str());
+ Show(x.base());
+ Outdent();
+}
+
void DumpEvaluateExpr::Print(llvm::Twine twine) {
outs_ << GetIndentString() << twine << '\n';
}
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index ce24321e2961a..f54bccfc949cf 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -59,6 +59,7 @@ static void PutBound(llvm::raw_ostream &, const Bound &);
static void PutShapeSpec(llvm::raw_ostream &, const ShapeSpec &);
static void PutShape(
llvm::raw_ostream &, const ArraySpec &, char open, char close);
+static bool HasRankOneBound(const ArraySpec &);
static void PutMapper(llvm::raw_ostream &, const Symbol &, SemanticsContext &);
static llvm::raw_ostream &PutAttr(llvm::raw_ostream &, Attr);
@@ -1064,18 +1065,63 @@ void PutShapeSpec(llvm::raw_ostream &os, const ShapeSpec &x) {
}
}
}
+
+// Check whether any bound in an ArraySpec holds a RankOneBoundElement,
+// indicating the shape came from a rank-1 integer array expression.
+bool HasRankOneBound(const ArraySpec &shape) {
+ const auto &first{shape.front()};
+ if (auto lb{first.lbound().GetExplicit()}) {
+ if (evaluate::UnwrapExpr<evaluate::RankOneBoundElement>(*lb)) {
+ return true;
+ }
+ }
+ if (auto ub{first.ubound().GetExplicit()}) {
+ if (evaluate::UnwrapExpr<evaluate::RankOneBoundElement>(*ub)) {
+ return true;
+ }
+ }
+ return false;
+}
+
void PutShape(
llvm::raw_ostream &os, const ArraySpec &shape, char open, char close) {
if (!shape.empty()) {
os << open;
- bool first{true};
- for (const auto &shapeSpec : shape) {
- if (first) {
- first = false;
- } else {
- os << ',';
+ if (HasRankOneBound(shape)) {
+ // Rank-1 bounds: all ShapeSpecs share the same rank-1 expression
+ // wrapped in RankOneBoundElement. Extract the base expression from the
+ // first element and emit it whole so the mod file round-trips through
+ // the parser as an ExplicitShapeBoundsSpec.
+ const auto &first{shape.front()};
+ if (!first.lbound().isColon()) {
+ auto lb{first.lbound().GetExplicit()};
+ if (auto *robe =
+ evaluate::UnwrapExpr<evaluate::RankOneBoundElement>(*lb)) {
+ robe->base().AsFortran(os);
+ } else {
+ PutBound(os, first.lbound());
+ }
+ }
+ os << ':';
+ if (!first.ubound().isColon()) {
+ auto ub{first.ubound().GetExplicit()};
+ if (auto *robe =
+ evaluate::UnwrapExpr<evaluate::RankOneBoundElement>(*ub)) {
+ robe->base().AsFortran(os);
+ } else {
+ PutBound(os, first.ubound());
+ }
+ }
+ } else {
+ bool first{true};
+ for (const auto &shapeSpec : shape) {
+ if (first) {
+ first = false;
+ } else {
+ os << ',';
+ }
+ PutShapeSpec(os, shapeSpec);
}
- PutShapeSpec(os, shapeSpec);
}
os << close;
}
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index d73a5bb1bfb4c..4edb1db30d248 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -214,6 +214,13 @@ class ArraySpecAnalyzer {
void MakeDeferred(int);
Bound GetBound(const std::optional<parser::SpecificationExpr> &);
Bound GetBound(const parser::SpecificationExpr &);
+ struct ExplicitShapeBoundsResult {
+ Bound ubound;
+ std::optional<Bound> lbound;
+ std::int64_t numDims;
+ };
+ std::optional<ExplicitShapeBoundsResult> checkExplicitShapeBoundsSpec(
+ const parser::ExplicitShapeBoundsSpec &x);
};
ArraySpec AnalyzeArraySpec(
@@ -396,11 +403,138 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeSpec &x) {
std::get<parser::SpecificationExpr>(x.t));
}
+std::optional<ArraySpecAnalyzer::ExplicitShapeBoundsResult>
+ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
+ const parser::ExplicitShapeBoundsSpec &x) {
+ const auto &lowerBoundOpt{std::get<0>(x.t)};
+ const auto &upperBound{std::get<1>(x.t)};
+
+ // Analyze, validate, fold, and wrap one bound expression in a Bound.
+ // Returns the Bound and, for rank-1, the constant extent; for scalar
+ // the extent is 0 (meaning "broadcast").
+ bool hasError{false};
+ auto analyzeBound =
+ [&](const auto &parseBound,
+ bool isUpper) -> std::optional<std::pair<Bound, std::int64_t>> {
+ MaybeExpr expr{AnalyzeExpr(context_, parseBound.thing)};
+ if (expr->Rank() > 1) {
+ context_.Say(parser::FindSourceLocation(parseBound),
+ "Integer array used as %s bounds in DECLARATION must be rank-1 "
+ "but is rank-%d"_err_en_US,
+ isUpper ? "upper" : "lower", expr->Rank());
+ hasError = true;
+ return std::nullopt;
+ }
+ auto folded{evaluate::Fold(context_.foldingContext(), std::move(*expr))};
+ const auto *someInt{evaluate::UnwrapExpr<SomeIntExpr>(folded)};
+ if (!someInt) {
+ hasError = true;
+ return std::nullopt;
+ }
+ auto asSI{evaluate::Fold(context_.foldingContext(),
+ evaluate::ConvertToType<evaluate::SubscriptInteger>(
+ common::Clone(*someInt)))};
+ if (folded.Rank() == 0) {
+ return std::make_pair(
+ Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, std::int64_t{0});
+ }
+ // Rank-1: must have constant extent.
+ auto extents{
+ evaluate::GetConstantExtents(context_.foldingContext(), folded)};
+ if (!extents) {
+ context_.Say(parser::FindSourceLocation(parseBound),
+ "Rank-1 integer array used as %s bounds in DECLARATION must "
+ "have constant size"_err_en_US,
+ isUpper ? "upper" : "lower");
+ hasError = true;
+ return std::nullopt;
+ }
+ return std::make_pair(
+ Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, (*extents)[0]);
+ };
+
+ // Upper bound (required)
+ auto ubResult{analyzeBound(upperBound, /*isUpper=*/true)};
+
+ // Lower bound (optional)
+ std::optional<std::pair<Bound, std::int64_t>> lbResult;
+ if (lowerBoundOpt) {
+ lbResult = analyzeBound(*lowerBoundOpt, /*isUpper=*/false);
+ }
+
+ if (hasError) {
+ return std::nullopt;
+ }
+
+ std::int64_t ubExtent{ubResult->second};
+ std::int64_t lbExtent{lbResult ? lbResult->second : 0};
+
+ // Determine numDims from whichever is rank-1 (extent > 0).
+ std::int64_t numDims{std::max(ubExtent, lbExtent)};
+
+ // Size mismatch check (only when both are rank-1).
+ if (ubExtent > 0 && lbExtent > 0 && ubExtent != lbExtent) {
+ context_.Say(parser::FindSourceLocation(x),
+ "DECLARATION bounds integer rank-1 arrays must have the same size; "
+ "lower bounds has %jd elements, upper bounds has %jd elements"_err_en_US,
+ lbExtent, ubExtent);
+ return std::nullopt;
+ }
+
+ std::optional<Bound> lb;
+ if (lbResult) {
+ lb.emplace(std::move(lbResult->first));
+ }
+ return ExplicitShapeBoundsResult{
+ std::move(ubResult->first), std::move(lb), numDims};
+}
+
void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
- context_.Say("TODO: Analyze overload for ExplicitShapeBoundsSpec"_todo_en_US);
- // prevent CHECK abort in Analyze(ArraySpec), otherwise it'll abort before
- // printing error message
- arraySpec_.push_back(ShapeSpec::MakeExplicit(Bound{1}));
+ auto result{checkExplicitShapeBoundsSpec(x)};
+ // Every path that results in result being false emits an error. In the event
+ // that we bail early without emitting an error, we silently pass the fallback
+ // Bound{1} WITHOUT failing. This check ensures that if we failed, we emitted
+ // an error message. This way we can pass the
+ // CHECK(!arraySpec_.empty());
+ // in Analyze(ArraySpec). If we don't, it'll crash before getting to emit
+ // the real (user) error messages.
+ if (!result) {
+ CHECK(context_.AnyFatalError());
+ arraySpec_.push_back(ShapeSpec::MakeExplicit(Bound{1}));
+ return;
+ }
+ // For rank-1 bounds, emit N ShapeSpecs each wrapping a scalar
+ // RankOneBoundElement that extracts element [dim] from the rank-1
+ // expression. This makes all downstream consumers see scalar bounds.
+ int numDims = static_cast<int>(result->numDims);
+ for (int dim = 0; dim < numDims; ++dim) {
+ // Upper bound
+ MaybeSubscriptIntExpr ubExpr;
+ if (auto &ubOrig = result->ubound.GetExplicit()) {
+ if (ubOrig->Rank() > 0) {
+ ubExpr = SubscriptIntExpr{
+ evaluate::RankOneBoundElement{common::Clone(*ubOrig), dim}};
+ } else {
+ ubExpr = common::Clone(*ubOrig);
+ }
+ }
+ // Lower bound
+ MaybeSubscriptIntExpr lbExpr;
+ if (result->lbound) {
+ if (auto &lbOrig = result->lbound->GetExplicit()) {
+ if (lbOrig->Rank() > 0) {
+ lbExpr = SubscriptIntExpr{
+ evaluate::RankOneBoundElement{common::Clone(*lbOrig), dim}};
+ } else {
+ lbExpr = common::Clone(*lbOrig);
+ }
+ }
+ }
+ Bound lb{lbExpr ? std::move(lbExpr)
+ : MaybeSubscriptIntExpr{SubscriptIntExpr{1}}};
+ Bound ub{std::move(ubExpr)};
+ arraySpec_.push_back(ShapeSpec::MakeExplicit(std::move(lb), std::move(ub)));
+ }
}
void ArraySpecAnalyzer::Analyze(const parser::AssumedImpliedSpec &x) {
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index 175c6c841677c..11f704e48cfe2 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -1,5 +1,24 @@
! RUN: %python %S/test_errors.py %s %flang_fc1 -Wautomatic-in-main-program -Wsaved-local-in-spec-expr
! ---- Module with rank-1 array-bounded declarations, USE'd elsewhere ----
+subroutine array_flatten(int)
+ integer, intent(IN) :: int
+ !Array Constructors produce rank-1 arrays, even with nested arrays,
+ !so neither of these should produce an error or warning.
+ integer :: fff([int, int])
+ integer :: ff([[int, [int, int]]])
+ integer :: arr([(int+i, integer(8) :: i=1_8, 2_8)])
+end subroutine
+module getter
+contains
+ pure function get_bounds() result(r)
+ integer :: r(2)
+ r = [8, 9]
+ end function
+ subroutine foo()
+ ! Function result (rank-1 integer array) as explicit shape bounds
+ integer :: from_func(get_bounds())
+ end subroutine
+end module
module bounds_provider
implicit none
integer, parameter :: dims(3) = [5, 5, 5]
@@ -10,18 +29,14 @@ module consumer
use bounds_provider
implicit none
! Declare arrays using USE-associated rank-1 parameter arrays
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: arr_upper(dims)
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: arr_both(lo : hi)
end module
subroutine sub_consumer()
use bounds_provider, only: dims, lo, hi
implicit none
! USE'd parameter arrays as bounds in a subroutine
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: local_arr(dims)
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: local_arr2(lo : hi)
end subroutine
subroutine sub_use_consumer()
@@ -31,80 +46,93 @@ subroutine sub_use_consumer()
arr_upper = 1
arr_both = 2
end subroutine
+subroutine bar(n, bounds, rank_bounds)
+ integer, intent(IN) :: n
+ integer, intent(IN) :: bounds(:)
+ integer, intent(IN) :: rank_bounds(..)
+ integer :: bounds2(n)
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
+ integer :: arr(bounds)
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
+ integer :: arr2(bounds2)
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
+ integer :: arr3(rank_bounds)
+end subroutine
module data
integer :: rank1_array_module(3) = [5, 5, 5]
- !future_ERROR: Automatic data object 'gg2' may not appear in a module
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: Automatic data object 'gg2' may not appear in a module
integer :: gg2(rank1_array_module)
integer, allocatable :: nonconstsize(:)
- !future_ERROR: Rank-1 integer array used as lower bounds in DECLARATION must have constant size
- !future_ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: Rank-1 integer array used as lower bounds in DECLARATION must have constant size
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
integer :: gg3(nonconstsize : nonconstsize)
end module
program declaration_array_bounds
+ use getter
implicit none
! Valid cases (no errors expected)
! Array upper bound only
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: c([3, 4, 5])
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer, dimension([3, 4, 5]) :: cc
! Array lower and upper bounds, same size
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: d((/2, 3/) : [10, 20])
! Scalar lower, array upper
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: e(2 : [10, 20])
! Array lower, scalar upper
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: f([2, 3] : 10)
! Using non-literal PARAMETER variables
integer, parameter :: rank1_parameter_array(3) = [5,5,5]
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: g(rank1_parameter_array)
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: ggg(rank1_parameter_array * 2 : rank1_parameter_array - 1)
! Negative cases (errors expected)
integer :: rank1_array(3) = [5,5,5]
! Use existing error message for constness checking
- !future_PORTABILITY: specification expression refers to local object 'rank1_array' (initialized and saved) [-Wsaved-local-in-spec-expr]
- !future_PORTABILITY: Automatic data object 'gg' should not appear in the specification part of a main program [-Wautomatic-in-main-program]
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !PORTABILITY: specification expression refers to local object 'rank1_array' (initialized and saved) [-Wsaved-local-in-spec-expr]
+ !PORTABILITY: Automatic data object 'gg' should not appear in the specification part of a main program [-Wautomatic-in-main-program]
integer :: gg(rank1_array)
integer :: scalar
- !future_ERROR: Invalid specification expression: reference to local entity 'scalar'
- !future_PORTABILITY: Automatic data object 'gggg' should not appear in the specification part of a main program [-Wautomatic-in-main-program]
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: Invalid specification expression: reference to local entity 'scalar'
+ !PORTABILITY: Automatic data object 'gggg' should not appear in the specification part of a main program [-Wautomatic-in-main-program]
integer :: gggg(rank1_parameter_array : scalar)
!ERROR: Must have INTEGER type, but is REAL(4)
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
integer :: h([1.2,2.2,3.2]:[1,2,3])
- !future_ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 3 elements, upper bounds has 2 elements
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 3 elements, upper bounds has 2 elements
integer :: i([1,2,3]:[3,3])
+ !Previously uncaught bug: array of size 1 is being treated as a scalar, and broadcast. This is incorrect.
+ !It should be treated as a size mismatch error like the one above.
+ !ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 1 elements, upper bounds has 2 elements
+ integer :: ii([1] : [1,2])
+ !Test same behavior with vector subscripts
+ !ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 1 elements, upper bounds has 2 elements
+ integer :: abc(rank1_array([scalar]) : rank1_array([scalar, scalar]))
+ !Test same behavior with array slices
+ !ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 2 elements, upper bounds has 1 elements
+ integer :: abcd(rank1_array(1:3:2) : rank1_array(1:1))
+ ! using a nonconst upper bound or stride for array slices makes the size nonconst. Should error
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
+ integer :: abcde(rank1_parameter_array(1:scalar:1))
+ !ERROR: Rank-1 integer array used as upper bounds in DECLARATION must have constant size
+ integer :: abcdef(rank1_parameter_array(1:1:scalar))
! Test error for rank > 1, fulfilling constness
integer, parameter :: rank2_parameter_array(2,2) = reshape([[1,2],[3,4]], [2,2])
- !future_ERROR: Integer array used as upper bounds in DECLARATION must be rank-1 but is rank-2
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: Integer array used as upper bounds in DECLARATION must be rank-1 but is rank-2
integer :: j(rank2_parameter_array)
! Test combined bounds error, first bound as before but second bound as wrong rank
! and nonconst
integer :: rank3_array(2,2,2)
- !future_ERROR: Integer array used as lower bounds in DECLARATION must be rank-1 but is rank-2
- !future_ERROR: Integer array used as upper bounds in DECLARATION must be rank-1 but is rank-3
- !ERROR: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
+ !ERROR: Integer array used as lower bounds in DECLARATION must be rank-1 but is rank-2
+ !ERROR: Integer array used as upper bounds in DECLARATION must be rank-1 but is rank-3
integer :: k(rank2_parameter_array : rank3_array)
! Test that any comma list is parsed as ExplicitShapeSpecList and not rewritten
diff --git a/flang/test/Semantics/modfile-explicit-shape-bounds.f90 b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
new file mode 100644
index 0000000000000..9c8330a8075ba
--- /dev/null
+++ b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
@@ -0,0 +1,52 @@
+! RUN: %python %S/test_modfile.py %s %flang_fc1
+! Test mod-file generation for F2023 explicit-shape bounds using rank-1
+! integer arrays (ExplicitShapeBoundsSpec / RankOneBoundElement).
+
+! PARAMETER rank-1 array as upper bounds
+module m1
+ integer, parameter :: dims(3) = [5, 10, 15]
+ real :: a(dims)
+end module
+
+!Expect: m1.mod
+!module m1
+!integer(4),parameter::dims(1_8:3_8)=[INTEGER(4)::5_4,10_4,15_4]
+!real(4)::a(1_8:[INTEGER(8)::5_8,10_8,15_8])
+!end
+
+! Rank-1 dummy as upper bounds
+module m2
+contains
+subroutine sub1(n,a)
+ integer, intent(in) :: n(3)
+ real :: a(n)
+end subroutine
+end module
+
+!Expect: m2.mod
+!module m2
+!contains
+!subroutine sub1(n,a)
+!integer(4),intent(in)::n(1_8:3_8)
+!real(4)::a(1_8:__builtin_int(n,kind=8))
+!end
+!end
+
+! Both lower and upper rank-1 bounds
+module m3
+contains
+subroutine sub2(lb,ub,a)
+ integer, intent(in) :: lb(2), ub(2)
+ real :: a(lb:ub)
+end subroutine
+end module
+
+!Expect: m3.mod
+!module m3
+!contains
+!subroutine sub2(lb,ub,a)
+!integer(4),intent(in)::lb(1_8:2_8)
+!integer(4),intent(in)::ub(1_8:2_8)
+!real(4)::a(__builtin_int(lb,kind=8):__builtin_int(ub,kind=8))
+!end
+!end
diff --git a/flang/test/Semantics/unparse-explicit-array-bounds.f90 b/flang/test/Semantics/unparse-explicit-array-bounds.f90
new file mode 100644
index 0000000000000..4e1ec7633a740
--- /dev/null
+++ b/flang/test/Semantics/unparse-explicit-array-bounds.f90
@@ -0,0 +1,42 @@
+! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s
+
+! Test unparse of ExplicitShapeBoundsSpec (rank-1 integer array bounds).
+
+! Upper bounds only: SHAPE(src)
+subroutine ub_only(src)
+ integer, intent(in) :: src(:,:)
+ integer :: a(SHAPE(src))
+ a = 1
+end subroutine
+!CHECK: INTEGER a([INTEGER(4)::__builtin_int(size(src,dim=1,kind=8),kind=4),__builtin_int(size(src,dim=2,kind=8),kind=4)])
+
+! Lower and upper bounds: lb:ub
+subroutine lb_and_ub(lb, ub)
+ integer, intent(in) :: lb(2), ub(2)
+ integer :: a(lb:ub)
+ a = 1
+end subroutine
+!CHECK: INTEGER a(lb:ub)
+
+! Expression bounds: two*SHAPE(src)
+subroutine expr_bounds(src)
+ integer, intent(in) :: src(:,:,:)
+ integer :: two = 2
+ integer :: a(two*SHAPE(src))
+ integer :: dims(3) = [2,3,4]
+ integer :: b(two * dims)
+ integer :: c(two*SHAPE(src) : two * dims)
+ a = 1
+end subroutine
+!SHAPE can be folded, but dims cannot. Check unparsing for both, then mix them.
+!CHECK: INTEGER a([INTEGER(4)::two*__builtin_int(size(src,dim=1,kind=8),kind=4),two*__builtin_int(size(src,dim=2,kind=8),kind=4),two*__builtin_int(size(src,dim=3,kind=8),kind=4)])
+!CHECK: INTEGER b(two*dims)
+!CHECK: INTEGER c([INTEGER(4)::two*__builtin_int(size(src,dim=1,kind=8),kind=4),two*__builtin_int(size(src,dim=2,kind=8),kind=4),two*__builtin_int(size(src,dim=3,kind=8),kind=4)]:two*dims)
+
+! Parameter bounds
+subroutine param_bounds()
+ integer, parameter :: dims(3) = [2, 3, 4]
+ integer :: a(dims)
+ a = 1
+end subroutine
+!CHECK: INTEGER a([INTEGER(4)::2_4,3_4,4_4])
>From fcf39d60532cd144cd93d026e4c64a0d76920825 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Mon, 13 Jul 2026 11:28:32 -0500
Subject: [PATCH 02/13] Stop using 0 as a sentinel and change implementation to
accept 0 as a valid size for rank-1 array used to specify dims
---
flang/lib/Semantics/resolve-names-utils.cpp | 59 +++++++++++++------
.../declaration-explicit-array-bounds.f90 | 9 +++
2 files changed, 50 insertions(+), 18 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index 4edb1db30d248..a403d6ece82da 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -195,6 +195,9 @@ class ArraySpecAnalyzer {
private:
SemanticsContext &context_;
ArraySpec arraySpec_;
+ // Set when an explicit-shape-bounds-spec with a zero-size bounds array
+ // legitimately produces a scalar (rank 0), leaving arraySpec_ empty.
+ bool zeroRankExplicitBounds_{false};
template <typename T> void Analyze(const std::list<T> &list) {
for (const auto &elem : list) {
@@ -364,7 +367,9 @@ ArraySpec ArraySpecAnalyzer::Analyze(const parser::ArraySpec &x) {
[&](const auto &y) { Analyze(y); },
},
x.u);
- CHECK(!arraySpec_.empty());
+ // arraySpec_ may legitimately be empty when an explicit-shape-bounds-spec
+ // has a zero-size bounds array, which declares a scalar (rank 0).
+ CHECK(!arraySpec_.empty() || zeroRankExplicitBounds_);
return arraySpec_;
}
ArraySpec ArraySpecAnalyzer::AnalyzeDeferredShapeSpecList(
@@ -410,12 +415,13 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
const auto &upperBound{std::get<1>(x.t)};
// Analyze, validate, fold, and wrap one bound expression in a Bound.
- // Returns the Bound and, for rank-1, the constant extent; for scalar
- // the extent is 0 (meaning "broadcast").
+ // Returns the Bound paired with the extent of the bound: std::nullopt
+ // for a scalar bound (which broadcasts to every dimension) or, for a
+ // rank-1 array bound, its constant extent (which may be zero).
bool hasError{false};
auto analyzeBound =
- [&](const auto &parseBound,
- bool isUpper) -> std::optional<std::pair<Bound, std::int64_t>> {
+ [&](const auto &parseBound, bool isUpper)
+ -> std::optional<std::pair<Bound, std::optional<std::int64_t>>> {
MaybeExpr expr{AnalyzeExpr(context_, parseBound.thing)};
if (expr->Rank() > 1) {
context_.Say(parser::FindSourceLocation(parseBound),
@@ -435,8 +441,9 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
evaluate::ConvertToType<evaluate::SubscriptInteger>(
common::Clone(*someInt)))};
if (folded.Rank() == 0) {
- return std::make_pair(
- Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, std::int64_t{0});
+ // Scalar bound: broadcasts to every dimension.
+ return std::make_pair(Bound{MaybeSubscriptIntExpr{std::move(asSI)}},
+ std::optional<std::int64_t>{});
}
// Rank-1: must have constant extent.
auto extents{
@@ -449,15 +456,15 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
hasError = true;
return std::nullopt;
}
- return std::make_pair(
- Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, (*extents)[0]);
+ return std::make_pair(Bound{MaybeSubscriptIntExpr{std::move(asSI)}},
+ std::optional<std::int64_t>{(*extents)[0]});
};
// Upper bound (required)
auto ubResult{analyzeBound(upperBound, /*isUpper=*/true)};
// Lower bound (optional)
- std::optional<std::pair<Bound, std::int64_t>> lbResult;
+ std::optional<std::pair<Bound, std::optional<std::int64_t>>> lbResult;
if (lowerBoundOpt) {
lbResult = analyzeBound(*lowerBoundOpt, /*isUpper=*/false);
}
@@ -466,21 +473,31 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
return std::nullopt;
}
- std::int64_t ubExtent{ubResult->second};
- std::int64_t lbExtent{lbResult ? lbResult->second : 0};
-
- // Determine numDims from whichever is rank-1 (extent > 0).
- std::int64_t numDims{std::max(ubExtent, lbExtent)};
+ // A bound is rank-1 (array-valued) iff it has a concrete extent; a scalar
+ // bound (or an omitted lower bound) has none and merely broadcasts.
+ std::optional<std::int64_t> ubExtent{ubResult->second};
+ std::optional<std::int64_t> lbExtent;
+ if (lbResult) {
+ lbExtent = lbResult->second;
+ }
+ bool ubIsRank1{ubExtent.has_value()};
+ bool lbIsRank1{lbExtent.has_value()};
- // Size mismatch check (only when both are rank-1).
- if (ubExtent > 0 && lbExtent > 0 && ubExtent != lbExtent) {
+ // C832: when both bounds are rank-1 arrays, they must have the same size.
+ // This must include the case where one of the sizes is zero.
+ if (ubIsRank1 && lbIsRank1 && *ubExtent != *lbExtent) {
context_.Say(parser::FindSourceLocation(x),
"DECLARATION bounds integer rank-1 arrays must have the same size; "
"lower bounds has %jd elements, upper bounds has %jd elements"_err_en_US,
- lbExtent, ubExtent);
+ *lbExtent, *ubExtent);
return std::nullopt;
}
+ // The rank of the entity is the size of whichever bound is the rank-1
+ // array (they are equal when both are). A zero-size array yields rank 0,
+ // i.e. the entity is scalar.
+ std::int64_t numDims{ubIsRank1 ? *ubExtent : (lbIsRank1 ? *lbExtent : 0)};
+
std::optional<Bound> lb;
if (lbResult) {
lb.emplace(std::move(lbResult->first));
@@ -507,6 +524,12 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
// RankOneBoundElement that extracts element [dim] from the rank-1
// expression. This makes all downstream consumers see scalar bounds.
int numDims = static_cast<int>(result->numDims);
+ if (numDims == 0) {
+ // A zero-size bounds array declares a scalar (rank 0); leave arraySpec_
+ // empty and record that the empty result is intentional.
+ zeroRankExplicitBounds_ = true;
+ return;
+ }
for (int dim = 0; dim < numDims; ++dim) {
// Upper bound
MaybeSubscriptIntExpr ubExpr;
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index 11f704e48cfe2..336fe52694f86 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -142,4 +142,13 @@ program declaration_array_bounds
!ERROR: Must be a scalar value, but is a rank-1 array
!ERROR: Must have INTEGER type, but is REAL(4)
integer :: test_array([1,2,3] : [2,3,4], 3, [1,2,3], 5.2)
+
+ integer :: dim0(0) = [integer::]
+ integer :: zerosize1([integer::])
+ integer :: zerosize2(dim0 : [integer::])
+ integer :: zerosize3(999 : dim0)
+ integer :: zerosize4([integer::] : 999)
+ !ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 0 elements, upper bounds has 2 elements
+ integer :: zerosize_n([integer::] : [1,2])
+
end program
>From ba8a1f59ab349100771fe881c24070d1ed63ce14 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Mon, 13 Jul 2026 11:29:13 -0500
Subject: [PATCH 03/13] Address comments
---
flang/lib/Semantics/resolve-names-utils.cpp | 9 ++++++---
.../test/Semantics/declaration-explicit-array-bounds.f90 | 6 ++++++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index a403d6ece82da..ff41564cb23a3 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -222,7 +222,7 @@ class ArraySpecAnalyzer {
std::optional<Bound> lbound;
std::int64_t numDims;
};
- std::optional<ExplicitShapeBoundsResult> checkExplicitShapeBoundsSpec(
+ std::optional<ExplicitShapeBoundsResult> CheckExplicitShapeBoundsSpec(
const parser::ExplicitShapeBoundsSpec &x);
};
@@ -409,7 +409,7 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeSpec &x) {
}
std::optional<ArraySpecAnalyzer::ExplicitShapeBoundsResult>
-ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
+ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
const parser::ExplicitShapeBoundsSpec &x) {
const auto &lowerBoundOpt{std::get<0>(x.t)};
const auto &upperBound{std::get<1>(x.t)};
@@ -423,6 +423,9 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
[&](const auto &parseBound, bool isUpper)
-> std::optional<std::pair<Bound, std::optional<std::int64_t>>> {
MaybeExpr expr{AnalyzeExpr(context_, parseBound.thing)};
+ // expr should never be invalid since it was analyzed as part
+ // of the rewrite to ExplicitShapeBoundsSpec
+ CHECK(expr);
if (expr->Rank() > 1) {
context_.Say(parser::FindSourceLocation(parseBound),
"Integer array used as %s bounds in DECLARATION must be rank-1 "
@@ -507,7 +510,7 @@ ArraySpecAnalyzer::checkExplicitShapeBoundsSpec(
}
void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
- auto result{checkExplicitShapeBoundsSpec(x)};
+ auto result{CheckExplicitShapeBoundsSpec(x)};
// Every path that results in result being false emits an error. In the event
// that we bail early without emitting an error, we silently pass the fallback
// Bound{1} WITHOUT failing. This check ensures that if we failed, we emitted
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index 336fe52694f86..bc9920540adb3 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -151,4 +151,10 @@ program declaration_array_bounds
!ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 0 elements, upper bounds has 2 elements
integer :: zerosize_n([integer::] : [1,2])
+ ! Test that maximum supported rank error fires. At the time of writing this test,
+ ! that is 15. See flang/include/flang/Common/Fortran-consts.h.
+ integer :: maxrank([(1,i=1,15)])
+ !ERROR: 'maxrank_n' has rank 16, which is greater than the maximum supported rank 15
+ integer :: maxrank_n([(1,i=1,16)])
+
end program
>From 5d984752be79a0fa6023e3481951011f07ab9ab4 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Mon, 13 Jul 2026 11:45:36 -0500
Subject: [PATCH 04/13] clang format
---
flang/lib/Semantics/resolve-names-utils.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index ff41564cb23a3..f5de102e635a0 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -419,8 +419,7 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
// for a scalar bound (which broadcasts to every dimension) or, for a
// rank-1 array bound, its constant extent (which may be zero).
bool hasError{false};
- auto analyzeBound =
- [&](const auto &parseBound, bool isUpper)
+ auto analyzeBound = [&](const auto &parseBound, bool isUpper)
-> std::optional<std::pair<Bound, std::optional<std::int64_t>>> {
MaybeExpr expr{AnalyzeExpr(context_, parseBound.thing)};
// expr should never be invalid since it was analyzed as part
>From 0c116976326fae05b63bdd9e0d96338abde2fdc6 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 22 Jul 2026 09:19:45 -0500
Subject: [PATCH 05/13] Analyze Integer type wrapper instead of raw expression
---
flang/lib/Semantics/resolve-names-utils.cpp | 23 ++++++++++++++-------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index f5de102e635a0..8011f3d9d51ca 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -418,13 +418,20 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
// Returns the Bound paired with the extent of the bound: std::nullopt
// for a scalar bound (which broadcasts to every dimension) or, for a
// rank-1 array bound, its constant extent (which may be zero).
+
+ // hasError should not be set to true unless there was an error
+ // diagnostic emitted beforehand.
bool hasError{false};
auto analyzeBound = [&](const auto &parseBound, bool isUpper)
-> std::optional<std::pair<Bound, std::optional<std::int64_t>>> {
- MaybeExpr expr{AnalyzeExpr(context_, parseBound.thing)};
- // expr should never be invalid since it was analyzed as part
- // of the rewrite to ExplicitShapeBoundsSpec
- CHECK(expr);
+ MaybeExpr expr{AnalyzeExpr(context_, parseBound)};
+ // Analyzing the parser::Integer<> wrapper enforces the INTEGER type
+ // constraint (C885) and emits a diagnostic for a non-INTEGER bound,
+ // returning std::nullopt.
+ if (!expr) {
+ hasError = true;
+ return std::nullopt;
+ }
if (expr->Rank() > 1) {
context_.Say(parser::FindSourceLocation(parseBound),
"Integer array used as %s bounds in DECLARATION must be rank-1 "
@@ -434,11 +441,11 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
return std::nullopt;
}
auto folded{evaluate::Fold(context_.foldingContext(), std::move(*expr))};
+ // The parser::Integer<> constraint enforced above guarantees an INTEGER
+ // type, so unwrapping the folded result as an integer expression must
+ // succeed.
const auto *someInt{evaluate::UnwrapExpr<SomeIntExpr>(folded)};
- if (!someInt) {
- hasError = true;
- return std::nullopt;
- }
+ CHECK(someInt);
auto asSI{evaluate::Fold(context_.foldingContext(),
evaluate::ConvertToType<evaluate::SubscriptInteger>(
common::Clone(*someInt)))};
>From 39558f4b7f7213b4f107efa799aeda111c433d09 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 22 Jul 2026 09:34:47 -0500
Subject: [PATCH 06/13] Add tests for Integer type constraint wrapper
---
.../declaration-explicit-array-bounds.f90 | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index bc9920540adb3..00d776e7cb656 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -143,6 +143,28 @@ program declaration_array_bounds
!ERROR: Must have INTEGER type, but is REAL(4)
integer :: test_array([1,2,3] : [2,3,4], 3, [1,2,3], 5.2)
+ ! Test that Integer type constraint wrapper correctly errors without aborting.
+ !ERROR: Must have INTEGER type, but is CHARACTER(KIND=1,LEN=1_8)
+ integer :: a1("a" : [1, 2])
+ !ERROR: Must have INTEGER type, but is CHARACTER(KIND=1,LEN=2_8)
+ integer :: a2(["ab", "cd"])
+ character(2), parameter :: ccc(2) = ["ab", "cd"]
+ !ERROR: Must have INTEGER type, but is CHARACTER(KIND=1,LEN=2_8)
+ integer :: a3(ccc)
+ !ERROR: Must have INTEGER type, but is typeless
+ integer :: a4(z'4' : [1, 2])
+ !ERROR: Must have INTEGER type, but is LOGICAL(4)
+ integer :: a5([.true., .false.])
+ !ERROR: Must have INTEGER type, but is REAL(4)
+ integer :: a6([1.2, 2.2, 3.2] : [1, 2, 3])
+ !ERROR: Must have INTEGER type, but is REAL(4)
+ !ERROR: Must have INTEGER type, but is REAL(4)
+ integer :: a7([1.2, 2.2, 3.2] : [1.2, 2.2, 3.2])
+ !ERROR: No explicit type declared for 'undef'
+ integer :: a8(undef : [1, 2])
+ !ERROR: No explicit type declared for 'undef'
+ integer :: a9([1, 2] : undef)
+
integer :: dim0(0) = [integer::]
integer :: zerosize1([integer::])
integer :: zerosize2(dim0 : [integer::])
>From 8c03117d40bf1a08a2f586bb1309d41e83acd68c Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Fri, 24 Jul 2026 13:55:32 -0500
Subject: [PATCH 07/13] clang format
---
flang/lib/Lower/ConvertExprToHLFIR.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index 4df1b58c0710c..776de79e62d19 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1862,7 +1862,7 @@ class HlfirBuilder {
template <typename T>
hlfir::Entity
genConditionalOp(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
- mlir::Type elementType, bool isPolymorphic) {
+ mlir::Type elementType, bool isPolymorphic) {
const mlir::Location loc{getLoc()};
fir::FirOpBuilder &builder{getBuilder()};
// Lower the condition to i1.
>From 3fdaa5ceacb3cee8c7ebfa37e02ddfe0b226aa61 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 29 Jul 2026 16:34:18 -0500
Subject: [PATCH 08/13] Address latest wave of comments:
Handle integer overflow edge case by validating extent before saving and casting to int.
Reject specifications that used an impure function (or any other unallowed restricted expression) even if that expression was being thrown away in the zero-size array case paired with a scalar broadcast. The scalar is thrown away and what would be invalid declarations are never detected. Call CheckSpecificationExpr early in this narrow case, as other cases are already handled naturally.
Implement RankOneBoundElement::AsFortran that I previously thought was unreachable and add a debug output test case for this (-fdebug-dump-symbols). In this test, also add a runline for unparse test (-fdebug-unparse-with-symbols) ensuring that unparse only prints the underlying base expression, once.
---
flang/lib/Evaluate/formatting.cpp | 6 ++-
flang/lib/Semantics/resolve-names-utils.cpp | 38 +++++++++++++++++
.../declaration-explicit-array-bounds.f90 | 41 ++++++++++++++++++-
.../Semantics/rank1-bound-element-symbols.f90 | 24 +++++++++++
4 files changed, 106 insertions(+), 3 deletions(-)
create mode 100644 flang/test/Semantics/rank1-bound-element-symbols.f90
diff --git a/flang/lib/Evaluate/formatting.cpp b/flang/lib/Evaluate/formatting.cpp
index 9eb461fe82aa6..fcedd15ee1791 100644
--- a/flang/lib/Evaluate/formatting.cpp
+++ b/flang/lib/Evaluate/formatting.cpp
@@ -895,7 +895,11 @@ llvm::raw_ostream &DescriptorInquiry::AsFortran(llvm::raw_ostream &o) const {
}
llvm::raw_ostream &RankOneBoundElement::AsFortran(llvm::raw_ostream &o) const {
- llvm_unreachable("RankOneBoundElement has no Fortran representation");
+ // A RankOneBoundElement extracts a single element from a rank-1 array that
+ // was used as an array bound in a declaration; it has no true Fortran
+ // surface syntax. Render it in an internal, clearly-synthetic form.
+ base().AsFortran(o << "rank1BoundElement(")
+ << ",dim=" << (dimension_ + 1) << ')';
return o;
}
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index 8011f3d9d51ca..5db6097a71e03 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -9,6 +9,7 @@
#include "resolve-names-utils.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
+#include "flang/Evaluate/check-expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/traverse.h"
@@ -507,6 +508,43 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
// i.e. the entity is scalar.
std::int64_t numDims{ubIsRank1 ? *ubExtent : (lbIsRank1 ? *lbExtent : 0)};
+ // A zero-sized rank-1 bounds array makes the entity a scalar, so neither
+ // bound is emitted into a ShapeSpec. The per-ShapeSpec specification-
+ // expression checks in declaration checking would therefore never see these
+ // bounds, letting invalid ones slip through (impure function references,
+ // local objects, and other restricted expressions). Validate them here
+ // instead; any diagnostic rejects the declaration.
+ if (numDims == 0) {
+ const Scope &scope{context_.FindScope(parser::FindSourceLocation(x))};
+ auto checkBound{[&](const Bound &bound, parser::CharBlock at) {
+ if (const auto &expr{bound.GetExplicit()}) {
+ auto restorer{context_.foldingContext().messages().SetLocation(at)};
+ evaluate::CheckSpecificationExpr(*expr, scope,
+ context_.foldingContext(), /*forElementalFunctionResult=*/false);
+ }
+ }};
+ checkBound(ubResult->first, parser::FindSourceLocation(upperBound));
+ if (lbResult) {
+ checkBound(lbResult->first, parser::FindSourceLocation(*lowerBoundOpt));
+ }
+ }
+
+ // numDims is the rank determined from the bounds array(s) (already validated
+ // above to agree in size when both are rank-1). Reject a rank above the
+ // maximum here, in signed 64-bit arithmetic, before numDims is narrowed to
+ // int and used to size the ArraySpec -- this avoids the signed-int overflow
+ // and avoids building an enormous ArraySpec for an already-invalid
+ // declaration.
+ if (numDims > common::maxRank) {
+ context_.Say(parser::FindSourceLocation(x),
+ "DECLARATION rank-1 integer array bound(s) imply rank %jd, which is "
+ "greater than the maximum "
+ "supported rank %d"_err_en_US,
+ static_cast<std::intmax_t>(numDims), common::maxRank);
+ hasError = true;
+ return std::nullopt;
+ }
+
std::optional<Bound> lb;
if (lbResult) {
lb.emplace(std::move(lbResult->first));
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index 00d776e7cb656..43169fc2ddf67 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -46,6 +46,17 @@ subroutine sub_use_consumer()
arr_upper = 1
arr_both = 2
end subroutine
+module m
+contains
+ impure integer function impf()
+ impf = 1
+ end function
+end module
+subroutine s1()
+ use m
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer :: z(impf() : [integer::])
+end subroutine
subroutine bar(n, bounds, rank_bounds)
integer, intent(IN) :: n
integer, intent(IN) :: bounds(:)
@@ -167,16 +178,42 @@ program declaration_array_bounds
integer :: dim0(0) = [integer::]
integer :: zerosize1([integer::])
+ !PORTABILITY: specification expression refers to local object 'dim0' (initialized and saved) [-Wsaved-local-in-spec-expr]
integer :: zerosize2(dim0 : [integer::])
+ !PORTABILITY: specification expression refers to local object 'dim0' (initialized and saved) [-Wsaved-local-in-spec-expr]
integer :: zerosize3(999 : dim0)
- integer :: zerosize4([integer::] : 999)
+ integer :: local9 = 9
+ !PORTABILITY: specification expression refers to local object 'local9' (initialized and saved) [-Wsaved-local-in-spec-expr]
+ integer :: zerosize4([integer::] : local9)
!ERROR: DECLARATION bounds integer rank-1 arrays must have the same size; lower bounds has 0 elements, upper bounds has 2 elements
integer :: zerosize_n([integer::] : [1,2])
! Test that maximum supported rank error fires. At the time of writing this test,
! that is 15. See flang/include/flang/Common/Fortran-consts.h.
integer :: maxrank([(1,i=1,15)])
- !ERROR: 'maxrank_n' has rank 16, which is greater than the maximum supported rank 15
+ !ERROR: DECLARATION rank-1 integer array bound(s) imply rank 16, which is greater than the maximum supported rank 15
integer :: maxrank_n([(1,i=1,16)])
+ !Test a rank size between the implementation's max rank and signed 32 bit integer overflow.
+ !Previously, this was crashing the compiler, despite the size (maxRank + 1 == 16) test case above
+ !erroring gracefully as expected. This is because pre-existing Rank() API on ArraySpec
+ !(and several other classes) use C++ `int`s (despite calling inherited vector<>'s .size()
+ !which is a size_t, so there is an implicit cast from unsigned int to a possibly smaller signed int).
+ !Furthermore, this allows us to error out before pushing an absurd amount of ShapeSpec's to a
+ !vector. This was previously not a problem, but now is due to the nature of the change this feature introduces
+ !to a variable's rank. Previously, this was a context-free parse-time property given by
+ !(number of commas - 1), constrained by the literal source text. Now we can use any compile-time
+ !constant value, and this can require a symbol table lookup (which makes rank a context-sensitive property,
+ !no longer strictly as parse-time).
+ !Consider the following:
+ !integer :: dims(100)
+ !Before we'd have to declare
+ !integer :: array(dims(1),dims(2), ..., dims(100))
+ !parsing every ',' along the way. This made overflow theoretically still possible but
+ !practically/computationally impossible. Now we can declare
+ !integer :: array(dims)
+ !and overflow much more easily.
+ integer :: n(2147483648_8) !2^31
+ !ERROR: DECLARATION rank-1 integer array bound(s) imply rank 2147483648, which is greater than the maximum supported rank 15
+ integer :: too_big(n)
end program
diff --git a/flang/test/Semantics/rank1-bound-element-symbols.f90 b/flang/test/Semantics/rank1-bound-element-symbols.f90
new file mode 100644
index 0000000000000..63065a4978575
--- /dev/null
+++ b/flang/test/Semantics/rank1-bound-element-symbols.f90
@@ -0,0 +1,24 @@
+! This test is eventually meant to test several contexts where a
+! rank1BoundElement node is used. That is currently limited to
+! ExplicitShapeBoundsSpec, but will later include every other context
+! where one can use rank-1 integer array bounds instead of past syntax.
+! This includes assumed shape bounds, pointer assignment with bounds remapping,
+! and allocate statements.
+
+! RUN: %flang_fc1 -fdebug-dump-symbols %s 2>&1 | FileCheck %s --check-prefix=SYMBOLS
+! RUN: %flang_fc1 -fdebug-unparse-with-symbols %s 2>&1 | FileCheck %s --check-prefix=UNPARSE
+
+subroutine s(n)
+ integer, intent(in) :: n(3)
+ !SYMBOLS: a {{.*}}: ObjectEntity type: REAL(4) shape: 1_8:rank1BoundElement(__builtin_int(n,kind=8),dim=1),1_8:rank1BoundElement(__builtin_int(n,kind=8),dim=2),1_8:rank1BoundElement(__builtin_int(n,kind=8),dim=3)
+ real :: a(n)
+ a = 0.0
+end subroutine
+
+! -fdebug-unparse-with-symbols intentionally reproduces the original bound syntax
+! rather than the synthesized rank1BoundElement node; this confirms the construct
+! still round-trips through that action with its symbol annotations.
+!UNPARSE: subroutine s (n)
+!UNPARSE: integer, intent(in) :: n(3)
+!UNPARSE: !DEF: /s/a ObjectEntity REAL(4)
+!UNPARSE: real a(n)
>From d991b76a9c4384d3fedf6714b0dea022fff501d6 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 29 Jul 2026 19:36:57 -0500
Subject: [PATCH 09/13] Stash all bounds that appear when any single zero-sized
bound appears.
Since a zero sized bound prevents anything from getting stored at all, we fail to check invalid declarations, and silently let them through. Stash those thrown-away bounds so we can later check whether or not they were invalid.
---
flang/include/flang/Semantics/symbol.h | 11 +++
flang/lib/Semantics/check-declarations.cpp | 7 ++
flang/lib/Semantics/resolve-names-utils.cpp | 70 +++++++++----------
flang/lib/Semantics/resolve-names-utils.h | 7 +-
flang/lib/Semantics/resolve-names.cpp | 25 ++++++-
.../declaration-explicit-array-bounds.f90 | 28 ++++++++
6 files changed, 108 insertions(+), 40 deletions(-)
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index f3951e7567813..1155fb4f6610e 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -469,12 +469,23 @@ class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
void set_cudaDataAttr(std::optional<common::CUDADataAttr> attr) {
cudaDataAttr_ = attr;
}
+ // Specification expressions from the bounds of a zero-size explicit-shape
+ // bounds array (F2023). The entity is scalar, so these bounds are not
+ // part of shape(), but they are still specification expressions that must be
+ // validated during declaration checking, when the scope is fully resolved.
+ void add_droppedBoundToCheck(Bound &&bound) {
+ droppedBoundsToCheck_.emplace_back(std::move(bound));
+ }
+ const std::list<Bound> &droppedBoundsToCheck() const {
+ return droppedBoundsToCheck_;
+ }
private:
MaybeExpr init_;
const parser::Expr *unanalyzedPDTComponentInit_{nullptr};
ArraySpec shape_;
ArraySpec coshape_;
+ std::list<Bound> droppedBoundsToCheck_;
common::IgnoreTKRSet ignoreTKR_;
const Symbol *commonBlock_{nullptr}; // common block this object is in
std::optional<common::CUDADataAttr> cudaDataAttr_;
diff --git a/flang/lib/Semantics/check-declarations.cpp b/flang/lib/Semantics/check-declarations.cpp
index 0a62e199d9779..d59bc073b0753 100644
--- a/flang/lib/Semantics/check-declarations.cpp
+++ b/flang/lib/Semantics/check-declarations.cpp
@@ -767,6 +767,13 @@ void CheckHelper::CheckObjectEntity(
CheckConflicting(symbol, Attr::VOLATILE, Attr::PARAMETER);
Check(details.shape());
Check(details.coshape());
+ // Validate bounds of a zero-size explicit-shape bounds array (F2023). The
+ // entity is scalar, so these bounds were dropped from its shape; they were
+ // stashed during name resolution and are checked here, where the scope is
+ // final.
+ for (const Bound &bound : details.droppedBoundsToCheck()) {
+ Check(bound);
+ }
if (details.shape().Rank() > common::maxRank) {
messages_.Say(
"'%s' has rank %d, which is greater than the maximum supported rank %d"_err_en_US,
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index 5db6097a71e03..62d3cf61114a8 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -9,7 +9,6 @@
#include "resolve-names-utils.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
-#include "flang/Evaluate/check-expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/traverse.h"
@@ -192,6 +191,12 @@ class ArraySpecAnalyzer {
ArraySpec AnalyzeDeferredShapeSpecList(const parser::DeferredShapeSpecList &);
ArraySpec Analyze(const parser::ComponentArraySpec &);
ArraySpec Analyze(const parser::CoarraySpec &);
+ // Bounds of a zero-size explicit-shape bounds array (F2023). The entity is
+ // scalar, so these are dropped from the shape, but they remain specification
+ // expressions to be validated during declaration checking.
+ std::list<Bound> TakeDroppedBoundsToCheck() {
+ return std::move(droppedBoundsToCheck_);
+ }
private:
SemanticsContext &context_;
@@ -199,6 +204,10 @@ class ArraySpecAnalyzer {
// Set when an explicit-shape-bounds-spec with a zero-size bounds array
// legitimately produces a scalar (rank 0), leaving arraySpec_ empty.
bool zeroRankExplicitBounds_{false};
+ // Bounds dropped from a scalar (zero-size bounds array) declaration's shape,
+ // retained as specification expressions to be validated during declaration
+ // checking.
+ std::list<Bound> droppedBoundsToCheck_;
template <typename T> void Analyze(const std::list<T> &list) {
for (const auto &elem : list) {
@@ -227,9 +236,13 @@ class ArraySpecAnalyzer {
const parser::ExplicitShapeBoundsSpec &x);
};
-ArraySpec AnalyzeArraySpec(
- SemanticsContext &context, const parser::ArraySpec &arraySpec) {
- return ArraySpecAnalyzer{context}.Analyze(arraySpec);
+ArraySpec AnalyzeArraySpec(SemanticsContext &context,
+ const parser::ArraySpec &arraySpec,
+ std::list<Bound> &droppedBoundsToCheck) {
+ ArraySpecAnalyzer analyzer{context};
+ ArraySpec result{analyzer.Analyze(arraySpec)};
+ droppedBoundsToCheck = analyzer.TakeDroppedBoundsToCheck();
+ return result;
}
ArraySpec AnalyzeArraySpec(
SemanticsContext &context, const parser::ComponentArraySpec &arraySpec) {
@@ -369,8 +382,11 @@ ArraySpec ArraySpecAnalyzer::Analyze(const parser::ArraySpec &x) {
},
x.u);
// arraySpec_ may legitimately be empty when an explicit-shape-bounds-spec
- // has a zero-size bounds array, which declares a scalar (rank 0).
- CHECK(!arraySpec_.empty() || zeroRankExplicitBounds_);
+ // has a zero-size bounds array, which declares a scalar (rank 0). It may
+ // also be empty on an error path, where a fatal diagnostic has already been
+ // emitted and we are only continuing far enough to surface it.
+ CHECK(context_.AnyFatalError() || !arraySpec_.empty() ||
+ zeroRankExplicitBounds_);
return arraySpec_;
}
ArraySpec ArraySpecAnalyzer::AnalyzeDeferredShapeSpecList(
@@ -508,27 +524,6 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
// i.e. the entity is scalar.
std::int64_t numDims{ubIsRank1 ? *ubExtent : (lbIsRank1 ? *lbExtent : 0)};
- // A zero-sized rank-1 bounds array makes the entity a scalar, so neither
- // bound is emitted into a ShapeSpec. The per-ShapeSpec specification-
- // expression checks in declaration checking would therefore never see these
- // bounds, letting invalid ones slip through (impure function references,
- // local objects, and other restricted expressions). Validate them here
- // instead; any diagnostic rejects the declaration.
- if (numDims == 0) {
- const Scope &scope{context_.FindScope(parser::FindSourceLocation(x))};
- auto checkBound{[&](const Bound &bound, parser::CharBlock at) {
- if (const auto &expr{bound.GetExplicit()}) {
- auto restorer{context_.foldingContext().messages().SetLocation(at)};
- evaluate::CheckSpecificationExpr(*expr, scope,
- context_.foldingContext(), /*forElementalFunctionResult=*/false);
- }
- }};
- checkBound(ubResult->first, parser::FindSourceLocation(upperBound));
- if (lbResult) {
- checkBound(lbResult->first, parser::FindSourceLocation(*lowerBoundOpt));
- }
- }
-
// numDims is the rank determined from the bounds array(s) (already validated
// above to agree in size when both are rank-1). Reject a rank above the
// maximum here, in signed 64-bit arithmetic, before numDims is narrowed to
@@ -555,16 +550,12 @@ ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec(
void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
auto result{CheckExplicitShapeBoundsSpec(x)};
- // Every path that results in result being false emits an error. In the event
- // that we bail early without emitting an error, we silently pass the fallback
- // Bound{1} WITHOUT failing. This check ensures that if we failed, we emitted
- // an error message. This way we can pass the
- // CHECK(!arraySpec_.empty());
- // in Analyze(ArraySpec). If we don't, it'll crash before getting to emit
- // the real (user) error messages.
+ // Every path that yields no result has already emitted a fatal diagnostic.
+ // Leave arraySpec_ empty and return; the CHECK in Analyze(ArraySpec) permits
+ // an empty spec once a fatal error has been recorded, so the real (user)
+ // error messages surface instead of an internal-error abort.
if (!result) {
CHECK(context_.AnyFatalError());
- arraySpec_.push_back(ShapeSpec::MakeExplicit(Bound{1}));
return;
}
// For rank-1 bounds, emit N ShapeSpecs each wrapping a scalar
@@ -573,8 +564,15 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
int numDims = static_cast<int>(result->numDims);
if (numDims == 0) {
// A zero-size bounds array declares a scalar (rank 0); leave arraySpec_
- // empty and record that the empty result is intentional.
+ // empty and record that the empty result is intentional. The bounds are
+ // not part of the shape, but they are still specification expressions;
+ // stash them so declaration checking validates them once the scope is
+ // fully resolved (see ObjectEntityDetails::droppedBoundsToCheck()).
zeroRankExplicitBounds_ = true;
+ droppedBoundsToCheck_.push_back(std::move(result->ubound));
+ if (result->lbound) {
+ droppedBoundsToCheck_.push_back(std::move(*result->lbound));
+ }
return;
}
for (int dim = 0; dim < numDims; ++dim) {
diff --git a/flang/lib/Semantics/resolve-names-utils.h b/flang/lib/Semantics/resolve-names-utils.h
index 774f7fd7a8976..bd5733e742add 100644
--- a/flang/lib/Semantics/resolve-names-utils.h
+++ b/flang/lib/Semantics/resolve-names-utils.h
@@ -91,7 +91,12 @@ class GenericSpecInfo {
};
// Analyze a parser::ArraySpec or parser::CoarraySpec
-ArraySpec AnalyzeArraySpec(SemanticsContext &, const parser::ArraySpec &);
+// A zero-size explicit-shape bounds array (F2023) declares a scalar; its
+// bounds are dropped from the shape but are still specification expressions,
+// appended to droppedBoundsToCheck so the caller can validate them during
+// declaration checking
+ArraySpec AnalyzeArraySpec(SemanticsContext &, const parser::ArraySpec &,
+ std::list<Bound> &droppedBoundsToCheck);
ArraySpec AnalyzeArraySpec(
SemanticsContext &, const parser::ComponentArraySpec &);
ArraySpec AnalyzeDeferredShapeSpecList(
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 83d92f253e626..578237158c11a 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -460,15 +460,23 @@ class ArraySpecVisitor : public virtual BaseVisitor {
const ArraySpec &arraySpec();
void set_arraySpec(const ArraySpec arraySpec) { arraySpec_ = arraySpec; }
const ArraySpec &coarraySpec();
+ std::list<Bound> &droppedBoundsToCheck() { return droppedBoundsToCheck_; }
void BeginArraySpec();
void EndArraySpec();
- void ClearArraySpec() { arraySpec_.clear(); }
+ void ClearArraySpec() {
+ arraySpec_.clear();
+ droppedBoundsToCheck_.clear();
+ }
void ClearCoarraySpec() { coarraySpec_.clear(); }
private:
// arraySpec_/coarraySpec_ are populated from any ArraySpec/CoarraySpec
ArraySpec arraySpec_;
ArraySpec coarraySpec_;
+ // Bounds of a zero-size explicit-shape bounds array (F2023): the entity is
+ // scalar, so these specification expressions are stashed here and validated
+ // later rather than stored in the shape.
+ std::list<Bound> droppedBoundsToCheck_;
// When an ArraySpec is under an AttrSpec or ComponentAttrSpec, it is moved
// into attrArraySpec_
ArraySpec attrArraySpec_;
@@ -2935,7 +2943,7 @@ void ArraySpecVisitor::Post(const parser::RankClause &x) {
void ArraySpecVisitor::Post(const parser::ArraySpec &x) {
CHECK(arraySpec_.empty());
- arraySpec_ = AnalyzeArraySpec(context(), x);
+ arraySpec_ = AnalyzeArraySpec(context(), x, droppedBoundsToCheck_);
}
void ArraySpecVisitor::Post(const parser::ComponentArraySpec &x) {
CHECK(arraySpec_.empty());
@@ -2963,6 +2971,7 @@ void ArraySpecVisitor::EndArraySpec() {
CHECK(coarraySpec_.empty());
attrArraySpec_.clear();
attrCoarraySpec_.clear();
+ droppedBoundsToCheck_.clear();
}
void ArraySpecVisitor::PostAttrSpec() {
// Save dimension/codimension from attrs so we can process array/coarray-spec
@@ -6477,7 +6486,11 @@ void DeclarationVisitor::Post(const parser::ObjectDecl &x) {
// Declare an entity not yet known to be an object or proc.
Symbol &DeclarationVisitor::DeclareUnknownEntity(
const parser::Name &name, Attrs attrs) {
- if (!arraySpec().empty() || !coarraySpec().empty()) {
+ if (!arraySpec().empty() || !coarraySpec().empty() ||
+ !droppedBoundsToCheck().empty()) {
+ // A zero-size explicit-shape bounds array (F2023) declares a scalar with an
+ // empty arraySpec, but still carries deferred bound checks that must be
+ // stashed on an ObjectEntityDetails, so route it to DeclareObjectEntity.
return DeclareObjectEntity(name, attrs);
} else {
Symbol &symbol{DeclareEntity<EntityDetails>(name, attrs)};
@@ -6607,6 +6620,12 @@ Symbol &DeclarationVisitor::DeclareObjectEntity(
details->set_coshape(coarraySpec());
}
}
+ // Stash bounds from a zero-size explicit-shape bounds array (F2023). The
+ // entity is scalar, so these are not part of its shape, but they are still
+ // specification expressions to be validated during declaration checking.
+ for (Bound &bound : droppedBoundsToCheck()) {
+ details->add_droppedBoundToCheck(std::move(bound));
+ }
SetBindNameOn(symbol);
}
ClearArraySpec();
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index 43169fc2ddf67..be0b008dc0c7e 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -51,11 +51,39 @@ module m
impure integer function impf()
impf = 1
end function
+ impure function impureZeroSize() result(r)
+ integer :: r(0)
+ end function
end module
subroutine s1()
use m
!ERROR: Invalid specification expression: reference to impure function 'impf'
integer :: z(impf() : [integer::])
+ ! A bare zero-size upper bound declares a scalar (implicit lower bound 1),
+ ! but the bound is still a specification expression: the deferred check must
+ ! reject the impure function reference.
+ !ERROR: Invalid specification expression: reference to impure function 'impurezerosize'
+ integer :: z2(impureZeroSize())
+ ! Both bounds of a zero-size explicit-shape-bounds-spec are still validated,
+ ! even though the entity is scalar.
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ !ERROR: Invalid specification expression: reference to impure function 'impurezerosize'
+ integer :: z3(impf() : impureZeroSize())
+end subroutine
+! The bounds of a zero-size (scalar) declaration are validated during
+! declaration checking, when the scope is complete -- not during name
+! resolution. The later DATA statement makes 'k' saved and initialized;
+! because the check happens once the scope is final, the zero-size case (zero)
+! reports the same portability warning as the ordinary broadcast case
+! (nonzero). Checking 'k' early, before DATA had been processed, would have
+! diagnosed these two declarations inconsistently.
+subroutine timing()
+ integer :: k
+ !PORTABILITY: specification expression refers to local object 'k' (initialized and saved) [-Wsaved-local-in-spec-expr]
+ integer :: zero(k : [integer::])
+ !PORTABILITY: specification expression refers to local object 'k' (initialized and saved) [-Wsaved-local-in-spec-expr]
+ integer :: nonzero(k : [1, 2])
+ data k / 5 /
end subroutine
subroutine bar(n, bounds, rank_bounds)
integer, intent(IN) :: n
>From d04577eae789878245428b14575cbaa0bdf59465 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Thu, 30 Jul 2026 19:24:18 -0500
Subject: [PATCH 10/13] Hold on to bounds that are dropped
When a scalar is broadcast to a 0 size array, that bound is never saved
and therefore never checked. This was a pre-existing bug in dimension
attributes being overridden by an individual entity declaration. Take
care of both cases, fixing existing bug for the old syntax along the way
(for free).
---
flang/lib/Semantics/resolve-names.cpp | 54 ++++++++++++++++++-
.../declaration-explicit-array-bounds.f90 | 26 +++++++++
flang/test/Semantics/resolve89.f90 | 4 ++
3 files changed, 82 insertions(+), 2 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 578237158c11a..27bdfebd00ef4 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -461,6 +461,32 @@ class ArraySpecVisitor : public virtual BaseVisitor {
void set_arraySpec(const ArraySpec arraySpec) { arraySpec_ = arraySpec; }
const ArraySpec &coarraySpec();
std::list<Bound> &droppedBoundsToCheck() { return droppedBoundsToCheck_; }
+ const std::list<Bound> &attrDroppedBoundsToCheck() const {
+ return attrDroppedBoundsToCheck_;
+ }
+ // Bounds from a DIMENSION attribute's array-spec that an entity-decl's own
+ // array-spec has overridden. They are no longer part of the entity's shape,
+ // but they remain specification expressions that must be validated. Copies
+ // are returned because the attribute applies to every entity-decl in the
+ // statement.
+ std::list<Bound> overriddenAttrArraySpecBounds() const {
+ std::list<Bound> result;
+ if (!arraySpec_.empty() && !attrArraySpec_.empty()) {
+ for (const ShapeSpec &spec : attrArraySpec_) {
+ for (const Bound *bound : {&spec.lbound(), &spec.ubound()}) {
+ // A constant bound -- notably the synthesized default lower bound of
+ // 1 when only an upper bound was written -- is trivially a valid
+ // specification expression, so retain only bounds that might
+ // reference something requiring validation.
+ if (const auto &expr{bound->GetExplicit()};
+ expr && !evaluate::IsConstantExpr(*expr)) {
+ result.push_back(*bound);
+ }
+ }
+ }
+ }
+ return result;
+ }
void BeginArraySpec();
void EndArraySpec();
void ClearArraySpec() {
@@ -481,6 +507,11 @@ class ArraySpecVisitor : public virtual BaseVisitor {
// into attrArraySpec_
ArraySpec attrArraySpec_;
ArraySpec attrCoarraySpec_;
+ // Bounds dropped from a zero-size or overriden DIMENSION attribute (F2023).
+ // Like attrArraySpec_, this is statement-level: it survives per-entity
+ // clearing so the bounds are validated for every entity-decl the attribute
+ // applies to, even when an entity-decl's own array-spec overrides the shape.
+ std::list<Bound> attrDroppedBoundsToCheck_;
void PostAttrSpec();
};
@@ -2965,6 +2996,7 @@ void ArraySpecVisitor::BeginArraySpec() {
CHECK(coarraySpec_.empty());
CHECK(attrArraySpec_.empty());
CHECK(attrCoarraySpec_.empty());
+ CHECK(attrDroppedBoundsToCheck_.empty());
}
void ArraySpecVisitor::EndArraySpec() {
CHECK(arraySpec_.empty());
@@ -2972,6 +3004,7 @@ void ArraySpecVisitor::EndArraySpec() {
attrArraySpec_.clear();
attrCoarraySpec_.clear();
droppedBoundsToCheck_.clear();
+ attrDroppedBoundsToCheck_.clear();
}
void ArraySpecVisitor::PostAttrSpec() {
// Save dimension/codimension from attrs so we can process array/coarray-spec
@@ -2985,6 +3018,11 @@ void ArraySpecVisitor::PostAttrSpec() {
"Attribute 'DIMENSION' cannot be used more than once"_err_en_US);
}
}
+ // A zero-size DIMENSION attribute leaves arraySpec_ empty but produces bounds
+ // to check; move them to the statement-level list so they apply to every
+ // entity-decl.
+ attrDroppedBoundsToCheck_.splice(
+ attrDroppedBoundsToCheck_.end(), droppedBoundsToCheck_);
if (!coarraySpec_.empty()) {
if (attrCoarraySpec_.empty()) {
attrCoarraySpec_ = coarraySpec_;
@@ -6487,9 +6525,9 @@ void DeclarationVisitor::Post(const parser::ObjectDecl &x) {
Symbol &DeclarationVisitor::DeclareUnknownEntity(
const parser::Name &name, Attrs attrs) {
if (!arraySpec().empty() || !coarraySpec().empty() ||
- !droppedBoundsToCheck().empty()) {
+ !droppedBoundsToCheck().empty() || !attrDroppedBoundsToCheck().empty()) {
// A zero-size explicit-shape bounds array (F2023) declares a scalar with an
- // empty arraySpec, but still carries deferred bound checks that must be
+ // empty arraySpec, but still carries dropped bound checks that must be
// stashed on an ObjectEntityDetails, so route it to DeclareObjectEntity.
return DeclareObjectEntity(name, attrs);
} else {
@@ -6623,9 +6661,21 @@ Symbol &DeclarationVisitor::DeclareObjectEntity(
// Stash bounds from a zero-size explicit-shape bounds array (F2023). The
// entity is scalar, so these are not part of its shape, but they are still
// specification expressions to be validated during declaration checking.
+ // Bounds from the entity-decl's own array-spec are moved; those from a
+ // DIMENSION attribute are copied, since the attribute applies to every
+ // entity-decl in the statement.
for (Bound &bound : droppedBoundsToCheck()) {
details->add_droppedBoundToCheck(std::move(bound));
}
+ for (const Bound &bound : attrDroppedBoundsToCheck()) {
+ details->add_droppedBoundToCheck(Bound{bound});
+ }
+ // An entity-decl's own array-spec overrides the DIMENSION attribute's
+ // array-spec (a non-zero-size bounds array), but the attribute's bounds
+ // remain specification expressions that must be validated.
+ for (Bound &bound : overriddenAttrArraySpecBounds()) {
+ details->add_droppedBoundToCheck(std::move(bound));
+ }
SetBindNameOn(symbol);
}
ClearArraySpec();
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index be0b008dc0c7e..bcf5269703df7 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -69,6 +69,32 @@ subroutine s1()
!ERROR: Invalid specification expression: reference to impure function 'impf'
!ERROR: Invalid specification expression: reference to impure function 'impurezerosize'
integer :: z3(impf() : impureZeroSize())
+
+ !Now test using the dimension attribute, and a mixture of both (overriding the dimension attribute).
+ !We must validate dimension even if overridden.
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension(impf() : [integer::]) :: z4
+
+ !The overridden dimension spec should still be checked and error
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension(impf() : [integer::]) :: z5(5)
+
+ !The overridden dimension spec should still be checked as above with z2, but x
+ !should also error. So, it should be emitted once for z6 and once for z7.
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension(impf() : [integer::]) :: z6(5), z7
+
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension([impf(),2,3]) :: z9
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension([1,2,3] : [4,5,impf()]) :: z10
+
+ ! An entity-decl's own array-spec overrides the DIMENSION attribute, but the
+ ! attribute's bounds remain specification expressions that must be validated.
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension([impf(),2,3]) :: z11([1,2,3])
+
end subroutine
! The bounds of a zero-size (scalar) declaration are validated during
! declaration checking, when the scope is complete -- not during name
diff --git a/flang/test/Semantics/resolve89.f90 b/flang/test/Semantics/resolve89.f90
index a0ad24da52da9..1d4e58fc77132 100644
--- a/flang/test/Semantics/resolve89.f90
+++ b/flang/test/Semantics/resolve89.f90
@@ -59,6 +59,10 @@ subroutine s(iArg, allocArg, pointerArg, arrayArg, ioArg, optionalArg)
real, dimension(iVolatileStmtFunc()) :: arrayVarWithVolatile
!ERROR: Invalid specification expression: reference to impure function 'iimpurestmtfunc'
real, dimension(iImpureStmtFunc()) :: arrayVarWithImpureFunction
+ ! The entity-decl's array-spec overrides the DIMENSION attribute's, but the
+ ! attribute's bound remains a specification expression that must be valid.
+ !ERROR: Invalid specification expression: reference to impure function 'iimpurestmtfunc'
+ real, dimension(iImpureStmtFunc()) :: overriddenArrayVar(3)
!ERROR: Invalid specification expression: reference to statement function 'ipurestmtfunc'
real, dimension(iPureStmtFunc()) :: arrayVarWithPureFunction
real, dimension(iabs(iArg)) :: arrayVarWithIntrinsic
>From 6961202ef7c47a6d3a4790b2b682a733e1ee7f11 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 5 Aug 2026 12:26:30 -0500
Subject: [PATCH 11/13] Address duplicate dimension detection
---
flang/lib/Semantics/resolve-names.cpp | 32 +++++++++++++++----
.../declaration-explicit-array-bounds.f90 | 13 ++++++++
.../modfile-explicit-shape-bounds.f90 | 11 +++++++
.../Semantics/rank1-bound-element-symbols.f90 | 7 ++++
4 files changed, 56 insertions(+), 7 deletions(-)
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 27bdfebd00ef4..92575c76772b0 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -471,7 +471,12 @@ class ArraySpecVisitor : public virtual BaseVisitor {
// statement.
std::list<Bound> overriddenAttrArraySpecBounds() const {
std::list<Bound> result;
- if (!arraySpec_.empty() && !attrArraySpec_.empty()) {
+ // An entity-decl overrides the DIMENSION attribute when it supplies its own
+ // array-spec, whether that yields an array (arraySpec_) or, for a zero-size
+ // explicit-shape bounds array (F2023), a scalar (droppedBoundsToCheck_).
+ bool entityHasOwnArraySpec{
+ !arraySpec_.empty() || !droppedBoundsToCheck_.empty()};
+ if (entityHasOwnArraySpec && !attrArraySpec_.empty()) {
for (const ShapeSpec &spec : attrArraySpec_) {
for (const Bound *bound : {&spec.lbound(), &spec.ubound()}) {
// A constant bound -- notably the synthesized default lower bound of
@@ -2986,7 +2991,18 @@ void ArraySpecVisitor::Post(const parser::CoarraySpec &x) {
}
const ArraySpec &ArraySpecVisitor::arraySpec() {
- return !arraySpec_.empty() ? arraySpec_ : attrArraySpec_;
+ if (!arraySpec_.empty()) {
+ return arraySpec_;
+ }
+ // An entity-decl's own array-spec that is a zero-size explicit-shape bounds
+ // array (F2023) declares a scalar: arraySpec_ is empty but its dropped
+ // bounds are recorded. This still overrides any DIMENSION attribute, so
+ // don't fall back to the attribute's array-spec -- return the empty scalar
+ // shape.
+ if (!droppedBoundsToCheck_.empty()) {
+ return arraySpec_;
+ }
+ return attrArraySpec_;
}
const ArraySpec &ArraySpecVisitor::coarraySpec() {
return !coarraySpec_.empty() ? coarraySpec_ : attrCoarraySpec_;
@@ -3008,11 +3024,13 @@ void ArraySpecVisitor::EndArraySpec() {
}
void ArraySpecVisitor::PostAttrSpec() {
// Save dimension/codimension from attrs so we can process array/coarray-spec
- // on the entity-decl
- if (!arraySpec_.empty()) {
- if (attrArraySpec_.empty()) {
- attrArraySpec_ = arraySpec_;
- arraySpec_.clear();
+ // on the entity-decl.
+ if (!arraySpec_.empty() || !droppedBoundsToCheck_.empty()) {
+ if (attrArraySpec_.empty() && attrDroppedBoundsToCheck_.empty()) {
+ if (!arraySpec_.empty()) {
+ attrArraySpec_ = arraySpec_;
+ arraySpec_.clear();
+ }
} else {
Say(currStmtSource().value(),
"Attribute 'DIMENSION' cannot be used more than once"_err_en_US);
diff --git a/flang/test/Semantics/declaration-explicit-array-bounds.f90 b/flang/test/Semantics/declaration-explicit-array-bounds.f90
index bcf5269703df7..0b3d43ba0a4fd 100644
--- a/flang/test/Semantics/declaration-explicit-array-bounds.f90
+++ b/flang/test/Semantics/declaration-explicit-array-bounds.f90
@@ -95,6 +95,19 @@ subroutine s1()
!ERROR: Invalid specification expression: reference to impure function 'impf'
integer, dimension([impf(),2,3]) :: z11([1,2,3])
+ ! A zero-size (scalar) entity-decl array-spec also overrides the DIMENSION
+ ! attribute, but the attribute's bounds must still be validated.
+ !ERROR: Invalid specification expression: reference to impure function 'impf'
+ integer, dimension(impf() : 5) :: z12(1 : [integer::])
+
+ ! A duplicate DIMENSION attribute is an error even when one of them is a
+ ! zero-size bounds array (F2023): such an attribute declares a scalar and so
+ ! leaves the statement-level array-spec empty, but it is still a DIMENSION.
+ !ERROR: Attribute 'DIMENSION' cannot be used more than once
+ integer, dimension(1 : [integer::]), dimension(3) :: z13
+ !ERROR: Attribute 'DIMENSION' cannot be used more than once
+ integer, dimension(3), dimension([integer::]) :: z14([integer::])
+
end subroutine
! The bounds of a zero-size (scalar) declaration are validated during
! declaration checking, when the scope is complete -- not during name
diff --git a/flang/test/Semantics/modfile-explicit-shape-bounds.f90 b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
index 9c8330a8075ba..11b596012c2c2 100644
--- a/flang/test/Semantics/modfile-explicit-shape-bounds.f90
+++ b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
@@ -50,3 +50,14 @@ subroutine sub2(lb,ub,a)
!real(4)::a(__builtin_int(lb,kind=8):__builtin_int(ub,kind=8))
!end
!end
+
+! Zero-size bounds array in an entity-decl declares a scalar, overriding the
+! DIMENSION attribute's array-spec.
+module m4
+ integer, dimension(5) :: z(1 : [integer ::])
+end module
+
+!Expect: m4.mod
+!module m4
+!integer(4)::z
+!end
diff --git a/flang/test/Semantics/rank1-bound-element-symbols.f90 b/flang/test/Semantics/rank1-bound-element-symbols.f90
index 63065a4978575..7839a06935a38 100644
--- a/flang/test/Semantics/rank1-bound-element-symbols.f90
+++ b/flang/test/Semantics/rank1-bound-element-symbols.f90
@@ -15,6 +15,13 @@ subroutine s(n)
a = 0.0
end subroutine
+subroutine s2
+ ! A zero-size bounds array overrides the DIMENSION attribute and declares a
+ ! scalar (size=4, no shape), rather than a size=5*4 array.
+ !SYMBOLS: z size=4 {{.*}}: ObjectEntity type: INTEGER(4)
+ integer, dimension(5) :: z(1 : [integer ::])
+end
+
! -fdebug-unparse-with-symbols intentionally reproduces the original bound syntax
! rather than the synthesized rank1BoundElement node; this confirms the construct
! still round-trips through that action with its symbol annotations.
>From e7f65b9270f6087ba9230c699ca053523b7f747e Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 5 Aug 2026 13:11:00 -0500
Subject: [PATCH 12/13] Use std::vector instead of std::list
---
flang/include/flang/Semantics/symbol.h | 4 ++--
flang/lib/Semantics/resolve-names-utils.cpp | 6 +++---
flang/lib/Semantics/resolve-names-utils.h | 2 +-
flang/lib/Semantics/resolve-names.cpp | 18 ++++++++++--------
4 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 1155fb4f6610e..a51b4542e4f36 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -476,7 +476,7 @@ class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
void add_droppedBoundToCheck(Bound &&bound) {
droppedBoundsToCheck_.emplace_back(std::move(bound));
}
- const std::list<Bound> &droppedBoundsToCheck() const {
+ const std::vector<Bound> &droppedBoundsToCheck() const {
return droppedBoundsToCheck_;
}
@@ -485,7 +485,7 @@ class ObjectEntityDetails : public EntityDetails, public WithOmpDeclarative {
const parser::Expr *unanalyzedPDTComponentInit_{nullptr};
ArraySpec shape_;
ArraySpec coshape_;
- std::list<Bound> droppedBoundsToCheck_;
+ std::vector<Bound> droppedBoundsToCheck_;
common::IgnoreTKRSet ignoreTKR_;
const Symbol *commonBlock_{nullptr}; // common block this object is in
std::optional<common::CUDADataAttr> cudaDataAttr_;
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index 62d3cf61114a8..6580c7e31abdb 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -194,7 +194,7 @@ class ArraySpecAnalyzer {
// Bounds of a zero-size explicit-shape bounds array (F2023). The entity is
// scalar, so these are dropped from the shape, but they remain specification
// expressions to be validated during declaration checking.
- std::list<Bound> TakeDroppedBoundsToCheck() {
+ std::vector<Bound> TakeDroppedBoundsToCheck() {
return std::move(droppedBoundsToCheck_);
}
@@ -207,7 +207,7 @@ class ArraySpecAnalyzer {
// Bounds dropped from a scalar (zero-size bounds array) declaration's shape,
// retained as specification expressions to be validated during declaration
// checking.
- std::list<Bound> droppedBoundsToCheck_;
+ std::vector<Bound> droppedBoundsToCheck_;
template <typename T> void Analyze(const std::list<T> &list) {
for (const auto &elem : list) {
@@ -238,7 +238,7 @@ class ArraySpecAnalyzer {
ArraySpec AnalyzeArraySpec(SemanticsContext &context,
const parser::ArraySpec &arraySpec,
- std::list<Bound> &droppedBoundsToCheck) {
+ std::vector<Bound> &droppedBoundsToCheck) {
ArraySpecAnalyzer analyzer{context};
ArraySpec result{analyzer.Analyze(arraySpec)};
droppedBoundsToCheck = analyzer.TakeDroppedBoundsToCheck();
diff --git a/flang/lib/Semantics/resolve-names-utils.h b/flang/lib/Semantics/resolve-names-utils.h
index bd5733e742add..ae81feb1427eb 100644
--- a/flang/lib/Semantics/resolve-names-utils.h
+++ b/flang/lib/Semantics/resolve-names-utils.h
@@ -96,7 +96,7 @@ class GenericSpecInfo {
// appended to droppedBoundsToCheck so the caller can validate them during
// declaration checking
ArraySpec AnalyzeArraySpec(SemanticsContext &, const parser::ArraySpec &,
- std::list<Bound> &droppedBoundsToCheck);
+ std::vector<Bound> &droppedBoundsToCheck);
ArraySpec AnalyzeArraySpec(
SemanticsContext &, const parser::ComponentArraySpec &);
ArraySpec AnalyzeDeferredShapeSpecList(
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 92575c76772b0..5f6de6f1bfb48 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -460,8 +460,8 @@ class ArraySpecVisitor : public virtual BaseVisitor {
const ArraySpec &arraySpec();
void set_arraySpec(const ArraySpec arraySpec) { arraySpec_ = arraySpec; }
const ArraySpec &coarraySpec();
- std::list<Bound> &droppedBoundsToCheck() { return droppedBoundsToCheck_; }
- const std::list<Bound> &attrDroppedBoundsToCheck() const {
+ std::vector<Bound> &droppedBoundsToCheck() { return droppedBoundsToCheck_; }
+ const std::vector<Bound> &attrDroppedBoundsToCheck() const {
return attrDroppedBoundsToCheck_;
}
// Bounds from a DIMENSION attribute's array-spec that an entity-decl's own
@@ -469,8 +469,8 @@ class ArraySpecVisitor : public virtual BaseVisitor {
// but they remain specification expressions that must be validated. Copies
// are returned because the attribute applies to every entity-decl in the
// statement.
- std::list<Bound> overriddenAttrArraySpecBounds() const {
- std::list<Bound> result;
+ std::vector<Bound> overriddenAttrArraySpecBounds() const {
+ std::vector<Bound> result;
// An entity-decl overrides the DIMENSION attribute when it supplies its own
// array-spec, whether that yields an array (arraySpec_) or, for a zero-size
// explicit-shape bounds array (F2023), a scalar (droppedBoundsToCheck_).
@@ -507,7 +507,7 @@ class ArraySpecVisitor : public virtual BaseVisitor {
// Bounds of a zero-size explicit-shape bounds array (F2023): the entity is
// scalar, so these specification expressions are stashed here and validated
// later rather than stored in the shape.
- std::list<Bound> droppedBoundsToCheck_;
+ std::vector<Bound> droppedBoundsToCheck_;
// When an ArraySpec is under an AttrSpec or ComponentAttrSpec, it is moved
// into attrArraySpec_
ArraySpec attrArraySpec_;
@@ -516,7 +516,7 @@ class ArraySpecVisitor : public virtual BaseVisitor {
// Like attrArraySpec_, this is statement-level: it survives per-entity
// clearing so the bounds are validated for every entity-decl the attribute
// applies to, even when an entity-decl's own array-spec overrides the shape.
- std::list<Bound> attrDroppedBoundsToCheck_;
+ std::vector<Bound> attrDroppedBoundsToCheck_;
void PostAttrSpec();
};
@@ -3039,8 +3039,10 @@ void ArraySpecVisitor::PostAttrSpec() {
// A zero-size DIMENSION attribute leaves arraySpec_ empty but produces bounds
// to check; move them to the statement-level list so they apply to every
// entity-decl.
- attrDroppedBoundsToCheck_.splice(
- attrDroppedBoundsToCheck_.end(), droppedBoundsToCheck_);
+ attrDroppedBoundsToCheck_.insert(attrDroppedBoundsToCheck_.end(),
+ std::make_move_iterator(droppedBoundsToCheck_.begin()),
+ std::make_move_iterator(droppedBoundsToCheck_.end()));
+ droppedBoundsToCheck_.clear();
if (!coarraySpec_.empty()) {
if (attrCoarraySpec_.empty()) {
attrCoarraySpec_ = coarraySpec_;
>From c865b0f894b530b0d2eac9724e241df4d8bf8e4a Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 5 Aug 2026 13:49:14 -0500
Subject: [PATCH 13/13] Correct [Begin|End]ArraySpec CHECKs
---
flang/lib/Semantics/resolve-names.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 5f6de6f1bfb48..e8fa97c19ace6 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -3010,6 +3010,7 @@ const ArraySpec &ArraySpecVisitor::coarraySpec() {
void ArraySpecVisitor::BeginArraySpec() {
CHECK(arraySpec_.empty());
CHECK(coarraySpec_.empty());
+ CHECK(droppedBoundsToCheck_.empty());
CHECK(attrArraySpec_.empty());
CHECK(attrCoarraySpec_.empty());
CHECK(attrDroppedBoundsToCheck_.empty());
@@ -3017,9 +3018,9 @@ void ArraySpecVisitor::BeginArraySpec() {
void ArraySpecVisitor::EndArraySpec() {
CHECK(arraySpec_.empty());
CHECK(coarraySpec_.empty());
+ CHECK(droppedBoundsToCheck_.empty());
attrArraySpec_.clear();
attrCoarraySpec_.clear();
- droppedBoundsToCheck_.clear();
attrDroppedBoundsToCheck_.clear();
}
void ArraySpecVisitor::PostAttrSpec() {
More information about the flang-commits
mailing list