Index: flang/docs/Extensions.md =================================================================== --- flang/docs/Extensions.md +++ flang/docs/Extensions.md @@ -71,6 +71,10 @@ In common with some other compilers, the clock is in milliseconds for kinds <= 4 and nanoseconds otherwise where the target system supports these rates. +* If a dimension of a descriptor has zero extent in a call to + `CFI_section`, `CFI_setpointer` or `CFI_allocate`, the lower + bound on that dimension will be set to 1 for consistency with + the `LBOUND()` intrinsic function. ## Extensions, deletions, and legacy features supported by default Index: flang/include/flang/Evaluate/shape.h =================================================================== --- flang/include/flang/Evaluate/shape.h +++ flang/include/flang/Evaluate/shape.h @@ -62,16 +62,27 @@ // The dimension argument to these inquiries is zero-based, // unlike the DIM= arguments to many intrinsics. -ExtentExpr GetLowerBound(const NamedEntity &, int dimension); -ExtentExpr GetLowerBound(FoldingContext &, const NamedEntity &, int dimension); +// +// GetRawLowerBound() returns a lower bound expression, which may +// not be suitable for all purposes; specifically, it might not be invariant +// in its scope, and it will not have been forced to 1 on an empty dimension. +// GetLBOUND()'s result is safer, but it is optional because it does fail +// in those circumstances. +ExtentExpr GetRawLowerBound(const NamedEntity &, int dimension); +ExtentExpr GetRawLowerBound( + FoldingContext &, const NamedEntity &, int dimension); +MaybeExtentExpr GetLBOUND(const NamedEntity &, int dimension); +MaybeExtentExpr GetLBOUND(FoldingContext &, const NamedEntity &, int dimension); MaybeExtentExpr GetUpperBound(const NamedEntity &, int dimension); MaybeExtentExpr GetUpperBound( FoldingContext &, const NamedEntity &, int dimension); MaybeExtentExpr ComputeUpperBound(ExtentExpr &&lower, MaybeExtentExpr &&extent); MaybeExtentExpr ComputeUpperBound( FoldingContext &, ExtentExpr &&lower, MaybeExtentExpr &&extent); -Shape GetLowerBounds(const NamedEntity &); -Shape GetLowerBounds(FoldingContext &, const NamedEntity &); +Shape GetRawLowerBounds(const NamedEntity &); +Shape GetRawLowerBounds(FoldingContext &, const NamedEntity &); +Shape GetLBOUNDs(const NamedEntity &); +Shape GetLBOUNDs(FoldingContext &, const NamedEntity &); Shape GetUpperBounds(const NamedEntity &); Shape GetUpperBounds(FoldingContext &, const NamedEntity &); MaybeExtentExpr GetExtent(const NamedEntity &, int dimension); Index: flang/include/flang/Runtime/descriptor.h =================================================================== --- flang/include/flang/Runtime/descriptor.h +++ flang/include/flang/Runtime/descriptor.h @@ -50,10 +50,17 @@ SubscriptValue ByteStride() const { return raw_.sm; } Dimension &SetBounds(SubscriptValue lower, SubscriptValue upper) { - raw_.lower_bound = lower; - raw_.extent = upper >= lower ? upper - lower + 1 : 0; + if (upper >= lower) { + raw_.lower_bound = lower; + raw_.extent = upper - lower + 1; + } else { + raw_.lower_bound = 1; + raw_.extent = 0; + } return *this; } + // Do not use this API to cause the LB of an empty dimension + // to anything other than 1. Use SetBounds() instead if you can. Dimension &SetLowerBound(SubscriptValue lower) { raw_.lower_bound = lower; return *this; Index: flang/lib/Evaluate/check-expression.cpp =================================================================== --- flang/lib/Evaluate/check-expression.cpp +++ flang/lib/Evaluate/check-expression.cpp @@ -120,7 +120,7 @@ } else if (intrinsic->name == "lbound" && call.arguments().size() == 1) { // LBOUND(x) without DIM= auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; - return base && IsConstantExprShape(GetLowerBounds(*base)); + return base && IsConstantExprShape(GetLBOUNDs(*base)); } else if (intrinsic->name == "ubound" && call.arguments().size() == 1) { // UBOUND(x) without DIM= auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; @@ -434,7 +434,7 @@ // expand the scalar constant to an array return ScalarConstantExpander{std::move(*extents), AsConstantExtents( - context, GetLowerBounds(context, NamedEntity{symbol}))} + context, GetRawLowerBounds(context, NamedEntity{symbol}))} .Expand(std::move(folded)); } else if (auto resultShape{GetShape(context, folded)}) { if (CheckConformance(context.messages(), symTS->shape(), @@ -443,8 +443,8 @@ .value_or(false /*fail if not known now to conform*/)) { // make a constant array with adjusted lower bounds return ArrayConstantBoundChanger{ - std::move(*AsConstantExtents( - context, GetLowerBounds(context, NamedEntity{symbol})))} + std::move(*AsConstantExtents(context, + GetRawLowerBounds(context, NamedEntity{symbol})))} .ChangeLbounds(std::move(folded)); } } Index: flang/lib/Evaluate/constant.cpp =================================================================== --- flang/lib/Evaluate/constant.cpp +++ flang/lib/Evaluate/constant.cpp @@ -25,6 +25,11 @@ void ConstantBounds::set_lbounds(ConstantSubscripts &&lb) { CHECK(lb.size() == shape_.size()); lbounds_ = std::move(lb); + for (std::size_t j{0}; j < shape_.size(); ++j) { + if (shape_[j] == 0) { + lbounds_[j] = 1; + } + } } void ConstantBounds::SetLowerBoundsToOne() { Index: flang/lib/Evaluate/fold-designator.cpp =================================================================== --- flang/lib/Evaluate/fold-designator.cpp +++ flang/lib/Evaluate/fold-designator.cpp @@ -50,7 +50,7 @@ if (auto type{DynamicType::From(array)}) { if (auto extents{GetConstantExtents(context_, array)}) { if (auto bytes{ToInt64(type->MeasureSizeInBytes(context_, true))}) { - Shape lbs{GetLowerBounds(context_, x.base())}; + Shape lbs{GetLBOUNDs(context_, x.base())}; if (auto lowerBounds{AsConstantExtents(context_, lbs)}) { std::optional result; if (!x.base().IsSymbol() && @@ -206,7 +206,7 @@ NamedEntity &&entity, const Shape &shape, const DynamicType &elementType, ConstantSubscript &offset) { auto extents{AsConstantExtents(context, shape)}; - Shape lbs{GetLowerBounds(context, entity)}; + Shape lbs{GetRawLowerBounds(context, entity)}; auto lower{AsConstantExtents(context, lbs)}; auto elementBytes{ToInt64(elementType.MeasureSizeInBytes(context, true))}; if (!extents || !lower || !elementBytes || *elementBytes <= 0) { Index: flang/lib/Evaluate/fold-integer.cpp =================================================================== --- flang/lib/Evaluate/fold-integer.cpp +++ flang/lib/Evaluate/fold-integer.cpp @@ -76,10 +76,11 @@ if (symbol.Rank() == rank) { lowerBoundsAreOne = false; if (dim) { - return Fold(context, - ConvertToType(GetLowerBound(context, *named, *dim))); + if (auto lb{GetLBOUND(context, *named, *dim)}) { + return Fold(context, ConvertToType(std::move(*lb))); + } } else if (auto extents{ - AsExtentArrayExpr(GetLowerBounds(context, *named))}) { + AsExtentArrayExpr(GetLBOUNDs(context, *named))}) { return Fold(context, ConvertToType(Expr{std::move(*extents)})); } Index: flang/lib/Evaluate/fold.cpp =================================================================== --- flang/lib/Evaluate/fold.cpp +++ flang/lib/Evaluate/fold.cpp @@ -36,12 +36,13 @@ auto lower{triplet.lower()}, upper{triplet.upper()}; std::optional stride{ToInt64(triplet.stride())}; if (!lower) { - lower = GetLowerBound(context, base, dim); + lower = GetLBOUND(context, base, dim); } if (!upper) { - upper = - ComputeUpperBound(context, GetLowerBound(context, base, dim), - GetExtent(context, base, dim)); + if (auto lb{GetLBOUND(context, base, dim)}) { + upper = ComputeUpperBound( + context, std::move(*lb), GetExtent(context, base, dim)); + } } auto lbi{ToInt64(lower)}, ubi{ToInt64(upper)}; if (lbi && ubi && stride && *stride != 0) { Index: flang/lib/Evaluate/shape.cpp =================================================================== --- flang/lib/Evaluate/shape.cpp +++ flang/lib/Evaluate/shape.cpp @@ -229,101 +229,151 @@ // Determines lower bound on a dimension. This can be other than 1 only // for a reference to a whole array object or component. (See LBOUND, 16.9.109). // ASSOCIATE construct entities may require traversal of their referents. -class GetLowerBoundHelper : public Traverse { +template +class GetLowerBoundHelper + : public Traverse, RESULT> { public: - using Result = ExtentExpr; - using Base = Traverse; + using Result = RESULT; + using Base = Traverse; using Base::operator(); - explicit GetLowerBoundHelper(int d) : Base{*this}, dimension_{d} {} - static ExtentExpr Default() { return ExtentExpr{1}; } - static ExtentExpr Combine(Result &&, Result &&) { return Default(); } - ExtentExpr operator()(const Symbol &); - ExtentExpr operator()(const Component &); - -private: - int dimension_; -}; + explicit GetLowerBoundHelper(int d, FoldingContext *context) + : Base{*this}, dimension_{d}, context_{context} {} + static Result Default() { return Result{1}; } + static Result Combine(Result &&, Result &&) { + // Operator results and array references always have lower bounds == 1 + return Result{1}; + } -auto GetLowerBoundHelper::operator()(const Symbol &symbol0) -> Result { - const Symbol &symbol{symbol0.GetUltimate()}; - if (const auto *details{symbol.detailsIf()}) { - int j{0}; - for (const auto &shapeSpec : details->shape()) { - if (j++ == dimension_) { - const auto &bound{shapeSpec.lbound().GetExplicit()}; - if (bound && IsScopeInvariantExpr(*bound)) { - return *bound; - } else if (IsDescriptor(symbol)) { + Result operator()(const Symbol &symbol0) const { + const Symbol &symbol{symbol0.GetUltimate()}; + if (const auto *details{ + symbol.detailsIf()}) { + int rank{details->shape().Rank()}; + if (dimension_ < rank) { + const semantics::ShapeSpec &shapeSpec{details->shape()[dimension_]}; + if (shapeSpec.lbound().isExplicit()) { + if (const auto &lbound{shapeSpec.lbound().GetExplicit()}) { + if constexpr (LBOUND_SEMANTICS) { + bool ok{false}; + auto lbValue{ToInt64(*lbound)}; + if (dimension_ == rank - 1 && details->IsAssumedSize()) { + // last dimension of assumed-size dummy array: don't worry + // about handling an empty dimension + ok = IsScopeInvariantExpr(*lbound); + } else if (lbValue.value_or(0) == 1) { + // Lower bound is 1, regardless of extent + ok = true; + } else if (const auto &ubound{shapeSpec.ubound().GetExplicit()}) { + // If we can't prove that the dimension is nonempty, + // we must be conservative. + // TODO: simple symbolic math in expression rewriting to + // cope with cases like A(J:J) + if (context_) { + auto extent{ToInt64(Fold(*context_, + ExtentExpr{*ubound} - ExtentExpr{*lbound} + + ExtentExpr{1}))}; + ok = extent && *extent > 0; + } else { + auto ubValue{ToInt64(*ubound)}; + ok = lbValue && ubValue && *lbValue <= *ubValue; + } + } + return ok ? *lbound : Result{}; + } else { + return *lbound; + } + } else { + return Result{1}; + } + } + if (IsDescriptor(symbol)) { return ExtentExpr{DescriptorInquiry{NamedEntity{symbol0}, DescriptorInquiry::Field::LowerBound, dimension_}}; - } else { - break; } } - } - } else if (const auto *assoc{ - symbol.detailsIf()}) { - if (assoc->rank()) { // SELECT RANK case - const Symbol &resolved{ResolveAssociations(symbol)}; - if (IsDescriptor(resolved) && dimension_ < *assoc->rank()) { - return ExtentExpr{DescriptorInquiry{NamedEntity{symbol0}, - DescriptorInquiry::Field::LowerBound, dimension_}}; + } else if (const auto *assoc{ + symbol.detailsIf()}) { + if (assoc->rank()) { // SELECT RANK case + const Symbol &resolved{ResolveAssociations(symbol)}; + if (IsDescriptor(resolved) && dimension_ < *assoc->rank()) { + return ExtentExpr{DescriptorInquiry{NamedEntity{symbol0}, + DescriptorInquiry::Field::LowerBound, dimension_}}; + } + } else { + return (*this)(assoc->expr()); } + } + if constexpr (LBOUND_SEMANTICS) { + return Result{}; } else { - return (*this)(assoc->expr()); + return Result{1}; } } - return Default(); -} -auto GetLowerBoundHelper::operator()(const Component &component) -> Result { - if (component.base().Rank() == 0) { - const Symbol &symbol{component.GetLastSymbol().GetUltimate()}; - if (const auto *details{ - symbol.detailsIf()}) { - int j{0}; - for (const auto &shapeSpec : details->shape()) { - if (j++ == dimension_) { - const auto &bound{shapeSpec.lbound().GetExplicit()}; - if (bound && IsScopeInvariantExpr(*bound)) { - return *bound; - } else if (IsDescriptor(symbol)) { - return ExtentExpr{ - DescriptorInquiry{NamedEntity{common::Clone(component)}, - DescriptorInquiry::Field::LowerBound, dimension_}}; - } else { - break; - } - } - } + Result operator()(const Component &component) const { + if (component.base().Rank() == 0) { + return (*this)(component.GetLastSymbol()); } + return Result{1}; } - return Default(); + +private: + int dimension_; + FoldingContext *context_{nullptr}; +}; + +ExtentExpr GetRawLowerBound(const NamedEntity &base, int dimension) { + return GetLowerBoundHelper{dimension, nullptr}(base); +} + +ExtentExpr GetRawLowerBound( + FoldingContext &context, const NamedEntity &base, int dimension) { + return Fold(context, + GetLowerBoundHelper{dimension, &context}(base)); } -ExtentExpr GetLowerBound(const NamedEntity &base, int dimension) { - return GetLowerBoundHelper{dimension}(base); +MaybeExtentExpr GetLBOUND(const NamedEntity &base, int dimension) { + return GetLowerBoundHelper{dimension, nullptr}(base); } -ExtentExpr GetLowerBound( +MaybeExtentExpr GetLBOUND( FoldingContext &context, const NamedEntity &base, int dimension) { - return Fold(context, GetLowerBound(base, dimension)); + return Fold(context, + GetLowerBoundHelper{dimension, &context}(base)); } -Shape GetLowerBounds(const NamedEntity &base) { +Shape GetRawLowerBounds(const NamedEntity &base) { Shape result; int rank{base.Rank()}; for (int dim{0}; dim < rank; ++dim) { - result.emplace_back(GetLowerBound(base, dim)); + result.emplace_back(GetRawLowerBound(base, dim)); } return result; } -Shape GetLowerBounds(FoldingContext &context, const NamedEntity &base) { +Shape GetRawLowerBounds(FoldingContext &context, const NamedEntity &base) { Shape result; int rank{base.Rank()}; for (int dim{0}; dim < rank; ++dim) { - result.emplace_back(GetLowerBound(context, base, dim)); + result.emplace_back(GetRawLowerBound(context, base, dim)); + } + return result; +} + +Shape GetLBOUNDs(const NamedEntity &base) { + Shape result; + int rank{base.Rank()}; + for (int dim{0}; dim < rank; ++dim) { + result.emplace_back(GetLBOUND(base, dim)); + } + return result; +} + +Shape GetLBOUNDs(FoldingContext &context, const NamedEntity &base) { + Shape result; + int rank{base.Rank()}; + for (int dim{0}; dim < rank; ++dim) { + result.emplace_back(GetLBOUND(context, base, dim)); } return result; } @@ -420,7 +470,7 @@ } MaybeExtentExpr lower{triplet.lower()}; if (!lower) { - lower = GetLowerBound(base, dimension); + lower = GetLBOUND(base, dimension); } return CountTrips(std::move(lower), std::move(upper), MaybeExtentExpr{triplet.stride()}); @@ -472,9 +522,8 @@ return *bound; } else if (details->IsAssumedSize() && dimension + 1 == symbol.Rank()) { break; - } else { - return ComputeUpperBound( - GetLowerBound(base, dimension), GetExtent(base, dimension)); + } else if (auto lb{GetLBOUND(base, dimension)}) { + return ComputeUpperBound(std::move(*lb), GetExtent(base, dimension)); } } } @@ -482,8 +531,10 @@ symbol.detailsIf()}) { if (auto shape{GetShape(assoc->expr())}) { if (dimension < static_cast(shape->size())) { - return ComputeUpperBound( - GetLowerBound(base, dimension), std::move(shape->at(dimension))); + if (auto lb{GetLBOUND(base, dimension)}) { + return ComputeUpperBound( + std::move(*lb), std::move(shape->at(dimension))); + } } } } @@ -506,9 +557,11 @@ result.push_back(*bound); } else if (details->IsAssumedSize() && dim + 1 == base.Rank()) { result.emplace_back(std::nullopt); // UBOUND folding replaces with -1 - } else { + } else if (auto lb{GetLBOUND(base, dim)}) { result.emplace_back( - ComputeUpperBound(GetLowerBound(base, dim), GetExtent(base, dim))); + ComputeUpperBound(std::move(*lb), GetExtent(base, dim))); + } else { + result.emplace_back(); // unknown } ++dim; } Index: flang/lib/Semantics/runtime-type-info.cpp =================================================================== --- flang/lib/Semantics/runtime-type-info.cpp +++ flang/lib/Semantics/runtime-type-info.cpp @@ -794,9 +794,10 @@ std::vector bounds; evaluate::NamedEntity entity{symbol}; for (int j{0}; j < rank; ++j) { - bounds.emplace_back(GetValue(std::make_optional(evaluate::GetLowerBound( - foldingContext, entity, j)), - parameters)); + bounds.emplace_back( + GetValue(std::make_optional( + evaluate::GetRawLowerBound(foldingContext, entity, j)), + parameters)); bounds.emplace_back(GetValue( evaluate::GetUpperBound(foldingContext, entity, j), parameters)); } Index: flang/runtime/ISO_Fortran_binding.cpp =================================================================== --- flang/runtime/ISO_Fortran_binding.cpp +++ flang/runtime/ISO_Fortran_binding.cpp @@ -72,7 +72,7 @@ CFI_index_t lb{lower_bounds[j]}; CFI_index_t ub{upper_bounds[j]}; CFI_index_t extent{ub >= lb ? ub - lb + 1 : 0}; - dim->lower_bound = lb; + dim->lower_bound = extent == 0 ? 1 : lb; dim->extent = extent; dim->sm = byteSize; byteSize *= extent; @@ -361,8 +361,10 @@ resRank = 0; for (int j{0}; j < source->rank; ++j) { if (actualStride[j] != 0) { - result->dim[resRank].lower_bound = 0; result->dim[resRank].extent = extent[j]; + result->dim[resRank].lower_bound = extent[j] == 0 ? 1 + : lower_bounds ? lower_bounds[j] + : source->dim[j].lower_bound; result->dim[resRank].sm = actualStride[j] * source->dim[j].sm; ++resRank; } @@ -437,10 +439,12 @@ result->base_addr = source->base_addr; if (source->base_addr) { for (int j{0}; j < result->rank; ++j) { - result->dim[j].extent = source->dim[j].extent; + CFI_index_t extent{source->dim[j].extent}; + result->dim[j].extent = extent; result->dim[j].sm = source->dim[j].sm; - result->dim[j].lower_bound = - copySrcLB ? source->dim[j].lower_bound : lower_bounds[j]; + result->dim[j].lower_bound = extent == 0 ? 1 + : copySrcLB ? source->dim[j].lower_bound + : lower_bounds[j]; } } return CFI_SUCCESS; Index: flang/runtime/pointer.cpp =================================================================== --- flang/runtime/pointer.cpp +++ flang/runtime/pointer.cpp @@ -76,9 +76,11 @@ Terminator terminator{__FILE__, __LINE__}; std::size_t boundElementBytes{lowerBounds.ElementBytes()}; for (int j{0}; j < rank; ++j) { - pointer.GetDimension(j).SetLowerBound( - GetInt64(lowerBounds.ZeroBasedIndexedElement(j), - boundElementBytes, terminator)); + Dimension &dim{pointer.GetDimension(j)}; + dim.SetLowerBound(dim.Extent() == 0 + ? 1 + : GetInt64(lowerBounds.ZeroBasedIndexedElement(j), + boundElementBytes, terminator)); } } Index: flang/test/Evaluate/folding08.f90 =================================================================== --- flang/test/Evaluate/folding08.f90 +++ flang/test/Evaluate/folding08.f90 @@ -2,50 +2,53 @@ ! Test folding of LBOUND and UBOUND module m + real :: a3(42:52) + integer, parameter :: lba3(*) = lbound(a3) + logical, parameter :: test_lba3 = all(lba3 == [42]) + type :: t + real :: a + end type + type(t) :: ta(0:2) + character(len=2) :: ca(-1:1) + integer, parameter :: lbtadim = lbound(ta,1) + logical, parameter :: test_lbtadim = lbtadim == 0 + integer, parameter :: ubtadim = ubound(ta,1) + logical, parameter :: test_ubtadim = ubtadim == 2 + integer, parameter :: lbta1(*) = lbound(ta) + logical, parameter :: test_lbta1 = all(lbta1 == [0]) + integer, parameter :: ubta1(*) = ubound(ta) + logical, parameter :: test_ubta1 = all(ubta1 == [2]) + integer, parameter :: lbta2(*) = lbound(ta(:)) + logical, parameter :: test_lbta2 = all(lbta2 == [1]) + integer, parameter :: ubta2(*) = ubound(ta(:)) + logical, parameter :: test_ubta2 = all(ubta2 == [3]) + integer, parameter :: lbta3(*) = lbound(ta%a) + logical, parameter :: test_lbta3 = all(lbta3 == [1]) + integer, parameter :: ubta3(*) = ubound(ta%a) + logical, parameter :: test_ubta3 = all(ubta3 == [3]) + integer, parameter :: lbca1(*) = lbound(ca) + logical, parameter :: test_lbca1 = all(lbca1 == [-1]) + integer, parameter :: ubca1(*) = ubound(ca) + logical, parameter :: test_ubca1 = all(ubca1 == [1]) + integer, parameter :: lbca2(*) = lbound(ca(:)(1:1)) + logical, parameter :: test_lbca2 = all(lbca2 == [1]) + integer, parameter :: ubca2(*) = ubound(ca(:)(1:1)) + logical, parameter :: test_ubca2 = all(ubca2 == [3]) + integer, parameter :: lbfoo(*) = lbound(foo()) + logical, parameter :: test_lbfoo = all(lbfoo == [1,1]) + integer, parameter :: ubfoo(*) = ubound(foo()) + logical, parameter :: test_ubfoo = all(ubfoo == [2,3]) contains function foo() real :: foo(2:3,4:6) end function subroutine test(n1,a1,a2) integer, intent(in) :: n1 - real, intent(in) :: a1(0:n1), a2(0:*) - type :: t - real :: a - end type - type(t) :: ta(0:2) - character(len=2) :: ca(-1:1) + real, intent(in) :: a1(1:n1), a2(0:*) integer, parameter :: lba1(*) = lbound(a1) - logical, parameter :: test_lba1 = all(lba1 == [0]) + logical, parameter :: test_lba1 = all(lba1 == [1]) integer, parameter :: lba2(*) = lbound(a2) logical, parameter :: test_lba2 = all(lba2 == [0]) - integer, parameter :: lbtadim = lbound(ta,1) - logical, parameter :: test_lbtadim = lbtadim == 0 - integer, parameter :: ubtadim = ubound(ta,1) - logical, parameter :: test_ubtadim = ubtadim == 2 - integer, parameter :: lbta1(*) = lbound(ta) - logical, parameter :: test_lbta1 = all(lbta1 == [0]) - integer, parameter :: ubta1(*) = ubound(ta) - logical, parameter :: test_ubta1 = all(ubta1 == [2]) - integer, parameter :: lbta2(*) = lbound(ta(:)) - logical, parameter :: test_lbta2 = all(lbta2 == [1]) - integer, parameter :: ubta2(*) = ubound(ta(:)) - logical, parameter :: test_ubta2 = all(ubta2 == [3]) - integer, parameter :: lbta3(*) = lbound(ta%a) - logical, parameter :: test_lbta3 = all(lbta3 == [1]) - integer, parameter :: ubta3(*) = ubound(ta%a) - logical, parameter :: test_ubta3 = all(ubta3 == [3]) - integer, parameter :: lbca1(*) = lbound(ca) - logical, parameter :: test_lbca1 = all(lbca1 == [-1]) - integer, parameter :: ubca1(*) = ubound(ca) - logical, parameter :: test_ubca1 = all(ubca1 == [1]) - integer, parameter :: lbca2(*) = lbound(ca(:)(1:1)) - logical, parameter :: test_lbca2 = all(lbca2 == [1]) - integer, parameter :: ubca2(*) = ubound(ca(:)(1:1)) - logical, parameter :: test_ubca2 = all(ubca2 == [3]) - integer, parameter :: lbfoo(*) = lbound(foo()) - logical, parameter :: test_lbfoo = all(lbfoo == [1,1]) - integer, parameter :: ubfoo(*) = ubound(foo()) - logical, parameter :: test_ubfoo = all(ubfoo == [2,3]) end subroutine subroutine test2 real :: a(2:3,4:6)