| //===-- lib/Evaluate/check-expression.cpp ---------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "flang/Evaluate/check-expression.h" |
| #include "flang/Evaluate/characteristics.h" |
| #include "flang/Evaluate/intrinsics.h" |
| #include "flang/Evaluate/tools.h" |
| #include "flang/Evaluate/traverse.h" |
| #include "flang/Evaluate/type.h" |
| #include "flang/Semantics/semantics.h" |
| #include "flang/Semantics/symbol.h" |
| #include "flang/Semantics/tools.h" |
| #include <set> |
| #include <string> |
| |
| namespace Fortran::evaluate { |
| |
| // Constant expression predicates IsConstantExpr() & IsScopeInvariantExpr(). |
| // This code determines whether an expression is a "constant expression" |
| // in the sense of section 10.1.12. This is not the same thing as being |
| // able to fold it (yet) into a known constant value; specifically, |
| // the expression may reference derived type kind parameters whose values |
| // are not yet known. |
| // |
| // The variant form (IsScopeInvariantExpr()) also accepts symbols that are |
| // INTENT(IN) dummy arguments without the VALUE attribute. |
| template <bool INVARIANT> |
| class IsConstantExprHelper |
| : public AllTraverse<IsConstantExprHelper<INVARIANT>, true> { |
| public: |
| using Base = AllTraverse<IsConstantExprHelper, true>; |
| IsConstantExprHelper() : Base{*this} {} |
| using Base::operator(); |
| |
| // A missing expression is not considered to be constant. |
| template <typename A> bool operator()(const std::optional<A> &x) const { |
| return x && (*this)(*x); |
| } |
| |
| bool operator()(const TypeParamInquiry &inq) const { |
| return INVARIANT || semantics::IsKindTypeParameter(inq.parameter()); |
| } |
| bool operator()(const semantics::Symbol &symbol) const { |
| const auto &ultimate{GetAssociationRoot(symbol)}; |
| return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) || |
| IsInitialProcedureTarget(ultimate) || |
| ultimate.has<semantics::TypeParamDetails>() || |
| (INVARIANT && IsIntentIn(symbol) && !IsOptional(symbol) && |
| !symbol.attrs().test(semantics::Attr::VALUE)); |
| } |
| bool operator()(const CoarrayRef &) const { return false; } |
| bool operator()(const semantics::ParamValue ¶m) const { |
| return param.isExplicit() && (*this)(param.GetExplicit()); |
| } |
| bool operator()(const ProcedureRef &) const; |
| bool operator()(const StructureConstructor &constructor) const { |
| for (const auto &[symRef, expr] : constructor) { |
| if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| bool operator()(const Component &component) const { |
| return (*this)(component.base()); |
| } |
| // Prevent integer division by known zeroes in constant expressions. |
| template <int KIND> |
| bool operator()( |
| const Divide<Type<TypeCategory::Integer, KIND>> &division) const { |
| using T = Type<TypeCategory::Integer, KIND>; |
| if ((*this)(division.left()) && (*this)(division.right())) { |
| const auto divisor{GetScalarConstantValue<T>(division.right())}; |
| return !divisor || !divisor->IsZero(); |
| } else { |
| return false; |
| } |
| } |
| |
| bool operator()(const Constant<SomeDerived> &) const { return true; } |
| bool operator()(const DescriptorInquiry &x) const { |
| const Symbol &sym{x.base().GetLastSymbol()}; |
| return INVARIANT && !IsAllocatable(sym) && |
| (!IsDummy(sym) || |
| (IsIntentIn(sym) && !IsOptional(sym) && |
| !sym.attrs().test(semantics::Attr::VALUE))); |
| } |
| |
| private: |
| bool IsConstantStructureConstructorComponent( |
| const Symbol &, const Expr<SomeType> &) const; |
| bool IsConstantExprShape(const Shape &) const; |
| }; |
| |
| template <bool INVARIANT> |
| bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent( |
| const Symbol &component, const Expr<SomeType> &expr) const { |
| if (IsAllocatable(component)) { |
| return IsNullObjectPointer(&expr); |
| } else if (IsPointer(component)) { |
| return IsNullPointerOrAllocatable(&expr) || IsInitialDataTarget(expr) || |
| IsInitialProcedureTarget(expr); |
| } else { |
| return (*this)(expr); |
| } |
| } |
| |
| template <bool INVARIANT> |
| bool IsConstantExprHelper<INVARIANT>::operator()( |
| const ProcedureRef &call) const { |
| // LBOUND, UBOUND, and SIZE with truly constant DIM= arguments will have |
| // been rewritten into DescriptorInquiry operations. |
| if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) { |
| const characteristics::Procedure &proc{intrinsic->characteristics.value()}; |
| if (intrinsic->name == "kind" || |
| intrinsic->name == IntrinsicProcTable::InvalidName || |
| call.arguments().empty() || !call.arguments()[0]) { |
| // kind is always a constant, and we avoid cascading errors by considering |
| // invalid calls to intrinsics to be constant |
| return true; |
| } else if (intrinsic->name == "lbound") { |
| auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; |
| return base && IsConstantExprShape(GetLBOUNDs(*base)); |
| } else if (intrinsic->name == "ubound") { |
| auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; |
| return base && IsConstantExprShape(GetUBOUNDs(*base)); |
| } else if (intrinsic->name == "shape" || intrinsic->name == "size") { |
| auto shape{GetShape(call.arguments()[0]->UnwrapExpr())}; |
| return shape && IsConstantExprShape(*shape); |
| } else if (proc.IsPure()) { |
| std::size_t j{0}; |
| for (const auto &arg : call.arguments()) { |
| if (const auto *dataDummy{j < proc.dummyArguments.size() |
| ? std::get_if<characteristics::DummyDataObject>( |
| &proc.dummyArguments[j].u) |
| : nullptr}; |
| dataDummy && |
| dataDummy->attrs.test( |
| characteristics::DummyDataObject::Attr::OnlyIntrinsicInquiry)) { |
| // The value of the argument doesn't matter |
| } else if (!arg) { |
| return false; |
| } else if (const auto *expr{arg->UnwrapExpr()}; |
| !expr || !(*this)(*expr)) { |
| return false; |
| } |
| ++j; |
| } |
| return true; |
| } |
| // TODO: STORAGE_SIZE |
| } |
| return false; |
| } |
| |
| template <bool INVARIANT> |
| bool IsConstantExprHelper<INVARIANT>::IsConstantExprShape( |
| const Shape &shape) const { |
| for (const auto &extent : shape) { |
| if (!(*this)(extent)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <typename A> bool IsConstantExpr(const A &x) { |
| return IsConstantExprHelper<false>{}(x); |
| } |
| template bool IsConstantExpr(const Expr<SomeType> &); |
| template bool IsConstantExpr(const Expr<SomeInteger> &); |
| template bool IsConstantExpr(const Expr<SubscriptInteger> &); |
| template bool IsConstantExpr(const StructureConstructor &); |
| |
| // IsScopeInvariantExpr() |
| template <typename A> bool IsScopeInvariantExpr(const A &x) { |
| return IsConstantExprHelper<true>{}(x); |
| } |
| template bool IsScopeInvariantExpr(const Expr<SomeType> &); |
| template bool IsScopeInvariantExpr(const Expr<SomeInteger> &); |
| template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &); |
| |
| // IsActuallyConstant() |
| struct IsActuallyConstantHelper { |
| template <typename A> bool operator()(const A &) { return false; } |
| template <typename T> bool operator()(const Constant<T> &) { return true; } |
| template <typename T> bool operator()(const Parentheses<T> &x) { |
| return (*this)(x.left()); |
| } |
| template <typename T> bool operator()(const Expr<T> &x) { |
| return common::visit([=](const auto &y) { return (*this)(y); }, x.u); |
| } |
| bool operator()(const Expr<SomeType> &x) { |
| return common::visit([this](const auto &y) { return (*this)(y); }, x.u); |
| } |
| bool operator()(const StructureConstructor &x) { |
| for (const auto &pair : x) { |
| const Expr<SomeType> &y{pair.second.value()}; |
| const auto sym{pair.first}; |
| const bool compIsConstant{(*this)(y)}; |
| // If an allocatable component is initialized by a constant, |
| // the structure constructor is not a constant. |
| if ((!compIsConstant && !IsNullPointerOrAllocatable(&y)) || |
| (compIsConstant && IsAllocatable(sym))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| template <typename A> bool operator()(const A *x) { return x && (*this)(*x); } |
| template <typename A> bool operator()(const std::optional<A> &x) { |
| return x && (*this)(*x); |
| } |
| }; |
| |
| template <typename A> bool IsActuallyConstant(const A &x) { |
| return IsActuallyConstantHelper{}(x); |
| } |
| |
| template bool IsActuallyConstant(const Expr<SomeType> &); |
| template bool IsActuallyConstant(const Expr<SomeInteger> &); |
| template bool IsActuallyConstant(const Expr<SubscriptInteger> &); |
| template bool IsActuallyConstant(const std::optional<Expr<SubscriptInteger>> &); |
| |
| // Object pointer initialization checking predicate IsInitialDataTarget(). |
| // This code determines whether an expression is allowable as the static |
| // data address used to initialize a pointer with "=> x". See C765. |
| class IsInitialDataTargetHelper |
| : public AllTraverse<IsInitialDataTargetHelper, true> { |
| public: |
| using Base = AllTraverse<IsInitialDataTargetHelper, true>; |
| using Base::operator(); |
| explicit IsInitialDataTargetHelper(parser::ContextualMessages *m) |
| : Base{*this}, messages_{m} {} |
| |
| bool emittedMessage() const { return emittedMessage_; } |
| |
| bool operator()(const BOZLiteralConstant &) const { return false; } |
| bool operator()(const NullPointer &) const { return true; } |
| template <typename T> bool operator()(const Constant<T> &) const { |
| return false; |
| } |
| bool operator()(const semantics::Symbol &symbol) { |
| // This function checks only base symbols, not components. |
| const Symbol &ultimate{symbol.GetUltimate()}; |
| if (const auto *assoc{ |
| ultimate.detailsIf<semantics::AssocEntityDetails>()}) { |
| if (const auto &expr{assoc->expr()}) { |
| if (IsVariable(*expr)) { |
| return (*this)(*expr); |
| } else if (messages_) { |
| messages_->Say( |
| "An initial data target may not be an associated expression ('%s')"_err_en_US, |
| ultimate.name()); |
| emittedMessage_ = true; |
| } |
| } |
| return false; |
| } else if (!CheckVarOrComponent(ultimate)) { |
| return false; |
| } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) { |
| if (messages_) { |
| messages_->Say( |
| "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US, |
| ultimate.name()); |
| emittedMessage_ = true; |
| } |
| return false; |
| } else if (!IsSaved(ultimate)) { |
| if (messages_) { |
| messages_->Say( |
| "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US, |
| ultimate.name()); |
| emittedMessage_ = true; |
| } |
| return false; |
| } else { |
| return true; |
| } |
| } |
| bool operator()(const StaticDataObject &) const { return false; } |
| bool operator()(const TypeParamInquiry &) const { return false; } |
| bool operator()(const Triplet &x) const { |
| return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && |
| IsConstantExpr(x.stride()); |
| } |
| bool operator()(const Subscript &x) const { |
| return common::visit(common::visitors{ |
| [&](const Triplet &t) { return (*this)(t); }, |
| [&](const auto &y) { |
| return y.value().Rank() == 0 && |
| IsConstantExpr(y.value()); |
| }, |
| }, |
| x.u); |
| } |
| bool operator()(const CoarrayRef &) const { return false; } |
| bool operator()(const Component &x) { |
| return CheckVarOrComponent(x.GetLastSymbol()) && (*this)(x.base()); |
| } |
| bool operator()(const Substring &x) const { |
| return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && |
| (*this)(x.parent()); |
| } |
| bool operator()(const DescriptorInquiry &) const { return false; } |
| template <typename T> bool operator()(const ArrayConstructor<T> &) const { |
| return false; |
| } |
| bool operator()(const StructureConstructor &) const { return false; } |
| template <typename D, typename R, typename... O> |
| bool operator()(const Operation<D, R, O...> &) const { |
| return false; |
| } |
| template <typename T> bool operator()(const Parentheses<T> &x) const { |
| return (*this)(x.left()); |
| } |
| bool operator()(const ProcedureRef &x) const { |
| if (const SpecificIntrinsic * intrinsic{x.proc().GetSpecificIntrinsic()}) { |
| return intrinsic->characteristics.value().attrs.test( |
| characteristics::Procedure::Attr::NullPointer) || |
| intrinsic->characteristics.value().attrs.test( |
| characteristics::Procedure::Attr::NullAllocatable); |
| } |
| return false; |
| } |
| bool operator()(const Relational<SomeType> &) const { return false; } |
| |
| private: |
| bool CheckVarOrComponent(const semantics::Symbol &symbol) { |
| const Symbol &ultimate{symbol.GetUltimate()}; |
| const char *unacceptable{nullptr}; |
| if (ultimate.Corank() > 0) { |
| unacceptable = "a coarray"; |
| } else if (IsAllocatable(ultimate)) { |
| unacceptable = "an ALLOCATABLE"; |
| } else if (IsPointer(ultimate)) { |
| unacceptable = "a POINTER"; |
| } else { |
| return true; |
| } |
| if (messages_) { |
| messages_->Say( |
| "An initial data target may not be a reference to %s '%s'"_err_en_US, |
| unacceptable, ultimate.name()); |
| emittedMessage_ = true; |
| } |
| return false; |
| } |
| |
| parser::ContextualMessages *messages_; |
| bool emittedMessage_{false}; |
| }; |
| |
| bool IsInitialDataTarget( |
| const Expr<SomeType> &x, parser::ContextualMessages *messages) { |
| IsInitialDataTargetHelper helper{messages}; |
| bool result{helper(x)}; |
| if (!result && messages && !helper.emittedMessage()) { |
| messages->Say( |
| "An initial data target must be a designator with constant subscripts"_err_en_US); |
| } |
| return result; |
| } |
| |
| bool IsInitialProcedureTarget(const semantics::Symbol &symbol) { |
| const auto &ultimate{symbol.GetUltimate()}; |
| return common::visit( |
| common::visitors{ |
| [&](const semantics::SubprogramDetails &subp) { |
| return !subp.isDummy() && !subp.stmtFunction() && |
| symbol.owner().kind() != semantics::Scope::Kind::MainProgram && |
| symbol.owner().kind() != semantics::Scope::Kind::Subprogram; |
| }, |
| [](const semantics::SubprogramNameDetails &x) { |
| return x.kind() != semantics::SubprogramKind::Internal; |
| }, |
| [&](const semantics::ProcEntityDetails &proc) { |
| return !semantics::IsPointer(ultimate) && !proc.isDummy(); |
| }, |
| [](const auto &) { return false; }, |
| }, |
| ultimate.details()); |
| } |
| |
| bool IsInitialProcedureTarget(const ProcedureDesignator &proc) { |
| if (const auto *intrin{proc.GetSpecificIntrinsic()}) { |
| return !intrin->isRestrictedSpecific; |
| } else if (proc.GetComponent()) { |
| return false; |
| } else { |
| return IsInitialProcedureTarget(DEREF(proc.GetSymbol())); |
| } |
| } |
| |
| bool IsInitialProcedureTarget(const Expr<SomeType> &expr) { |
| if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) { |
| return IsInitialProcedureTarget(*proc); |
| } else { |
| return IsNullProcedurePointer(&expr); |
| } |
| } |
| |
| class SuspiciousRealLiteralFinder |
| : public AnyTraverse<SuspiciousRealLiteralFinder> { |
| public: |
| using Base = AnyTraverse<SuspiciousRealLiteralFinder>; |
| SuspiciousRealLiteralFinder(int kind, FoldingContext &c) |
| : Base{*this}, kind_{kind}, context_{c} {} |
| using Base::operator(); |
| template <int KIND> |
| bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const { |
| if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) { |
| context_.Warn(common::UsageWarning::RealConstantWidening, |
| "Default real literal in REAL(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US, |
| kind_, x.AsFortran()); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| template <int KIND> |
| bool operator()(const Constant<Type<TypeCategory::Complex, KIND>> &x) const { |
| if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) { |
| context_.Warn(common::UsageWarning::RealConstantWidening, |
| "Default real literal in COMPLEX(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US, |
| kind_, x.AsFortran()); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| template <TypeCategory TOCAT, int TOKIND, TypeCategory FROMCAT> |
| bool operator()(const Convert<Type<TOCAT, TOKIND>, FROMCAT> &x) const { |
| if constexpr ((TOCAT == TypeCategory::Real || |
| TOCAT == TypeCategory::Complex) && |
| (FROMCAT == TypeCategory::Real || FROMCAT == TypeCategory::Complex)) { |
| auto fromType{x.left().GetType()}; |
| if (!fromType || fromType->kind() < TOKIND) { |
| return false; |
| } |
| } |
| return (*this)(x.left()); |
| } |
| |
| private: |
| int kind_; |
| FoldingContext &context_; |
| }; |
| |
| void CheckRealWidening(const Expr<SomeType> &expr, const DynamicType &toType, |
| FoldingContext &context) { |
| if (toType.category() == TypeCategory::Real || |
| toType.category() == TypeCategory::Complex) { |
| if (auto fromType{expr.GetType()}) { |
| if ((fromType->category() == TypeCategory::Real || |
| fromType->category() == TypeCategory::Complex) && |
| toType.kind() > fromType->kind()) { |
| SuspiciousRealLiteralFinder{toType.kind(), context}(expr); |
| } |
| } |
| } |
| } |
| |
| void CheckRealWidening(const Expr<SomeType> &expr, |
| const std::optional<DynamicType> &toType, FoldingContext &context) { |
| if (toType) { |
| CheckRealWidening(expr, *toType, context); |
| } |
| } |
| |
| class InexactLiteralConversionFlagClearer |
| : public AnyTraverse<InexactLiteralConversionFlagClearer> { |
| public: |
| using Base = AnyTraverse<InexactLiteralConversionFlagClearer>; |
| InexactLiteralConversionFlagClearer() : Base(*this) {} |
| using Base::operator(); |
| template <int KIND> |
| bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const { |
| auto &mut{const_cast<Type<TypeCategory::Real, KIND> &>(x.result())}; |
| mut.set_isFromInexactLiteralConversion(false); |
| return false; |
| } |
| }; |
| |
| // Converts, folds, and then checks type, rank, and shape of an |
| // initialization expression for a named constant, a non-pointer |
| // variable static initialization, a component default initializer, |
| // a type parameter default value, or instantiated type parameter value. |
| std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol, |
| Expr<SomeType> &&x, FoldingContext &context, |
| const semantics::Scope *instantiation) { |
| CHECK(!IsPointer(symbol)); |
| if (auto symTS{ |
| characteristics::TypeAndShape::Characterize(symbol, context)}) { |
| auto xType{x.GetType()}; |
| CheckRealWidening(x, symTS->type(), context); |
| auto converted{ConvertToType(symTS->type(), Expr<SomeType>{x})}; |
| if (!converted && |
| symbol.owner().context().IsEnabled( |
| common::LanguageFeature::LogicalIntegerAssignment)) { |
| converted = DataConstantConversionExtension(context, symTS->type(), x); |
| if (converted) { |
| context.Warn(common::LanguageFeature::LogicalIntegerAssignment, |
| "nonstandard usage: initialization of %s with %s"_port_en_US, |
| symTS->type().AsFortran(), x.GetType().value().AsFortran()); |
| } |
| } |
| if (converted) { |
| auto folded{Fold(context, std::move(*converted))}; |
| if (IsActuallyConstant(folded)) { |
| InexactLiteralConversionFlagClearer{}(folded); |
| int symRank{symTS->Rank()}; |
| if (IsImpliedShape(symbol)) { |
| if (folded.Rank() == symRank) { |
| return ArrayConstantBoundChanger{ |
| std::move(*AsConstantExtents( |
| context, GetRawLowerBounds(context, NamedEntity{symbol})))} |
| .ChangeLbounds(std::move(folded)); |
| } else { |
| context.messages().Say( |
| "Implied-shape parameter '%s' has rank %d but its initializer has rank %d"_err_en_US, |
| symbol.name(), symRank, folded.Rank()); |
| } |
| } else if (auto extents{AsConstantExtents(context, symTS->shape())}; |
| extents && !HasNegativeExtent(*extents)) { |
| if (folded.Rank() == 0 && symRank == 0) { |
| // symbol and constant are both scalars |
| return {std::move(folded)}; |
| } else if (folded.Rank() == 0 && symRank > 0) { |
| // expand the scalar constant to an array |
| return ScalarConstantExpander{std::move(*extents), |
| AsConstantExtents( |
| context, GetRawLowerBounds(context, NamedEntity{symbol}))} |
| .Expand(std::move(folded)); |
| } else if (auto resultShape{GetShape(context, folded)}) { |
| CHECK(symTS->shape()); // Assumed-ranks cannot be initialized. |
| if (CheckConformance(context.messages(), *symTS->shape(), |
| *resultShape, CheckConformanceFlags::None, |
| "initialized object", "initialization expression") |
| .value_or(false /*fail if not known now to conform*/)) { |
| // make a constant array with adjusted lower bounds |
| return ArrayConstantBoundChanger{ |
| std::move(*AsConstantExtents(context, |
| GetRawLowerBounds(context, NamedEntity{symbol})))} |
| .ChangeLbounds(std::move(folded)); |
| } |
| } |
| } else if (IsNamedConstant(symbol)) { |
| if (IsExplicitShape(symbol)) { |
| context.messages().Say( |
| "Named constant '%s' array must have constant shape"_err_en_US, |
| symbol.name()); |
| } else { |
| // Declaration checking handles other cases |
| } |
| } else { |
| context.messages().Say( |
| "Shape of initialized object '%s' must be constant"_err_en_US, |
| symbol.name()); |
| } |
| } else if (IsErrorExpr(folded)) { |
| } else if (IsLenTypeParameter(symbol)) { |
| return {std::move(folded)}; |
| } else if (IsKindTypeParameter(symbol)) { |
| if (instantiation) { |
| context.messages().Say( |
| "Value of kind type parameter '%s' (%s) must be a scalar INTEGER constant"_err_en_US, |
| symbol.name(), folded.AsFortran()); |
| } else { |
| return {std::move(folded)}; |
| } |
| } else if (IsNamedConstant(symbol)) { |
| if (symbol.name() == "numeric_storage_size" && |
| symbol.owner().IsModule() && |
| DEREF(symbol.owner().symbol()).name() == "iso_fortran_env") { |
| // Very special case: numeric_storage_size is not folded until |
| // it read from the iso_fortran_env module file, as its value |
| // depends on compilation options. |
| return {std::move(folded)}; |
| } |
| context.messages().Say( |
| "Value of named constant '%s' (%s) cannot be computed as a constant value"_err_en_US, |
| symbol.name(), folded.AsFortran()); |
| } else { |
| context.messages().Say( |
| "Initialization expression for '%s' (%s) cannot be computed as a constant value"_err_en_US, |
| symbol.name(), x.AsFortran()); |
| } |
| } else if (xType) { |
| context.messages().Say( |
| "Initialization expression cannot be converted to declared type of '%s' from %s"_err_en_US, |
| symbol.name(), xType->AsFortran()); |
| } else { |
| context.messages().Say( |
| "Initialization expression cannot be converted to declared type of '%s'"_err_en_US, |
| symbol.name()); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // Specification expression validation (10.1.11(2), C1010) |
| class CheckSpecificationExprHelper |
| : public AnyTraverse<CheckSpecificationExprHelper, |
| std::optional<std::string>> { |
| public: |
| using Result = std::optional<std::string>; |
| using Base = AnyTraverse<CheckSpecificationExprHelper, Result>; |
| explicit CheckSpecificationExprHelper(const semantics::Scope &s, |
| FoldingContext &context, bool forElementalFunctionResult) |
| : Base{*this}, scope_{s}, context_{context}, |
| forElementalFunctionResult_{forElementalFunctionResult} {} |
| using Base::operator(); |
| |
| Result operator()(const CoarrayRef &) const { return "coindexed reference"; } |
| |
| Result operator()(const semantics::Symbol &symbol) const { |
| const auto &ultimate{symbol.GetUltimate()}; |
| const auto *object{ultimate.detailsIf<semantics::ObjectEntityDetails>()}; |
| bool isInitialized{semantics::IsSaved(ultimate) && |
| !IsAllocatable(ultimate) && object && |
| (ultimate.test(Symbol::Flag::InDataStmt) || |
| object->init().has_value())}; |
| bool hasHostAssociation{ |
| &symbol.owner() != &scope_ || &ultimate.owner() != &scope_}; |
| if (const auto *assoc{ |
| ultimate.detailsIf<semantics::AssocEntityDetails>()}) { |
| return (*this)(assoc->expr()); |
| } else if (semantics::IsNamedConstant(ultimate) || |
| ultimate.owner().IsModule() || ultimate.owner().IsSubmodule()) { |
| return std::nullopt; |
| } else if (scope_.IsDerivedType() && |
| IsVariableName(ultimate)) { // C750, C754 |
| return "derived type component or type parameter value not allowed to " |
| "reference variable '"s + |
| ultimate.name().ToString() + "'"; |
| } else if (IsDummy(ultimate)) { |
| if (!inInquiry_ && forElementalFunctionResult_) { |
| return "dependence on value of dummy argument '"s + |
| ultimate.name().ToString() + "'"; |
| } else if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) { |
| return "reference to OPTIONAL dummy argument '"s + |
| ultimate.name().ToString() + "'"; |
| } else if (!inInquiry_ && !hasHostAssociation && |
| ultimate.attrs().test(semantics::Attr::INTENT_OUT)) { |
| return "reference to INTENT(OUT) dummy argument '"s + |
| ultimate.name().ToString() + "'"; |
| } else if (!ultimate.has<semantics::ObjectEntityDetails>()) { |
| return "dummy procedure argument"; |
| } else { |
| // Sketchy case: some compilers allow an INTENT(OUT) dummy argument |
| // to be used in a specification expression if it is host-associated. |
| // The arguments raised in support this usage, however, depend on |
| // a reading of the standard that would also accept an OPTIONAL |
| // host-associated dummy argument, and that doesn't seem like a |
| // good idea. |
| if (!inInquiry_ && hasHostAssociation && |
| ultimate.attrs().test(semantics::Attr::INTENT_OUT)) { |
| context_.Warn(common::UsageWarning::HostAssociatedIntentOutInSpecExpr, |
| "specification expression refers to host-associated INTENT(OUT) dummy argument '%s'"_port_en_US, |
| ultimate.name()); |
| } |
| return std::nullopt; |
| } |
| } else if (hasHostAssociation) { |
| return std::nullopt; // host association is in play |
| } else if (isInitialized && |
| context_.languageFeatures().IsEnabled( |
| common::LanguageFeature::SavedLocalInSpecExpr)) { |
| context_.Warn(common::LanguageFeature::SavedLocalInSpecExpr, |
| "specification expression refers to local object '%s' (initialized and saved)"_port_en_US, |
| ultimate.name()); |
| return std::nullopt; |
| } else if (const auto *object{ |
| ultimate.detailsIf<semantics::ObjectEntityDetails>()}) { |
| if (object->commonBlock()) { |
| return std::nullopt; |
| } |
| } |
| if (inInquiry_) { |
| return std::nullopt; |
| } else { |
| return "reference to local entity '"s + ultimate.name().ToString() + "'"; |
| } |
| } |
| |
| Result operator()(const Component &x) const { |
| // Don't look at the component symbol. |
| return (*this)(x.base()); |
| } |
| Result operator()(const ArrayRef &x) const { |
| if (auto result{(*this)(x.base())}) { |
| return result; |
| } |
| // The subscripts don't get special protection for being in a |
| // specification inquiry context; |
| auto restorer{common::ScopedSet(inInquiry_, false)}; |
| return (*this)(x.subscript()); |
| } |
| Result operator()(const Substring &x) const { |
| if (auto result{(*this)(x.parent())}) { |
| return result; |
| } |
| // The bounds don't get special protection for being in a |
| // specification inquiry context; |
| auto restorer{common::ScopedSet(inInquiry_, false)}; |
| if (auto result{(*this)(x.lower())}) { |
| return result; |
| } |
| return (*this)(x.upper()); |
| } |
| Result operator()(const DescriptorInquiry &x) const { |
| // Many uses of SIZE(), LBOUND(), &c. that are valid in specification |
| // expressions will have been converted to expressions over descriptor |
| // inquiries by Fold(). |
| // Catch REAL, ALLOCATABLE :: X(:); REAL :: Y(SIZE(X)) |
| if (IsPermissibleInquiry( |
| x.base().GetFirstSymbol(), x.base().GetLastSymbol(), x.field())) { |
| auto restorer{common::ScopedSet(inInquiry_, true)}; |
| return (*this)(x.base()); |
| } else if (IsConstantExpr(x)) { |
| return std::nullopt; |
| } else { |
| return "non-constant descriptor inquiry not allowed for local object"; |
| } |
| } |
| |
| Result operator()(const TypeParamInquiry &inq) const { |
| if (scope_.IsDerivedType()) { |
| if (!IsConstantExpr(inq) && |
| inq.base() /* X%T, not local T */) { // C750, C754 |
| return "non-constant reference to a type parameter inquiry not allowed " |
| "for derived type components or type parameter values"; |
| } |
| } else if (inq.base() && |
| IsInquiryAlwaysPermissible(inq.base()->GetFirstSymbol())) { |
| auto restorer{common::ScopedSet(inInquiry_, true)}; |
| return (*this)(inq.base()); |
| } else if (!IsConstantExpr(inq)) { |
| return "non-constant type parameter inquiry not allowed for local object"; |
| } |
| return std::nullopt; |
| } |
| |
| Result operator()(const ProcedureRef &x) const { |
| if (const auto *symbol{x.proc().GetSymbol()}) { |
| const Symbol &ultimate{symbol->GetUltimate()}; |
| if (!semantics::IsPureProcedure(ultimate)) { |
| return "reference to impure function '"s + ultimate.name().ToString() + |
| "'"; |
| } |
| if (semantics::IsStmtFunction(ultimate)) { |
| return "reference to statement function '"s + |
| ultimate.name().ToString() + "'"; |
| } |
| if (scope_.IsDerivedType()) { // C750, C754 |
| return "reference to function '"s + ultimate.name().ToString() + |
| "' not allowed for derived type components or type parameter" |
| " values"; |
| } |
| if (auto procChars{characteristics::Procedure::Characterize( |
| x.proc(), context_, /*emitError=*/true)}) { |
| const auto iter{std::find_if(procChars->dummyArguments.begin(), |
| procChars->dummyArguments.end(), |
| [](const characteristics::DummyArgument &dummy) { |
| return std::holds_alternative<characteristics::DummyProcedure>( |
| dummy.u); |
| })}; |
| if (iter != procChars->dummyArguments.end() && |
| ultimate.name().ToString() != "__builtin_c_funloc") { |
| return "reference to function '"s + ultimate.name().ToString() + |
| "' with dummy procedure argument '" + iter->name + '\''; |
| } |
| } |
| // References to internal functions are caught in expression semantics. |
| // TODO: other checks for standard module procedures |
| auto restorer{common::ScopedSet(inInquiry_, false)}; |
| return (*this)(x.arguments()); |
| } else { // intrinsic |
| const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())}; |
| bool inInquiry{context_.intrinsics().GetIntrinsicClass(intrin.name) == |
| IntrinsicClass::inquiryFunction}; |
| if (scope_.IsDerivedType()) { // C750, C754 |
| if ((context_.intrinsics().IsIntrinsic(intrin.name) && |
| badIntrinsicsForComponents_.find(intrin.name) != |
| badIntrinsicsForComponents_.end())) { |
| return "reference to intrinsic '"s + intrin.name + |
| "' not allowed for derived type components or type parameter" |
| " values"; |
| } |
| if (inInquiry && !IsConstantExpr(x)) { |
| return "non-constant reference to inquiry intrinsic '"s + |
| intrin.name + |
| "' not allowed for derived type components or type" |
| " parameter values"; |
| } |
| } |
| // Type-determined inquiries (DIGITS, HUGE, &c.) will have already been |
| // folded and won't arrive here. Inquiries that are represented with |
| // DescriptorInquiry operations (LBOUND) are checked elsewhere. If a |
| // call that makes it to here satisfies the requirements of a constant |
| // expression (as Fortran defines it), it's fine. |
| if (IsConstantExpr(x)) { |
| return std::nullopt; |
| } |
| if (intrin.name == "present") { |
| return std::nullopt; // always ok |
| } |
| const auto &proc{intrin.characteristics.value()}; |
| std::size_t j{0}; |
| for (const auto &arg : x.arguments()) { |
| bool checkArg{true}; |
| if (const auto *dataDummy{j < proc.dummyArguments.size() |
| ? std::get_if<characteristics::DummyDataObject>( |
| &proc.dummyArguments[j].u) |
| : nullptr}) { |
| if (dataDummy->attrs.test(characteristics::DummyDataObject::Attr:: |
| OnlyIntrinsicInquiry)) { |
| checkArg = false; // value unused, e.g. IEEE_SUPPORT_FLAG(,,,. X) |
| } |
| } |
| if (arg && checkArg) { |
| // Catch CHARACTER(:), ALLOCATABLE :: X; CHARACTER(LEN(X)) :: Y |
| if (inInquiry) { |
| if (auto dataRef{ExtractDataRef(*arg, true, true)}) { |
| if (intrin.name == "allocated" || intrin.name == "associated" || |
| intrin.name == "is_contiguous") { // ok |
| } else if (intrin.name == "len" && |
| IsPermissibleInquiry(dataRef->GetFirstSymbol(), |
| dataRef->GetLastSymbol(), |
| DescriptorInquiry::Field::Len)) { // ok |
| } else if (intrin.name == "lbound" && |
| IsPermissibleInquiry(dataRef->GetFirstSymbol(), |
| dataRef->GetLastSymbol(), |
| DescriptorInquiry::Field::LowerBound)) { // ok |
| } else if ((intrin.name == "shape" || intrin.name == "size" || |
| intrin.name == "sizeof" || |
| intrin.name == "storage_size" || |
| intrin.name == "ubound") && |
| IsPermissibleInquiry(dataRef->GetFirstSymbol(), |
| dataRef->GetLastSymbol(), |
| DescriptorInquiry::Field::Extent)) { // ok |
| } else { |
| return "non-constant inquiry function '"s + intrin.name + |
| "' not allowed for local object"; |
| } |
| } |
| } |
| auto restorer{common::ScopedSet(inInquiry_, inInquiry)}; |
| if (auto err{(*this)(*arg)}) { |
| return err; |
| } |
| } |
| ++j; |
| } |
| return std::nullopt; |
| } |
| } |
| |
| private: |
| const semantics::Scope &scope_; |
| FoldingContext &context_; |
| // Contextual information: this flag is true when in an argument to |
| // an inquiry intrinsic like SIZE(). |
| mutable bool inInquiry_{false}; |
| bool forElementalFunctionResult_{false}; // F'2023 C15121 |
| const std::set<std::string> badIntrinsicsForComponents_{ |
| "allocated", "associated", "extends_type_of", "present", "same_type_as"}; |
| |
| bool IsInquiryAlwaysPermissible(const semantics::Symbol &) const; |
| bool IsPermissibleInquiry(const semantics::Symbol &firstSymbol, |
| const semantics::Symbol &lastSymbol, |
| DescriptorInquiry::Field field) const; |
| }; |
| |
| bool CheckSpecificationExprHelper::IsInquiryAlwaysPermissible( |
| const semantics::Symbol &symbol) const { |
| if (&symbol.owner() != &scope_ || symbol.has<semantics::UseDetails>() || |
| symbol.owner().kind() == semantics::Scope::Kind::Module || |
| semantics::FindCommonBlockContaining(symbol) || |
| symbol.has<semantics::HostAssocDetails>()) { |
| return true; // it's nonlocal |
| } else if (semantics::IsDummy(symbol) && !forElementalFunctionResult_) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool CheckSpecificationExprHelper::IsPermissibleInquiry( |
| const semantics::Symbol &firstSymbol, const semantics::Symbol &lastSymbol, |
| DescriptorInquiry::Field field) const { |
| if (IsInquiryAlwaysPermissible(firstSymbol)) { |
| return true; |
| } |
| // Inquiries on local objects may not access a deferred bound or length. |
| // (This code used to be a switch, but it proved impossible to write it |
| // thus without running afoul of bogus warnings from different C++ |
| // compilers.) |
| if (field == DescriptorInquiry::Field::Rank) { |
| return true; // always known |
| } |
| const auto *object{lastSymbol.detailsIf<semantics::ObjectEntityDetails>()}; |
| if (field == DescriptorInquiry::Field::LowerBound || |
| field == DescriptorInquiry::Field::Extent || |
| field == DescriptorInquiry::Field::Stride) { |
| return object && !object->shape().CanBeDeferredShape(); |
| } |
| if (field == DescriptorInquiry::Field::Len) { |
| return object && object->type() && |
| object->type()->category() == semantics::DeclTypeSpec::Character && |
| !object->type()->characterTypeSpec().length().isDeferred(); |
| } |
| return false; |
| } |
| |
| template <typename A> |
| void CheckSpecificationExpr(const A &x, const semantics::Scope &scope, |
| FoldingContext &context, bool forElementalFunctionResult) { |
| CheckSpecificationExprHelper errors{ |
| scope, context, forElementalFunctionResult}; |
| if (auto why{errors(x)}) { |
| context.messages().Say("Invalid specification expression%s: %s"_err_en_US, |
| forElementalFunctionResult ? " for elemental function result" : "", |
| *why); |
| } |
| } |
| |
| template void CheckSpecificationExpr(const Expr<SomeType> &, |
| const semantics::Scope &, FoldingContext &, |
| bool forElementalFunctionResult); |
| template void CheckSpecificationExpr(const Expr<SomeInteger> &, |
| const semantics::Scope &, FoldingContext &, |
| bool forElementalFunctionResult); |
| template void CheckSpecificationExpr(const Expr<SubscriptInteger> &, |
| const semantics::Scope &, FoldingContext &, |
| bool forElementalFunctionResult); |
| template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &, |
| const semantics::Scope &, FoldingContext &, |
| bool forElementalFunctionResult); |
| template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &, |
| const semantics::Scope &, FoldingContext &, |
| bool forElementalFunctionResult); |
| template void CheckSpecificationExpr( |
| const std::optional<Expr<SubscriptInteger>> &, const semantics::Scope &, |
| FoldingContext &, bool forElementalFunctionResult); |
| |
| // IsContiguous() -- 9.5.4 |
| class IsContiguousHelper |
| : public AnyTraverse<IsContiguousHelper, std::optional<bool>> { |
| public: |
| using Result = std::optional<bool>; // tri-state |
| using Base = AnyTraverse<IsContiguousHelper, Result>; |
| explicit IsContiguousHelper(FoldingContext &c, |
| bool namedConstantSectionsAreContiguous, |
| bool firstDimensionStride1 = false) |
| : Base{*this}, context_{c}, |
| namedConstantSectionsAreContiguous_{namedConstantSectionsAreContiguous}, |
| firstDimensionStride1_{firstDimensionStride1} {} |
| using Base::operator(); |
| |
| template <typename T> Result operator()(const Constant<T> &) const { |
| return true; |
| } |
| Result operator()(const StaticDataObject &) const { return true; } |
| Result operator()(const semantics::Symbol &symbol) const { |
| const auto &ultimate{symbol.GetUltimate()}; |
| if (ultimate.attrs().test(semantics::Attr::CONTIGUOUS)) { |
| return true; |
| } else if (!IsVariable(symbol)) { |
| return true; |
| } else if (ultimate.Rank() == 0) { |
| // Extension: accept scalars as a degenerate case of |
| // simple contiguity to allow their use in contexts like |
| // data targets in pointer assignments with remapping. |
| return true; |
| } else if (const auto *details{ |
| ultimate.detailsIf<semantics::AssocEntityDetails>()}) { |
| // RANK(*) associating entity is contiguous. |
| if (details->IsAssumedSize()) { |
| return true; |
| } else if (!IsVariable(details->expr()) && |
| (namedConstantSectionsAreContiguous_ || |
| !ExtractDataRef(details->expr(), true, true))) { |
| // Selector is associated to an expression value. |
| return true; |
| } else { |
| return Base::operator()(ultimate); // use expr |
| } |
| } else if (semantics::IsPointer(ultimate) || IsAssumedShape(ultimate) || |
| IsAssumedRank(ultimate)) { |
| return std::nullopt; |
| } else if (ultimate.has<semantics::ObjectEntityDetails>()) { |
| return true; |
| } else { |
| return Base::operator()(ultimate); |
| } |
| } |
| |
| Result operator()(const ArrayRef &x) const { |
| if (x.Rank() == 0) { |
| return true; // scalars considered contiguous |
| } |
| int subscriptRank{0}; |
| auto baseLbounds{GetLBOUNDs(context_, x.base())}; |
| auto baseUbounds{GetUBOUNDs(context_, x.base())}; |
| auto subscripts{CheckSubscripts( |
| x.subscript(), subscriptRank, &baseLbounds, &baseUbounds)}; |
| if (!subscripts.value_or(false)) { |
| return subscripts; // subscripts not known to be contiguous |
| } else if (subscriptRank > 0) { |
| // a(1)%b(:,:) is contiguous if and only if a(1)%b is contiguous. |
| return (*this)(x.base()); |
| } else { |
| // a(:)%b(1,1) is (probably) not contiguous. |
| return std::nullopt; |
| } |
| } |
| Result operator()(const CoarrayRef &x) const { return (*this)(x.base()); } |
| Result operator()(const Component &x) const { |
| if (x.base().Rank() == 0) { |
| return (*this)(x.GetLastSymbol()); |
| } else { |
| const DataRef &base{x.base()}; |
| if (Result baseIsContiguous{(*this)(base)}) { |
| if (!*baseIsContiguous) { |
| return false; |
| } else { |
| bool sizeKnown{false}; |
| if (auto constShape{GetConstantExtents(context_, x)}) { |
| sizeKnown = true; |
| if (GetSize(*constShape) <= 1) { |
| return true; // empty or singleton |
| } |
| } |
| const Symbol &last{base.GetLastSymbol()}; |
| if (auto type{DynamicType::From(last)}) { |
| CHECK(type->category() == TypeCategory::Derived); |
| if (!type->IsPolymorphic()) { |
| const auto &derived{type->GetDerivedTypeSpec()}; |
| if (const auto *scope{derived.scope()}) { |
| auto iter{scope->begin()}; |
| if (++iter == scope->end()) { |
| return true; // type has but one component |
| } else if (sizeKnown) { |
| return false; // multiple components & array size is known > 1 |
| } |
| } |
| } |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| } |
| Result operator()(const ComplexPart &x) const { |
| // TODO: should be true when base is empty array or singleton, too |
| return x.complex().Rank() == 0; |
| } |
| Result operator()(const Substring &x) const { |
| if (x.Rank() == 0) { |
| return true; // scalar substring always contiguous |
| } |
| // Substrings with rank must have DataRefs as their parents |
| const DataRef &parentDataRef{DEREF(x.GetParentIf<DataRef>())}; |
| std::optional<std::int64_t> len; |
| if (auto lenExpr{parentDataRef.LEN()}) { |
| len = ToInt64(Fold(context_, std::move(*lenExpr))); |
| if (len) { |
| if (*len <= 0) { |
| return true; // empty substrings |
| } else if (*len == 1) { |
| // Substrings can't be incomplete; is base array contiguous? |
| return (*this)(parentDataRef); |
| } |
| } |
| } |
| std::optional<std::int64_t> upper; |
| bool upperIsLen{false}; |
| if (auto upperExpr{x.upper()}) { |
| upper = ToInt64(Fold(context_, common::Clone(*upperExpr))); |
| if (upper) { |
| if (*upper < 1) { |
| return true; // substring(n:0) empty |
| } |
| upperIsLen = len && *upper >= *len; |
| } else if (const auto *inquiry{ |
| UnwrapConvertedExpr<DescriptorInquiry>(*upperExpr)}; |
| inquiry && inquiry->field() == DescriptorInquiry::Field::Len) { |
| upperIsLen = |
| &parentDataRef.GetLastSymbol() == &inquiry->base().GetLastSymbol(); |
| } |
| } else { |
| upperIsLen = true; // substring(n:) |
| } |
| if (auto lower{ToInt64(Fold(context_, x.lower()))}) { |
| if (*lower == 1 && upperIsLen) { |
| // known complete substring; is base contiguous? |
| return (*this)(parentDataRef); |
| } else if (upper) { |
| if (*upper < *lower) { |
| return true; // empty substring(3:2) |
| } else if (*lower > 1) { |
| return false; // known incomplete substring |
| } else if (len && *upper < *len) { |
| return false; // known incomplete substring |
| } |
| } |
| } |
| return std::nullopt; // contiguity not known |
| } |
| |
| Result operator()(const ProcedureRef &x) const { |
| if (auto chars{characteristics::Procedure::Characterize( |
| x.proc(), context_, /*emitError=*/true)}) { |
| if (chars->functionResult) { |
| const auto &result{*chars->functionResult}; |
| if (!result.IsProcedurePointer()) { |
| if (result.attrs.test( |
| characteristics::FunctionResult::Attr::Contiguous)) { |
| return true; |
| } |
| if (!result.attrs.test( |
| characteristics::FunctionResult::Attr::Pointer)) { |
| return true; |
| } |
| if (const auto *type{result.GetTypeAndShape()}; |
| type && type->Rank() == 0) { |
| return true; // pointer to scalar |
| } |
| // Must be non-CONTIGUOUS pointer to array |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| Result operator()(const NullPointer &) const { return true; } |
| |
| private: |
| // Returns "true" for a provably empty or simply contiguous array section; |
| // return "false" for a provably nonempty discontiguous section or for use |
| // of a vector subscript. |
| std::optional<bool> CheckSubscripts(const std::vector<Subscript> &subscript, |
| int &rank, const Shape *baseLbounds = nullptr, |
| const Shape *baseUbounds = nullptr) const { |
| bool anyTriplet{false}; |
| rank = 0; |
| // Detect any provably empty dimension in this array section, which would |
| // render the whole section empty and therefore vacuously contiguous. |
| std::optional<bool> result; |
| bool mayBeEmpty{false}; |
| auto dims{subscript.size()}; |
| std::vector<bool> knownPartialSlice(dims, false); |
| for (auto j{dims}; j-- > 0;) { |
| if (j == 0 && firstDimensionStride1_ && !result.value_or(true)) { |
| result.reset(); // ignore problems on later dimensions |
| } |
| std::optional<ConstantSubscript> dimLbound; |
| std::optional<ConstantSubscript> dimUbound; |
| std::optional<ConstantSubscript> dimExtent; |
| if (baseLbounds && j < baseLbounds->size()) { |
| if (const auto &lb{baseLbounds->at(j)}) { |
| dimLbound = ToInt64(Fold(context_, Expr<SubscriptInteger>{*lb})); |
| } |
| } |
| if (baseUbounds && j < baseUbounds->size()) { |
| if (const auto &ub{baseUbounds->at(j)}) { |
| dimUbound = ToInt64(Fold(context_, Expr<SubscriptInteger>{*ub})); |
| } |
| } |
| if (dimLbound && dimUbound) { |
| if (*dimLbound <= *dimUbound) { |
| dimExtent = *dimUbound - *dimLbound + 1; |
| } else { |
| // This is an empty dimension. |
| result = true; |
| dimExtent = 0; |
| } |
| } |
| if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) { |
| ++rank; |
| const Expr<SubscriptInteger> *lowerBound{triplet->GetLower()}; |
| const Expr<SubscriptInteger> *upperBound{triplet->GetUpper()}; |
| std::optional<ConstantSubscript> lowerVal{lowerBound |
| ? ToInt64(Fold(context_, Expr<SubscriptInteger>{*lowerBound})) |
| : dimLbound}; |
| std::optional<ConstantSubscript> upperVal{upperBound |
| ? ToInt64(Fold(context_, Expr<SubscriptInteger>{*upperBound})) |
| : dimUbound}; |
| if (auto stride{ToInt64(triplet->stride())}) { |
| if (j == 0 && *stride == 1 && firstDimensionStride1_) { |
| result = *stride == 1; // contiguous or empty if so |
| } |
| if (lowerVal && upperVal) { |
| if (*lowerVal < *upperVal) { |
| if (*stride < 0) { |
| result = true; // empty dimension |
| } else if (!result && *stride > 1 && |
| *lowerVal + *stride <= *upperVal) { |
| result = false; // discontiguous if not empty |
| } |
| } else if (*lowerVal > *upperVal) { |
| if (*stride > 0) { |
| result = true; // empty dimension |
| } else if (!result && *stride < 0 && |
| *lowerVal + *stride >= *upperVal) { |
| result = false; // discontiguous if not empty |
| } |
| } else { // bounds known and equal |
| if (j == 0 && firstDimensionStride1_) { |
| result = true; // stride doesn't matter |
| } |
| } |
| } else { // bounds not both known |
| mayBeEmpty = true; |
| } |
| } else { // stride not known |
| if (lowerVal && upperVal && *lowerVal == *upperVal) { |
| // stride doesn't matter when bounds are equal |
| if (j == 0 && firstDimensionStride1_) { |
| result = true; |
| } |
| } else { |
| mayBeEmpty = true; |
| } |
| } |
| } else if (subscript[j].Rank() > 0) { // vector subscript |
| ++rank; |
| if (!result) { |
| result = false; |
| } |
| mayBeEmpty = true; |
| } else { // scalar subscript |
| if (dimExtent && *dimExtent > 1) { |
| knownPartialSlice[j] = true; |
| } |
| } |
| } |
| if (rank == 0) { |
| result = true; // scalar |
| } |
| if (result) { |
| return result; |
| } |
| // Not provably contiguous or discontiguous at this point. |
| // Return "true" if simply contiguous, otherwise nullopt. |
| for (auto j{subscript.size()}; j-- > 0;) { |
| if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) { |
| auto stride{ToInt64(triplet->stride())}; |
| if (!stride || stride != 1) { |
| return std::nullopt; |
| } else if (anyTriplet) { |
| if (triplet->GetLower() || triplet->GetUpper()) { |
| // all triplets before the last one must be just ":" for |
| // simple contiguity |
| return std::nullopt; |
| } |
| } else { |
| anyTriplet = true; |
| } |
| ++rank; |
| } else if (anyTriplet) { |
| // If the section cannot be empty, and this dimension's |
| // scalar subscript is known not to cover the whole |
| // dimension, then the array section is provably |
| // discontiguous. |
| return (mayBeEmpty || !knownPartialSlice[j]) |
| ? std::nullopt |
| : std::make_optional(false); |
| } |
| } |
| return true; // simply contiguous |
| } |
| |
| FoldingContext &context_; |
| bool namedConstantSectionsAreContiguous_{false}; |
| bool firstDimensionStride1_{false}; |
| }; |
| |
| template <typename A> |
| std::optional<bool> IsContiguous(const A &x, FoldingContext &context, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1) { |
| if (!IsVariable(x) && |
| (namedConstantSectionsAreContiguous || !ExtractDataRef(x, true, true))) { |
| return true; |
| } else { |
| return IsContiguousHelper{ |
| context, namedConstantSectionsAreContiguous, firstDimensionStride1}(x); |
| } |
| } |
| |
| std::optional<bool> IsContiguous(const ActualArgument &actual, |
| FoldingContext &fc, bool namedConstantSectionsAreContiguous, |
| bool firstDimensionStride1) { |
| auto *expr{actual.UnwrapExpr()}; |
| return expr && |
| IsContiguous( |
| *expr, fc, namedConstantSectionsAreContiguous, firstDimensionStride1); |
| } |
| |
| template std::optional<bool> IsContiguous(const Expr<SomeType> &, |
| FoldingContext &, bool namedConstantSectionsAreContiguous, |
| bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const ActualArgument &, |
| FoldingContext &, bool namedConstantSectionsAreContiguous, |
| bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const ArrayRef &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const Substring &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const Component &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const ComplexPart &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const CoarrayRef &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| template std::optional<bool> IsContiguous(const Symbol &, FoldingContext &, |
| bool namedConstantSectionsAreContiguous, bool firstDimensionStride1); |
| |
| // IsErrorExpr() |
| struct IsErrorExprHelper : public AnyTraverse<IsErrorExprHelper, bool> { |
| using Result = bool; |
| using Base = AnyTraverse<IsErrorExprHelper, Result>; |
| IsErrorExprHelper() : Base{*this} {} |
| using Base::operator(); |
| |
| bool operator()(const SpecificIntrinsic &x) { |
| return x.name == IntrinsicProcTable::InvalidName; |
| } |
| }; |
| |
| template <typename A> bool IsErrorExpr(const A &x) { |
| return IsErrorExprHelper{}(x); |
| } |
| |
| template bool IsErrorExpr(const Expr<SomeType> &); |
| |
| // C1577 |
| // TODO: Also check C1579 & C1582 here |
| class StmtFunctionChecker |
| : public AnyTraverse<StmtFunctionChecker, std::optional<parser::Message>> { |
| public: |
| using Result = std::optional<parser::Message>; |
| using Base = AnyTraverse<StmtFunctionChecker, Result>; |
| |
| static constexpr auto feature{ |
| common::LanguageFeature::StatementFunctionExtensions}; |
| |
| StmtFunctionChecker(const Symbol &sf, FoldingContext &context) |
| : Base{*this}, sf_{sf}, context_{context} { |
| if (!context_.languageFeatures().IsEnabled(feature)) { |
| severity_ = parser::Severity::Error; |
| } else if (context_.languageFeatures().ShouldWarn(feature)) { |
| severity_ = parser::Severity::Portability; |
| } |
| } |
| using Base::operator(); |
| |
| Result Return(parser::Message &&msg) const { |
| if (severity_) { |
| msg.set_severity(*severity_); |
| if (*severity_ != parser::Severity::Error) { |
| msg.set_languageFeature(feature); |
| } |
| } |
| return std::move(msg); |
| } |
| |
| template <typename T> Result operator()(const ArrayConstructor<T> &) const { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not contain an array constructor"_port_en_US, |
| sf_.name()}); |
| } else { |
| return std::nullopt; |
| } |
| } |
| Result operator()(const StructureConstructor &) const { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not contain a structure constructor"_port_en_US, |
| sf_.name()}); |
| } else { |
| return std::nullopt; |
| } |
| } |
| Result operator()(const TypeParamInquiry &) const { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not contain a type parameter inquiry"_port_en_US, |
| sf_.name()}); |
| } else { |
| return std::nullopt; |
| } |
| } |
| Result operator()(const ProcedureDesignator &proc) const { |
| if (const Symbol * symbol{proc.GetSymbol()}) { |
| const Symbol &ultimate{symbol->GetUltimate()}; |
| if (const auto *subp{ |
| ultimate.detailsIf<semantics::SubprogramDetails>()}) { |
| if (subp->stmtFunction() && &ultimate.owner() == &sf_.owner()) { |
| if (ultimate.name().begin() > sf_.name().begin()) { |
| return parser::Message{sf_.name(), |
| "Statement function '%s' may not reference another statement function '%s' that is defined later"_err_en_US, |
| sf_.name(), ultimate.name()}; |
| } |
| } |
| } |
| if (auto chars{characteristics::Procedure::Characterize( |
| proc, context_, /*emitError=*/true)}) { |
| if (!chars->CanBeCalledViaImplicitInterface()) { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not reference function '%s' that requires an explicit interface"_port_en_US, |
| sf_.name(), symbol->name()}); |
| } |
| } |
| } |
| } |
| if (proc.Rank() > 0) { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not reference a function that returns an array"_port_en_US, |
| sf_.name()}); |
| } |
| } |
| return std::nullopt; |
| } |
| Result operator()(const ActualArgument &arg) const { |
| if (const auto *expr{arg.UnwrapExpr()}) { |
| if (auto result{(*this)(*expr)}) { |
| return result; |
| } |
| if (expr->Rank() > 0 && !UnwrapWholeSymbolOrComponentDataRef(*expr)) { |
| if (severity_) { |
| return Return(parser::Message{sf_.name(), |
| "Statement function '%s' should not pass an array argument that is not a whole array"_port_en_US, |
| sf_.name()}); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| private: |
| const Symbol &sf_; |
| FoldingContext &context_; |
| std::optional<parser::Severity> severity_; |
| }; |
| |
| std::optional<parser::Message> CheckStatementFunction( |
| const Symbol &sf, const Expr<SomeType> &expr, FoldingContext &context) { |
| return StmtFunctionChecker{sf, context}(expr); |
| } |
| |
| // Helper class for checking differences between actual and dummy arguments |
| class CopyInOutExplicitInterface { |
| public: |
| explicit CopyInOutExplicitInterface(FoldingContext &fc, |
| const ActualArgument &actual, |
| const characteristics::DummyDataObject &dummyObj) |
| : fc_{fc}, actual_{actual}, dummyObj_{dummyObj} {} |
| |
| // Returns true, if actual and dummy have different contiguity requirements |
| bool HaveContiguityDifferences() const { |
| // Check actual contiguity, unless dummy doesn't care |
| bool dummyTreatAsArray{dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank)}; |
| bool actualTreatAsContiguous{ |
| dummyObj_.ignoreTKR.test(common::IgnoreTKR::Contiguous) || |
| IsSimplyContiguous(actual_, fc_)}; |
| bool dummyIsExplicitShape{dummyObj_.type.IsExplicitShape()}; |
| bool dummyIsAssumedSize{dummyObj_.type.attrs().test( |
| characteristics::TypeAndShape::Attr::AssumedSize)}; |
| bool dummyIsPolymorphic{dummyObj_.type.type().IsPolymorphic()}; |
| // type(*) with IGNORE_TKR(tkr) is often used to interface with C "void*". |
| // Since the other languages don't know about Fortran's discontiguity |
| // handling, such cases should require contiguity. |
| bool dummyIsVoidStar{dummyObj_.type.type().IsAssumedType() && |
| dummyObj_.ignoreTKR.test(common::IgnoreTKR::Type) && |
| dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank) && |
| dummyObj_.ignoreTKR.test(common::IgnoreTKR::Kind)}; |
| // Explicit shape and assumed size arrays must be contiguous |
| bool dummyNeedsContiguity{dummyIsExplicitShape || dummyIsAssumedSize || |
| (dummyTreatAsArray && !dummyIsPolymorphic) || dummyIsVoidStar || |
| dummyObj_.attrs.test( |
| characteristics::DummyDataObject::Attr::Contiguous)}; |
| return !actualTreatAsContiguous && dummyNeedsContiguity; |
| } |
| |
| // Returns true, if actual and dummy have polymorphic differences |
| bool HavePolymorphicDifferences() const { |
| bool dummyIsAssumedRank{dummyObj_.type.attrs().test( |
| characteristics::TypeAndShape::Attr::AssumedRank)}; |
| bool actualIsAssumedRank{semantics::IsAssumedRank(actual_)}; |
| bool dummyIsAssumedShape{dummyObj_.type.attrs().test( |
| characteristics::TypeAndShape::Attr::AssumedShape)}; |
| bool actualIsAssumedShape{semantics::IsAssumedShape(actual_)}; |
| if ((actualIsAssumedRank && dummyIsAssumedRank) || |
| (actualIsAssumedShape && dummyIsAssumedShape)) { |
| // Assumed-rank and assumed-shape arrays are represented by descriptors, |
| // so don't need to do polymorphic check. |
| } else if (!dummyObj_.ignoreTKR.test(common::IgnoreTKR::Type)) { |
| // flang supports limited cases of passing polymorphic to non-polimorphic. |
| // These cases require temporary of non-polymorphic type. (For example, |
| // the actual argument could be polymorphic array of child type, |
| // while the dummy argument could be non-polymorphic array of parent |
| // type.) |
| bool dummyIsPolymorphic{dummyObj_.type.type().IsPolymorphic()}; |
| auto actualType{ |
| characteristics::TypeAndShape::Characterize(actual_, fc_)}; |
| bool actualIsPolymorphic{ |
| actualType && actualType->type().IsPolymorphic()}; |
| if (actualIsPolymorphic && !dummyIsPolymorphic) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool HaveArrayOrAssumedRankArgs() const { |
| bool dummyTreatAsArray{dummyObj_.ignoreTKR.test(common::IgnoreTKR::Rank)}; |
| return IsArrayOrAssumedRank(actual_) && |
| (IsArrayOrAssumedRank(dummyObj_) || dummyTreatAsArray); |
| } |
| |
| bool PassByValue() const { |
| return dummyObj_.attrs.test(characteristics::DummyDataObject::Attr::Value); |
| } |
| |
| bool HaveCoarrayDifferences() const { |
| return ExtractCoarrayRef(actual_) && dummyObj_.type.corank() == 0; |
| } |
| |
| bool HasIntentOut() const { return dummyObj_.intent == common::Intent::Out; } |
| |
| bool HasIntentIn() const { return dummyObj_.intent == common::Intent::In; } |
| |
| static bool IsArrayOrAssumedRank(const ActualArgument &actual) { |
| return semantics::IsAssumedRank(actual) || actual.Rank() > 0; |
| } |
| |
| static bool IsArrayOrAssumedRank( |
| const characteristics::DummyDataObject &dummy) { |
| return dummy.type.attrs().test( |
| characteristics::TypeAndShape::Attr::AssumedRank) || |
| dummy.type.Rank() > 0; |
| } |
| |
| private: |
| FoldingContext &fc_; |
| const ActualArgument &actual_; |
| const characteristics::DummyDataObject &dummyObj_; |
| }; |
| |
| // If forCopyOut is false, returns if a particular actual/dummy argument |
| // combination may need a temporary creation with copy-in operation. If |
| // forCopyOut is true, returns the same for copy-out operation. For |
| // procedures with explicit interface, it's expected that "dummy" is not null. |
| // For procedures with implicit interface dummy may be null. |
| // |
| // Note that these copy-in and copy-out checks are done from the caller's |
| // perspective, meaning that for copy-in the caller need to do the copy |
| // before calling the callee. Similarly, for copy-out the caller is expected |
| // to do the copy after the callee returns. |
| bool MayNeedCopy(const ActualArgument *actual, |
| const characteristics::DummyArgument *dummy, FoldingContext &fc, |
| bool forCopyOut) { |
| if (!actual) { |
| return false; |
| } |
| if (actual->isAlternateReturn()) { |
| return false; |
| } |
| const auto *dummyObj{dummy |
| ? std::get_if<characteristics::DummyDataObject>(&dummy->u) |
| : nullptr}; |
| const bool forCopyIn = !forCopyOut; |
| if (!evaluate::IsVariable(*actual)) { |
| // Actual argument expressions that aren’t variables are copy-in, but |
| // not copy-out. |
| return forCopyIn; |
| } |
| if (dummyObj) { // Explict interface |
| CopyInOutExplicitInterface check{fc, *actual, *dummyObj}; |
| if (forCopyOut && check.HasIntentIn()) { |
| // INTENT(IN) dummy args never need copy-out |
| return false; |
| } |
| if (forCopyIn && check.HasIntentOut()) { |
| // INTENT(OUT) dummy args never need copy-in |
| return false; |
| } |
| if (check.PassByValue()) { |
| // Pass by value, always copy-in, never copy-out |
| return forCopyIn; |
| } |
| if (check.HaveCoarrayDifferences()) { |
| return true; |
| } |
| // Note: contiguity and polymorphic checks deal with array or assumed rank |
| // arguments |
| if (!check.HaveArrayOrAssumedRankArgs()) { |
| return false; |
| } |
| if (check.HaveContiguityDifferences()) { |
| return true; |
| } |
| if (check.HavePolymorphicDifferences()) { |
| return true; |
| } |
| } else { // Implicit interface |
| if (ExtractCoarrayRef(*actual)) { |
| // Coindexed actual args may need copy-in and copy-out with implicit |
| // interface |
| return true; |
| } |
| if (!IsSimplyContiguous(*actual, fc)) { |
| // Copy-in: actual arguments that are variables are copy-in when |
| // non-contiguous. |
| // Copy-out: vector subscripts could refer to duplicate elements, can't |
| // copy out. |
| return !(forCopyOut && HasVectorSubscript(*actual)); |
| } |
| } |
| // For everything else, no copy-in or copy-out |
| return false; |
| } |
| |
| } // namespace Fortran::evaluate |