[flang-commits] [flang] [flang] Add parser support for allocate-shape-bounds-spec (PR #188445)
via flang-commits
flang-commits at lists.llvm.org
Wed Mar 25 02:34:44 PDT 2026
https://github.com/ivanrodriguez3753 created https://github.com/llvm/llvm-project/pull/188445
Implement parse-tree rewrite in Sema. Emit TODO messages for semantic analysis, while still exiting gracefully and printing other error messages when applicable. `future_ERROR` messages in `LIT` test indicate expected error messages once semantic analysis is implemented in an upcoming PR.
>From faacd7547c356f1b7a30beaa42cd8f5dbbf854c3 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 25 Mar 2026 02:07:43 -0500
Subject: [PATCH 1/2] Amend Allocation node and introduce and add type for
AllocateShapeBoundsSpec.
Bare minimum to fix compile errors and trigger analysis for the new wrapper in Allocation. Test failures as expected due to new node shortcircuiting recurisve analysis of the tree (based on fatal internal error: node has not been analyzed).
---
flang/include/flang/Parser/dump-parse-tree.h | 2 +
flang/include/flang/Parser/parse-tree.h | 39 +++++++++++++++++++-
flang/include/flang/Semantics/expression.h | 5 +++
flang/lib/Lower/Allocatable.cpp | 2 +-
flang/lib/Parser/unparse.cpp | 2 +-
flang/lib/Semantics/check-allocate.cpp | 4 +-
flang/lib/Semantics/expression.cpp | 5 +++
7 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 84c7b8d2a5349..dc42b9ad6ecfb 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -155,6 +155,8 @@ class ParseTreeDumper {
NODE(parser, AllocateCoarraySpec)
NODE(parser, AllocateObject)
NODE(parser, AllocateShapeSpec)
+ NODE(parser, AllocateShapeBoundsSpec)
+ NODE(parser, AllocateShapeSpecListOrBounds)
NODE(parser, AllocateStmt)
NODE(parser, Allocation)
NODE(parser, AltReturnSpec)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index a0106cac84620..82c1eb7070f1a 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -1923,10 +1923,45 @@ struct AllocateCoarraySpec {
// R932 allocation ->
// allocate-object [( allocate-shape-spec-list )]
-// [lbracket allocate-coarray-spec rbracket]
+// [lbracket allocate-coarray-spec rbracket] |
+// ( [ lower-bounds-expr : ] upper-bounds-expr )
+// [ lbracket allocate-coarray-spec rbracket ]
+// The 2028 spec has a typo in the spec, as well as a deviation from the
+// similar explicit-shape-bounds-spec and assumed-shape-bounds-spec
+// rules for array-spec. The typo is that it's missing an allocate-object
+// for the second rule. The deviation is that array-spec has rules
+// differentiating the bound-list versus bounds-spec, while this
+// allocation rule has allocate-shape-spec-list as part of the first rule,
+// and what would be allocate-shape-bounds-spec written inline as
+// [ lower-bounds-expr : ] upper-bounds-expr
+// Altogether, we can use the following grammar:
+// R933 allocation ->
+// allocate-object [ ( allocate-shape-spec-list-or-bounds ) ]
+// [ lbracket allocate-coarray-spec rbracket ] |
+// allocate-shape-spec-list-or-bounds ->
+// allocate-shape-spec-list |
+// allocate-shape-bounds-spec
+// allocate-shape-bounds-spec ->
+// [ lower-bounds-expr : ] upper-bounds-expr
+
+using BoundsExpr = IntExpr;
+
+struct AllocateShapeBoundsSpec {
+ TUPLE_CLASS_BOILERPLATE(AllocateShapeBoundsSpec);
+ std::tuple<
+ std::optional<BoundsExpr>,
+ BoundsExpr>
+ t;
+};
+
+struct AllocateShapeSpecListOrBounds {
+ UNION_CLASS_BOILERPLATE(AllocateShapeSpecListOrBounds);
+ std::variant<std::list<AllocateShapeSpec>, AllocateShapeBoundsSpec> u;
+};
+
struct Allocation {
TUPLE_CLASS_BOILERPLATE(Allocation);
- std::tuple<AllocateObject, std::list<AllocateShapeSpec>,
+ std::tuple<AllocateObject, AllocateShapeSpecListOrBounds,
std::optional<AllocateCoarraySpec>>
t;
};
diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index e2817ea542131..1a6b441b655d8 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -169,6 +169,7 @@ class ExpressionAnalyzer {
MaybeExpr Analyze(const parser::DataStmtValue &);
MaybeExpr Analyze(const parser::AllocateObject &);
MaybeExpr Analyze(const parser::PointerObject &);
+ MaybeExpr Analyze(const parser::AllocateShapeSpecListOrBounds &x);
template <typename A> MaybeExpr Analyze(const common::Indirection<A> &x) {
return Analyze(x.value());
@@ -503,6 +504,10 @@ class ExprChecker {
AnalyzeAndNoteUses(x, /*isDefinition=*/true);
return false;
}
+ bool Pre(const parser::AllocateShapeSpecListOrBounds &x) {
+ exprAnalyzer_.Analyze(x);
+ return false;
+ }
bool Pre(const parser::DataStmtObject &);
void Post(const parser::DataStmtObject &);
bool Pre(const parser::DataImpliedDo &);
diff --git a/flang/lib/Lower/Allocatable.cpp b/flang/lib/Lower/Allocatable.cpp
index 1912027f8742d..af1a211e2bd54 100644
--- a/flang/lib/Lower/Allocatable.cpp
+++ b/flang/lib/Lower/Allocatable.cpp
@@ -337,7 +337,7 @@ class AllocateStmtHelper {
return unwrapSymbol(getAllocObj());
}
const std::list<Fortran::parser::AllocateShapeSpec> &getShapeSpecs() const {
- return std::get<std::list<Fortran::parser::AllocateShapeSpec>>(alloc.t);
+ return std::get<std::list<Fortran::parser::AllocateShapeSpec>>(std::get<Fortran::parser::AllocateShapeSpecListOrBounds>(alloc.t).u);
}
};
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index c31eac0b3ff68..39bb7ec78a12d 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -849,7 +849,7 @@ class UnparseVisitor {
}
void Unparse(const Allocation &x) { // R932
Walk(std::get<AllocateObject>(x.t));
- Walk("(", std::get<std::list<AllocateShapeSpec>>(x.t), ",", ")");
+ Walk("(", std::get<std::list<AllocateShapeSpec>>(std::get<AllocateShapeSpecListOrBounds>(x.t).u), ",", ")");
Walk("[", std::get<std::optional<AllocateCoarraySpec>>(x.t), "]");
}
void Unparse(const AllocateShapeSpec &x) { // R934 & R938
diff --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp
index 7f099d51221c0..bc582cb303bab 100644
--- a/flang/lib/Semantics/check-allocate.cpp
+++ b/flang/lib/Semantics/check-allocate.cpp
@@ -59,7 +59,7 @@ class AllocationCheckerHelper {
static int ShapeSpecRank(const parser::Allocation &allocation) {
return static_cast<int>(
- std::get<std::list<parser::AllocateShapeSpec>>(allocation.t).size());
+ std::get<std::list<parser::AllocateShapeSpec>>(std::get<parser::AllocateShapeSpecListOrBounds>(allocation.t).u).size());
}
static int CoarraySpecRank(const parser::Allocation &allocation) {
@@ -612,7 +612,7 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) {
static_cast<std::size_t>(allocateShapeSpecRank_)) {
std::size_t j{0};
for (const auto &shapeSpec :
- std::get<std::list<parser::AllocateShapeSpec>>(allocation_.t)) {
+ std::get<std::list<parser::AllocateShapeSpec>>(std::get<parser::AllocateShapeSpecListOrBounds>(allocation_.t).u)) {
if (j >= allocateInfo_.sourceExprShape->size()) {
break;
}
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 2d7c32fd5fb8f..f1e4040ef401f 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -4373,6 +4373,11 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::PointerObject &x) {
return ExprOrVariable(x, parser::FindSourceLocation(x));
}
+MaybeExpr ExpressionAnalyzer::Analyze(const parser::AllocateShapeSpecListOrBounds &x) {
+ return std::nullopt;
+}
+
+
Expr<SubscriptInteger> ExpressionAnalyzer::AnalyzeKindSelector(
TypeCategory category,
const std::optional<parser::KindSelector> &selector) {
>From 225aa6047eb009efc7d3732c6e9aa34592ada7e0 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Wed, 25 Mar 2026 04:03:32 -0500
Subject: [PATCH 2/2] Implement rewrite for AllocateShapeBoundsSpec, and add
test
---
flang/lib/Semantics/check-allocate.cpp | 14 ++-
flang/lib/Semantics/expression.cpp | 62 +++++++++++
.../test/Semantics/allocate_array_bounds.f90 | 105 ++++++++++++++++++
3 files changed, 180 insertions(+), 1 deletion(-)
create mode 100644 flang/test/Semantics/allocate_array_bounds.f90
diff --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp
index bc582cb303bab..380d4816b75b6 100644
--- a/flang/lib/Semantics/check-allocate.cpp
+++ b/flang/lib/Semantics/check-allocate.cpp
@@ -47,6 +47,7 @@ class AllocationCheckerHelper {
const parser::Allocation &alloc, AllocateCheckerInfo &info)
: allocateInfo_{info}, allocation_{alloc},
allocateObject_{std::get<parser::AllocateObject>(alloc.t)},
+ isArray(IsArray(alloc)),
allocateShapeSpecRank_{ShapeSpecRank(alloc)},
allocateCoarraySpecRank_{CoarraySpecRank(alloc)} {}
@@ -57,7 +58,13 @@ class AllocationCheckerHelper {
bool hasAllocateCoarraySpec() const { return allocateCoarraySpecRank_ != 0; }
bool RunCoarrayRelatedChecks(SemanticsContext &) const;
- static int ShapeSpecRank(const parser::Allocation &allocation) {
+ static bool IsArray(const parser::Allocation &allocation) {
+ const auto &listOrBounds{std::get<parser::AllocateShapeSpecListOrBounds>(allocation.t)};
+ return std::get_if<parser::AllocateShapeBoundsSpec>(&listOrBounds.u);
+ }
+
+ int ShapeSpecRank(const parser::Allocation &allocation) {
+ if(isArray) return 1; // just need hasAllocateShapeSpecList to return false
return static_cast<int>(
std::get<std::list<parser::AllocateShapeSpec>>(std::get<parser::AllocateShapeSpecListOrBounds>(allocation.t).u).size());
}
@@ -91,6 +98,7 @@ class AllocationCheckerHelper {
AllocateCheckerInfo &allocateInfo_;
const parser::Allocation &allocation_;
const parser::AllocateObject &allocateObject_;
+ const bool isArray{false};
const int allocateShapeSpecRank_{0};
const int allocateCoarraySpecRank_{0};
const parser::Name &name_{parser::GetLastName(allocateObject_)};
@@ -600,6 +608,10 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) {
}
} else {
// explicit shape-spec-list
+ if(isArray) {
+ context.Say("TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp"_err_en_US);
+ return false;
+ }
if (allocateShapeSpecRank_ != rank_) {
context
.Say(name_.source,
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index f1e4040ef401f..da748bc402206 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -4374,6 +4374,68 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::PointerObject &x) {
}
MaybeExpr ExpressionAnalyzer::Analyze(const parser::AllocateShapeSpecListOrBounds &x) {
+ auto &shapeSpecList{
+ std::get<std::list<parser::AllocateShapeSpec>>(x.u)};
+ if(shapeSpecList.size() == 0) {
+ return std::nullopt;
+ }
+
+ if(shapeSpecList.size() == 1) {
+ // Get upper bound - BoundExpr is Scalar<Integer<Indirection<Expr>>>
+ const auto &upperBound{std::get<1>(shapeSpecList.front().t)};
+ const auto &lowerBoundOpt = std::get<0>(shapeSpecList.front().t);
+ const auto *lowerBound = lowerBoundOpt ? &*lowerBoundOpt : nullptr;
+
+ bool foundArray{false};
+ // We want to rewrite as an AllocateShapeBoundsSpec even if
+ // the element type is wrong (say a real instead of integer), so
+ // analyze as an unwrapped Expr for its rank, then analyze as
+ // an Integer<Indirection<Expr>>.
+ if(MaybeExpr analyzedExpr = Analyze(upperBound.thing.thing.value());
+ analyzedExpr && (analyzedExpr->Rank() > 0)) {
+ foundArray = true;
+ Analyze(upperBound.thing);
+ }
+ if(lowerBound) {
+ if(MaybeExpr analyzedExpr = Analyze(lowerBound->thing.thing.value());
+ analyzedExpr && analyzedExpr->Rank() > 0) {
+ foundArray = true;
+ Analyze(lowerBound->thing);
+ }
+ }
+
+ if(foundArray) {
+ // Get the IntExpr from the upper bound (BoundExpr.thing is the IntExpr)
+ auto &mutableUpperBound{const_cast<parser::BoundExpr&>(upperBound)};
+ parser::IntExpr upperIntExpr{std::move(mutableUpperBound.thing)};
+
+ // Handle optional lower bound
+ std::optional<parser::IntExpr> lowerIntExpr;
+ if(lowerBound) {
+ auto &mutableLowerBound{const_cast<parser::BoundExpr&>(*lowerBound)};
+ lowerIntExpr = std::move(mutableLowerBound.thing);
+ }
+
+ parser::AllocateShapeBoundsSpec boundsExpr{
+ std::make_tuple(std::move(lowerIntExpr), std::move(upperIntExpr))};
+ auto &mutableListOrBounds{const_cast<parser::AllocateShapeSpecListOrBounds&>(x)};
+ mutableListOrBounds.u = std::move(boundsExpr);
+
+ // Say("TODO: AllocateShapeBoundsSpec semantic chekcs in check-allocate.cpp"_err_en_US);
+ return std::nullopt;
+ }
+ }
+
+ // Analyze each AllocateShapeSpec, as a Scalar<Int<Expr>>
+ for(auto it = shapeSpecList.begin(); it != shapeSpecList.end(); ++it) {
+ const auto &upperBound{std::get<1>(it->t)};
+ Analyze(upperBound);
+ const auto &lowerBoundOpt{std::get<0>(it->t)};
+ if(lowerBoundOpt) {
+ Analyze(*lowerBoundOpt);
+ }
+ }
+
return std::nullopt;
}
diff --git a/flang/test/Semantics/allocate_array_bounds.f90 b/flang/test/Semantics/allocate_array_bounds.f90
new file mode 100644
index 0000000000000..cfb29b7fa8661
--- /dev/null
+++ b/flang/test/Semantics/allocate_array_bounds.f90
@@ -0,0 +1,105 @@
+! RUN: %python %S/test_errors.py %s %flang_fc1
+program int_array_alloc_03
+ implicit none
+ real, allocatable, dimension(:) :: rank1_test_array
+ real, allocatable, dimension(:,:,:) :: test_array
+
+ integer :: seven = 7
+ integer :: valid_lower(3) = [1,1,1]
+ integer :: lower(4), upper(4)
+ integer :: rank_2_array(3,3), rank_3_array(3,3,3)
+ ! Positive test cases, expecting no errors
+ ! Test direct use of scalar integer and array integer expressions
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(rank1_test_array([5]))
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(rank1_test_array([1]:[5]))
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(rank1_test_array(1:[5]))
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(rank1_test_array([1]:5))
+
+ ! Test indirect use of scalar integer and array integer expressions
+ ! array : array
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array([1,2,3] : [1,2,3] + 1))
+ ! array : array
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(valid_lower - 1 : seven * (valid_lower + seven)))
+ ! array : scalar (broadcast)
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(valid_lower : return_seven()))
+ ! scalar : array (broadcast)
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(seven : [9,9,9]))
+
+ !Negative test cases, expecting errors
+ !ERROR: Must have INTEGER type, but is REAL(4)
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array([1.2,2.2,3.2]:[1,2,3]))
+
+ !future_ERROR: ALLOCATE bounds integer rank-1 arrays must have the same size; lower bounds has 3 elements, upper bounds has 2 elements
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array([1,2,3]:[3,3]))
+
+ !future_ERROR: ALLOCATE bounds integer rank-1 arrays have 4 elements but allocatable object 'test_array' has rank 3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(lower:upper))
+ !future_ERROR: ALLOCATE upper bounds integer rank-1 array has 4 elements but allocatable object 'test_array' has rank 3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(7 : [1,2,3,4]))
+ !future_ERROR: ALLOCATE lower bounds integer rank-1 array has 2 elements but allocatable object 'test_array' has rank 3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array([1,2] : 7))
+
+ !future_ERROR: Integer array used as upper bounds in ALLOCATE must be rank-1 but is rank-3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array([1,2,4] : rank_3_array))
+ !future_ERROR: Integer array used as lower bounds in ALLOCATE must be rank-1 but is rank-2
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(rank_2_array : [1,2,4]))
+ !future_ERROR: Integer array used as lower bounds in ALLOCATE must be rank-1 but is rank-2
+ !future_ERROR: Integer array used as upper bounds in ALLOCATE must be rank-1 but is rank-3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(rank_2_array : rank_3_array))
+ !future_ERROR: Integer array used as lower bounds in ALLOCATE must be rank-1 but is rank-2
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(rank_2_array : 7))
+ !future_ERROR: Integer array used as upper bounds in ALLOCATE must be rank-1 but is rank-3
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_array(7 : rank_3_array))
+
+ ! Test that any comma list is parsed as AllocateShapeSpecList and not rewritten
+ ! to AllocateShapeSpecArray, giving error messages expecting same number of
+ ! aruments as rank of test_array and scalar integers
+ !ERROR: The number of shape specifications, when they appear, must match the rank of allocatable object
+ !ERROR: Must be a scalar value, but is a rank-1 array
+ !ERROR: Must be a scalar value, but is a rank-1 array
+ !ERROR: Must be a scalar value, but is a rank-1 array
+ !ERROR: Must have INTEGER type, but is REAL(4)
+ allocate(test_array([1,2,3] : [2,3,4], 3, [1,2,3], 5.2))
+
+ contains
+ subroutine tmp02(unknown_size, test_ptr_01)
+ real, allocatable, dimension(:,:,:), INTENT(OUT) :: test_ptr_01
+ integer, INTENT(IN) :: unknown_size
+ integer :: lower(unknown_size), upper(unknown_size)
+
+ !future_ERROR: Rank-1 integer array used as upper bounds in ALLOCATE must have constant size
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_ptr_01(upper))
+ !future_ERROR: Rank-1 integer array used as lower bounds in ALLOCATE must have constant size
+ !future_ERROR: Rank-1 integer array used as upper bounds in ALLOCATE must have constant size
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_ptr_01(lower : upper))
+ !future_ERROR: Rank-1 integer array used as lower bounds in ALLOCATE must have constant size
+ !ERROR: TODO: AllocateShapeBoundsSpec semantic checks in check-allocate.cpp
+ allocate(test_ptr_01(lower : 10))
+ end subroutine
+
+ function return_seven()
+ integer :: return_seven
+ return_seven = 7
+ end function
+
+end program
More information about the flang-commits
mailing list