[flang-commits] [flang] [flang][semantic] Implement semantic checks and new data structure for explicit-shape-bounds-spec (PR #203030)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Tue Jul 28 14:46:47 PDT 2026


================
@@ -396,11 +408,165 @@ 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 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)};
+    // 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 "
+          "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))};
+    // 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)};
+    CHECK(someInt);
+    auto asSI{evaluate::Fold(context_.foldingContext(),
+        evaluate::ConvertToType<evaluate::SubscriptInteger>(
+            common::Clone(*someInt)))};
+    if (folded.Rank() == 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{
+        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)}},
+        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::optional<std::int64_t>>> lbResult;
+  if (lowerBoundOpt) {
+    lbResult = analyzeBound(*lowerBoundOpt, /*isUpper=*/false);
+  }
+
+  if (hasError) {
+    return std::nullopt;
+  }
+
+  // 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()};
+
+  // 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);
+    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));
+  }
+  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);
+  if (numDims == 0) {
----------------
eugeneepshteyn wrote:

I'm concerned that the user wouldn't get any notification that the impure function call is thrown away, especially if that call was supposed to have side-effects. I think there should be an error here like in the cases you mentioned above. 

I don't think this should block this PR, though. Perhaps we can file an issue that could be fixed later.

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


More information about the flang-commits mailing list