//===-- 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 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(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 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; } // When a DataRef contains pointers, gets the rightmost one (unless it is // the entity being defined, in which case the last pointer above it); // otherwise, returns the leftmost symbol. The resulting symbol is the // relevant base object for definabiliy checking. Examples: // ptr1%ptr2 => ... -> ptr1 // nonptr%ptr => ... -> nonptr // nonptr%ptr = ... -> ptr // ptr1%ptr2 = ... -> ptr2 // ptr1%ptr2%nonptr = ... -> ptr2 // nonptr1%nonptr2 = ... -> nonptr1 static const Symbol &GetRelevantSymbol(const evaluate::DataRef &dataRef, bool isPointerDefinition, bool acceptAllocatable) { if (isPointerDefinition) { if (const auto *component{std::get_if(&dataRef.u)}) { if (IsPointer(component->GetLastSymbol()) || (acceptAllocatable && IsAllocatable(component->GetLastSymbol()))) { return GetRelevantSymbol(component->base(), false, false); } } } if (const Symbol * lastPointer{GetLastPointerSymbol(dataRef)}) { return *lastPointer; } else { return dataRef.GetFirstSymbol(); } } // Check the leftmost (or only) symbol from a data-ref or expression. static std::optional WhyNotDefinableBase(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const Symbol &original, bool isWholeSymbol) { 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()}) { 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, GetRelevantSymbol(*dataRef, isPointerDefinition, acceptAllocatable), isWholeSymbol); } } if (isTargetDefinition) { } 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) { 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) { return BlameSymbol(at, "'%s' is not device or managed 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 WhyNotDefinableLast(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const Symbol &original) { const Symbol &ultimate{original.GetUltimate()}; if (const auto *association{ultimate.detailsIf()}) { 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 WhyNotDefinable(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const evaluate::DataRef &dataRef) { const Symbol &base{GetRelevantSymbol(dataRef, flags.test(DefinabilityFlag::PointerDefinition), flags.test(DefinabilityFlag::AcceptAllocatable))}; if (auto whyNot{WhyNotDefinableBase(at, scope, flags, base, std::holds_alternative(dataRef.u))}) { return whyNot; } else { return WhyNotDefinableLast(at, scope, flags, dataRef.GetLastSymbol()); } } // Checks a NOPASS procedure pointer component static std::optional WhyNotDefinable(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const evaluate::Component &component) { const evaluate::DataRef &dataRef{component.base()}; const Symbol &base{GetRelevantSymbol(dataRef, false, false)}; DefinabilityFlags baseFlags{flags}; baseFlags.reset(DefinabilityFlag::PointerDefinition); return WhyNotDefinableBase(at, scope, baseFlags, base, std::holds_alternative(dataRef.u)); } std::optional WhyNotDefinable(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const Symbol &original) { if (auto base{WhyNotDefinableBase(at, scope, flags, original, true)}) { return base; } return WhyNotDefinableLast(at, scope, flags, original); } class DuplicatedSubscriptFinder : public evaluate::AnyTraverse { using Base = evaluate::AnyTraverse; 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(&ss.u)}) { auto folded{evaluate::Fold(foldingContext_, evaluate::Expr{vecExpr->value()})}; if (const auto *con{ evaluate::UnwrapConstantValue( folded)}) { std::set 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 WhyNotDefinable(parser::CharBlock at, const Scope &scope, DefinabilityFlags flags, const evaluate::Expr &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()}) { if (!subp->dummyArgs().empty()) { if (const Symbol * arg{subp->dummyArgs()[0]}) { const auto *object{arg->detailsIf()}; 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(&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()}) { return WhyNotDefinable(at, scope, flags, *component); } 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