| //===-- lib/Semantics/definable.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 "definable.h" |
| #include "flang/Evaluate/tools.h" |
| #include "flang/Semantics/tools.h" |
| |
| using namespace Fortran::parser::literals; |
| |
| namespace Fortran::semantics { |
| |
| template <typename... A> |
| static parser::Message BlameSymbol(parser::CharBlock at, |
| const parser::MessageFixedText &text, const Symbol &original, A &&...x) { |
| parser::Message message{at, text, original.name(), std::forward<A>(x)...}; |
| message.set_severity(parser::Severity::Because); |
| evaluate::AttachDeclaration(message, original); |
| return message; |
| } |
| |
| static bool IsPointerDummyOfPureFunction(const Symbol &x) { |
| return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) && |
| x.owner().symbol() && IsFunction(*x.owner().symbol()); |
| } |
| |
| // See C1594, first paragraph. These conditions enable checks on both |
| // left-hand and right-hand sides in various circumstances. |
| const char *WhyBaseObjectIsSuspicious(const Symbol &x, const Scope &scope) { |
| if (IsHostAssociatedIntoSubprogram(x, scope)) { |
| return "host-associated"; |
| } else if (IsUseAssociated(x, scope)) { |
| return "USE-associated"; |
| } else if (IsPointerDummyOfPureFunction(x)) { |
| return "a POINTER dummy argument of a pure function"; |
| } else if (IsIntentIn(x)) { |
| return "an INTENT(IN) dummy argument"; |
| } else if (FindCommonBlockContaining(x)) { |
| return "in a COMMON block"; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| // Checks C1594(1,2); false if check fails |
| static std::optional<parser::Message> CheckDefinabilityInPureScope( |
| SourceName at, const Symbol &original, const Symbol &ultimate, |
| const Scope &context, const Scope &pure) { |
| if (pure.symbol()) { |
| if (const char *why{WhyBaseObjectIsSuspicious(ultimate, context)}) { |
| return BlameSymbol(at, |
| "'%s' may not be defined in pure subprogram '%s' because it is %s"_en_US, |
| original, pure.symbol()->name(), why); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // True when the object being defined is not a subobject of the base |
| // object, e.g. X%PTR = 1., X%PTR%PTR2 => T (but not X%PTR => T). |
| // F'2023 9.4.2p5 |
| static bool DefinesComponentPointerTarget( |
| const evaluate::DataRef &dataRef, DefinabilityFlags flags) { |
| if (const evaluate::Component * |
| component{common::visit( |
| common::visitors{ |
| [](const SymbolRef &) -> const evaluate::Component * { |
| return nullptr; |
| }, |
| [](const evaluate::Component &component) { return &component; }, |
| [](const evaluate::ArrayRef &aRef) { |
| return aRef.base().UnwrapComponent(); |
| }, |
| [](const evaluate::CoarrayRef &aRef) |
| -> const evaluate::Component * { return nullptr; }, |
| }, |
| dataRef.u)}) { |
| const Symbol &compSym{component->GetLastSymbol()}; |
| if (IsPointer(compSym) || |
| (flags.test(DefinabilityFlag::AcceptAllocatable) && |
| IsAllocatable(compSym))) { |
| if (!flags.test(DefinabilityFlag::PointerDefinition)) { |
| return true; |
| } |
| } |
| flags.reset(DefinabilityFlag::PointerDefinition); |
| return DefinesComponentPointerTarget(component->base(), flags); |
| } else { |
| return false; |
| } |
| } |
| |
| // Check the leftmost (or only) symbol from a data-ref or expression. |
| static std::optional<parser::Message> WhyNotDefinableBase(parser::CharBlock at, |
| const Scope &scope, DefinabilityFlags flags, const Symbol &original, |
| bool isWholeSymbol, bool isComponentPointerTarget) { |
| const Symbol &ultimate{original.GetUltimate()}; |
| bool isPointerDefinition{flags.test(DefinabilityFlag::PointerDefinition)}; |
| bool acceptAllocatable{flags.test(DefinabilityFlag::AcceptAllocatable)}; |
| bool isTargetDefinition{!isPointerDefinition && IsPointer(ultimate)}; |
| if (const auto *association{ultimate.detailsIf<AssocEntityDetails>()}) { |
| if (!IsVariable(association->expr())) { |
| return BlameSymbol(at, |
| "'%s' is construct associated with an expression"_en_US, original); |
| } else if (evaluate::HasVectorSubscript(association->expr().value())) { |
| return BlameSymbol(at, |
| "Construct association '%s' has a vector subscript"_en_US, original); |
| } else if (auto dataRef{evaluate::ExtractDataRef( |
| *association->expr(), true, true)}) { |
| return WhyNotDefinableBase(at, scope, flags, dataRef->GetFirstSymbol(), |
| isWholeSymbol && |
| std::holds_alternative<evaluate::SymbolRef>(dataRef->u), |
| isComponentPointerTarget || |
| DefinesComponentPointerTarget(*dataRef, flags)); |
| } |
| } |
| if (isTargetDefinition || isComponentPointerTarget) { |
| } else if (!isPointerDefinition && !IsVariableName(ultimate)) { |
| return BlameSymbol(at, "'%s' is not a variable"_en_US, original); |
| } else if (IsProtected(ultimate) && IsUseAssociated(original, scope)) { |
| return BlameSymbol(at, "'%s' is protected in this scope"_en_US, original); |
| } else if (IsIntentIn(ultimate) && |
| (!IsPointer(ultimate) || (isWholeSymbol && isPointerDefinition))) { |
| return BlameSymbol( |
| at, "'%s' is an INTENT(IN) dummy argument"_en_US, original); |
| } |
| if (const Scope * pure{FindPureProcedureContaining(scope)}) { |
| // Additional checking for pure subprograms. |
| if (!isTargetDefinition || isComponentPointerTarget) { |
| if (auto msg{CheckDefinabilityInPureScope( |
| at, original, ultimate, scope, *pure)}) { |
| return msg; |
| } |
| } |
| if (const Symbol * |
| visible{FindExternallyVisibleObject( |
| ultimate, *pure, isPointerDefinition)}) { |
| return BlameSymbol(at, |
| "'%s' is externally visible via '%s' and not definable in a pure subprogram"_en_US, |
| original, visible->name()); |
| } |
| } |
| if (const Scope * deviceContext{FindCUDADeviceContext(&scope)}) { |
| bool isOwnedByDeviceCode{deviceContext->Contains(ultimate.owner())}; |
| if (isPointerDefinition && !acceptAllocatable) { |
| return BlameSymbol(at, |
| "'%s' is a pointer and may not be associated in a device subprogram"_err_en_US, |
| original); |
| } else if (auto cudaDataAttr{GetCUDADataAttr(&ultimate)}) { |
| if (*cudaDataAttr == common::CUDADataAttr::Constant) { |
| return BlameSymbol(at, |
| "'%s' has ATTRIBUTES(CONSTANT) and is not definable in a device subprogram"_err_en_US, |
| original); |
| } else if (acceptAllocatable && !isOwnedByDeviceCode) { |
| return BlameSymbol(at, |
| "'%s' is a host-associated allocatable and is not definable in a device subprogram"_err_en_US, |
| original); |
| } else if (*cudaDataAttr != common::CUDADataAttr::Device && |
| *cudaDataAttr != common::CUDADataAttr::Managed && |
| *cudaDataAttr != common::CUDADataAttr::Shared) { |
| return BlameSymbol(at, |
| "'%s' is not device or managed or shared data and is not definable in a device subprogram"_err_en_US, |
| original); |
| } |
| } else if (!isOwnedByDeviceCode) { |
| return BlameSymbol(at, |
| "'%s' is a host variable and is not definable in a device subprogram"_err_en_US, |
| original); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| static std::optional<parser::Message> WhyNotDefinableLast(parser::CharBlock at, |
| const Scope &scope, DefinabilityFlags flags, const Symbol &original) { |
| const Symbol &ultimate{original.GetUltimate()}; |
| if (const auto *association{ultimate.detailsIf<AssocEntityDetails>()}) { |
| if (auto dataRef{ |
| evaluate::ExtractDataRef(*association->expr(), true, true)}) { |
| return WhyNotDefinableLast(at, scope, flags, dataRef->GetLastSymbol()); |
| } |
| } |
| if (flags.test(DefinabilityFlag::PointerDefinition)) { |
| if (flags.test(DefinabilityFlag::AcceptAllocatable)) { |
| if (!IsAllocatableOrObjectPointer(&ultimate)) { |
| return BlameSymbol( |
| at, "'%s' is neither a pointer nor an allocatable"_en_US, original); |
| } |
| } else if (!IsPointer(ultimate)) { |
| return BlameSymbol(at, "'%s' is not a pointer"_en_US, original); |
| } |
| return std::nullopt; // pointer assignment - skip following checks |
| } |
| if (IsOrContainsEventOrLockComponent(ultimate)) { |
| return BlameSymbol(at, |
| "'%s' is an entity with either an EVENT_TYPE or LOCK_TYPE"_en_US, |
| original); |
| } |
| if (FindPureProcedureContaining(scope)) { |
| if (auto dyType{evaluate::DynamicType::From(ultimate)}) { |
| if (!flags.test(DefinabilityFlag::PolymorphicOkInPure)) { |
| if (dyType->IsPolymorphic()) { // C1596 |
| return BlameSymbol(at, |
| "'%s' is polymorphic in a pure subprogram"_because_en_US, |
| original); |
| } |
| } |
| if (const Symbol * impure{HasImpureFinal(ultimate)}) { |
| return BlameSymbol(at, |
| "'%s' has an impure FINAL procedure '%s'"_because_en_US, original, |
| impure->name()); |
| } |
| if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(dyType)}) { |
| if (!flags.test(DefinabilityFlag::PolymorphicOkInPure)) { |
| if (auto bad{FindPolymorphicAllocatableUltimateComponent(*derived)}) { |
| return BlameSymbol(at, |
| "'%s' has polymorphic component '%s' in a pure subprogram"_because_en_US, |
| original, bad.BuildResultDesignatorName()); |
| } |
| } |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // Checks a data-ref |
| static std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at, |
| const Scope &scope, DefinabilityFlags flags, |
| const evaluate::DataRef &dataRef) { |
| if (auto whyNot{ |
| WhyNotDefinableBase(at, scope, flags, dataRef.GetFirstSymbol(), |
| std::holds_alternative<evaluate::SymbolRef>(dataRef.u), |
| DefinesComponentPointerTarget(dataRef, flags))}) { |
| return whyNot; |
| } else { |
| return WhyNotDefinableLast(at, scope, flags, dataRef.GetLastSymbol()); |
| } |
| } |
| |
| std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at, |
| const Scope &scope, DefinabilityFlags flags, const Symbol &original) { |
| if (auto base{WhyNotDefinableBase(at, scope, flags, original, |
| /*isWholeSymbol=*/true, /*isComponentPointerTarget=*/false)}) { |
| return base; |
| } else { |
| return WhyNotDefinableLast(at, scope, flags, original); |
| } |
| } |
| |
| class DuplicatedSubscriptFinder |
| : public evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool> { |
| using Base = evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool>; |
| |
| public: |
| explicit DuplicatedSubscriptFinder(evaluate::FoldingContext &foldingContext) |
| : Base{*this}, foldingContext_{foldingContext} {} |
| using Base::operator(); |
| bool operator()(const evaluate::ActualArgument &) { |
| return false; // don't descend into argument expressions |
| } |
| bool operator()(const evaluate::ArrayRef &aRef) { |
| bool anyVector{false}; |
| for (const auto &ss : aRef.subscript()) { |
| if (ss.Rank() > 0) { |
| anyVector = true; |
| if (const auto *vecExpr{ |
| std::get_if<evaluate::IndirectSubscriptIntegerExpr>(&ss.u)}) { |
| auto folded{evaluate::Fold(foldingContext_, |
| evaluate::Expr<evaluate::SubscriptInteger>{vecExpr->value()})}; |
| if (const auto *con{ |
| evaluate::UnwrapConstantValue<evaluate::SubscriptInteger>( |
| folded)}) { |
| std::set<std::int64_t> values; |
| for (const auto &j : con->values()) { |
| if (auto pair{values.emplace(j.ToInt64())}; !pair.second) { |
| return true; // duplicate |
| } |
| } |
| } |
| return false; |
| } |
| } |
| } |
| return anyVector ? false : (*this)(aRef.base()); |
| } |
| |
| private: |
| evaluate::FoldingContext &foldingContext_; |
| }; |
| |
| std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at, |
| const Scope &scope, DefinabilityFlags flags, |
| const evaluate::Expr<evaluate::SomeType> &expr) { |
| if (auto dataRef{evaluate::ExtractDataRef(expr, true, true)}) { |
| if (evaluate::HasVectorSubscript(expr)) { |
| if (flags.test(DefinabilityFlag::VectorSubscriptIsOk)) { |
| if (auto type{expr.GetType()}) { |
| if (!type->IsUnlimitedPolymorphic() && |
| type->category() == TypeCategory::Derived) { |
| // Seek the FINAL subroutine that should but cannot be called |
| // for this definition of an array with a vector-valued subscript. |
| // If there's an elemental FINAL subroutine, all is well; otherwise, |
| // if there is a FINAL subroutine with a matching or assumed rank |
| // dummy argument, there's no way to call it. |
| int rank{expr.Rank()}; |
| const DerivedTypeSpec *spec{&type->GetDerivedTypeSpec()}; |
| while (spec) { |
| bool anyElemental{false}; |
| const Symbol *anyRankMatch{nullptr}; |
| for (auto ref : FinalsForDerivedTypeInstantiation(*spec)) { |
| const Symbol &ultimate{ref->GetUltimate()}; |
| anyElemental |= ultimate.attrs().test(Attr::ELEMENTAL); |
| if (const auto *subp{ultimate.detailsIf<SubprogramDetails>()}) { |
| if (!subp->dummyArgs().empty()) { |
| if (const Symbol * arg{subp->dummyArgs()[0]}) { |
| const auto *object{arg->detailsIf<ObjectEntityDetails>()}; |
| if (arg->Rank() == rank || |
| (object && object->IsAssumedRank())) { |
| anyRankMatch = &*ref; |
| } |
| } |
| } |
| } |
| } |
| if (anyRankMatch && !anyElemental) { |
| return parser::Message{at, |
| "Variable '%s' has a vector subscript and cannot be finalized by non-elemental subroutine '%s'"_because_en_US, |
| expr.AsFortran(), anyRankMatch->name()}; |
| } |
| const auto *parent{FindParentTypeSpec(*spec)}; |
| spec = parent ? parent->AsDerived() : nullptr; |
| } |
| } |
| } |
| if (!flags.test(DefinabilityFlag::DuplicatesAreOk) && |
| DuplicatedSubscriptFinder{scope.context().foldingContext()}(expr)) { |
| return parser::Message{at, |
| "Variable has a vector subscript with a duplicated element"_because_en_US}; |
| } |
| } else { |
| return parser::Message{at, |
| "Variable '%s' has a vector subscript"_because_en_US, |
| expr.AsFortran()}; |
| } |
| } |
| if (FindPureProcedureContaining(scope) && |
| evaluate::ExtractCoarrayRef(expr)) { |
| return parser::Message(at, |
| "A pure subprogram may not define the coindexed object '%s'"_because_en_US, |
| expr.AsFortran()); |
| } |
| return WhyNotDefinable(at, scope, flags, *dataRef); |
| } else if (evaluate::IsNullPointer(expr)) { |
| return parser::Message{ |
| at, "'%s' is a null pointer"_because_en_US, expr.AsFortran()}; |
| } else if (flags.test(DefinabilityFlag::PointerDefinition)) { |
| if (const auto *procDesignator{ |
| std::get_if<evaluate::ProcedureDesignator>(&expr.u)}) { |
| // Defining a procedure pointer |
| if (const Symbol * procSym{procDesignator->GetSymbol()}) { |
| if (evaluate::ExtractCoarrayRef(expr)) { // C1027 |
| return BlameSymbol(at, |
| "Procedure pointer '%s' may not be a coindexed object"_because_en_US, |
| *procSym, expr.AsFortran()); |
| } |
| if (const auto *component{procDesignator->GetComponent()}) { |
| flags.reset(DefinabilityFlag::PointerDefinition); |
| return WhyNotDefinableBase(at, scope, flags, |
| component->base().GetFirstSymbol(), false, |
| DefinesComponentPointerTarget(component->base(), flags)); |
| } else { |
| return WhyNotDefinable(at, scope, flags, *procSym); |
| } |
| } |
| } |
| return parser::Message{ |
| at, "'%s' is not a definable pointer"_because_en_US, expr.AsFortran()}; |
| } else if (!evaluate::IsVariable(expr)) { |
| return parser::Message{at, |
| "'%s' is not a variable or pointer"_because_en_US, expr.AsFortran()}; |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| } // namespace Fortran::semantics |