//===-- lib/Semantics/resolve-names-utils.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 "resolve-names-utils.h" #include "flang/Common/Fortran-features.h" #include "flang/Common/idioms.h" #include "flang/Common/indirection.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/tools.h" #include "flang/Evaluate/type.h" #include "flang/Parser/char-block.h" #include "flang/Parser/parse-tree.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/semantics.h" #include "flang/Semantics/tools.h" #include #include namespace Fortran::semantics { using common::LanguageFeature; using common::LogicalOperator; using common::NumericOperator; using common::RelationalOperator; using IntrinsicOperator = parser::DefinedOperator::IntrinsicOperator; static constexpr const char *operatorPrefix{"operator("}; static GenericKind MapIntrinsicOperator(IntrinsicOperator); Symbol *Resolve(const parser::Name &name, Symbol *symbol) { if (symbol && !name.symbol) { name.symbol = symbol; } return symbol; } Symbol &Resolve(const parser::Name &name, Symbol &symbol) { return *Resolve(name, &symbol); } parser::MessageFixedText WithIsFatal( const parser::MessageFixedText &msg, bool isFatal) { return parser::MessageFixedText{ msg.text().begin(), msg.text().size(), isFatal}; } bool IsIntrinsicOperator( const SemanticsContext &context, const SourceName &name) { std::string str{name.ToString()}; for (int i{0}; i != common::LogicalOperator_enumSize; ++i) { auto names{context.languageFeatures().GetNames(LogicalOperator{i})}; if (std::find(names.begin(), names.end(), str) != names.end()) { return true; } } for (int i{0}; i != common::RelationalOperator_enumSize; ++i) { auto names{context.languageFeatures().GetNames(RelationalOperator{i})}; if (std::find(names.begin(), names.end(), str) != names.end()) { return true; } } return false; } template std::forward_list GetOperatorNames( const SemanticsContext &context, E opr) { std::forward_list result; for (const char *name : context.languageFeatures().GetNames(opr)) { result.emplace_front(std::string{operatorPrefix} + name + ')'); } return result; } std::forward_list GetAllNames( const SemanticsContext &context, const SourceName &name) { std::string str{name.ToString()}; if (!name.empty() && name.end()[-1] == ')' && name.ToString().rfind(std::string{operatorPrefix}, 0) == 0) { for (int i{0}; i != common::LogicalOperator_enumSize; ++i) { auto names{GetOperatorNames(context, LogicalOperator{i})}; if (std::find(names.begin(), names.end(), str) != names.end()) { return names; } } for (int i{0}; i != common::RelationalOperator_enumSize; ++i) { auto names{GetOperatorNames(context, RelationalOperator{i})}; if (std::find(names.begin(), names.end(), str) != names.end()) { return names; } } } return {str}; } bool IsLogicalConstant( const SemanticsContext &context, const SourceName &name) { std::string str{name.ToString()}; return str == ".true." || str == ".false." || (context.IsEnabled(LanguageFeature::LogicalAbbreviations) && (str == ".t" || str == ".f.")); } void GenericSpecInfo::Resolve(Symbol *symbol) const { if (symbol) { if (auto *details{symbol->detailsIf()}) { details->set_kind(kind_); } if (parseName_) { semantics::Resolve(*parseName_, symbol); } } } void GenericSpecInfo::Analyze(const parser::DefinedOpName &name) { kind_ = GenericKind::OtherKind::DefinedOp; parseName_ = &name.v; symbolName_ = name.v.source; } void GenericSpecInfo::Analyze(const parser::GenericSpec &x) { symbolName_ = x.source; kind_ = std::visit( common::visitors{ [&](const parser::Name &y) -> GenericKind { parseName_ = &y; symbolName_ = y.source; return GenericKind::OtherKind::Name; }, [&](const parser::DefinedOperator &y) { return std::visit( common::visitors{ [&](const parser::DefinedOpName &z) -> GenericKind { Analyze(z); return GenericKind::OtherKind::DefinedOp; }, [&](const IntrinsicOperator &z) { return MapIntrinsicOperator(z); }, }, y.u); }, [&](const parser::GenericSpec::Assignment &) -> GenericKind { return GenericKind::OtherKind::Assignment; }, [&](const parser::GenericSpec::ReadFormatted &) -> GenericKind { return GenericKind::DefinedIo::ReadFormatted; }, [&](const parser::GenericSpec::ReadUnformatted &) -> GenericKind { return GenericKind::DefinedIo::ReadUnformatted; }, [&](const parser::GenericSpec::WriteFormatted &) -> GenericKind { return GenericKind::DefinedIo::WriteFormatted; }, [&](const parser::GenericSpec::WriteUnformatted &) -> GenericKind { return GenericKind::DefinedIo::WriteUnformatted; }, }, x.u); } llvm::raw_ostream &operator<<( llvm::raw_ostream &os, const GenericSpecInfo &info) { os << "GenericSpecInfo: kind=" << info.kind_.ToString(); os << " parseName=" << (info.parseName_ ? info.parseName_->ToString() : "null"); os << " symbolName=" << (info.symbolName_ ? info.symbolName_->ToString() : "null"); return os; } // parser::DefinedOperator::IntrinsicOperator -> GenericKind static GenericKind MapIntrinsicOperator(IntrinsicOperator op) { switch (op) { SWITCH_COVERS_ALL_CASES case IntrinsicOperator::Concat: return GenericKind::OtherKind::Concat; case IntrinsicOperator::Power: return NumericOperator::Power; case IntrinsicOperator::Multiply: return NumericOperator::Multiply; case IntrinsicOperator::Divide: return NumericOperator::Divide; case IntrinsicOperator::Add: return NumericOperator::Add; case IntrinsicOperator::Subtract: return NumericOperator::Subtract; case IntrinsicOperator::AND: return LogicalOperator::And; case IntrinsicOperator::OR: return LogicalOperator::Or; case IntrinsicOperator::EQV: return LogicalOperator::Eqv; case IntrinsicOperator::NEQV: return LogicalOperator::Neqv; case IntrinsicOperator::NOT: return LogicalOperator::Not; case IntrinsicOperator::LT: return RelationalOperator::LT; case IntrinsicOperator::LE: return RelationalOperator::LE; case IntrinsicOperator::EQ: return RelationalOperator::EQ; case IntrinsicOperator::NE: return RelationalOperator::NE; case IntrinsicOperator::GE: return RelationalOperator::GE; case IntrinsicOperator::GT: return RelationalOperator::GT; } } class ArraySpecAnalyzer { public: ArraySpecAnalyzer(SemanticsContext &context) : context_{context} {} ArraySpec Analyze(const parser::ArraySpec &); ArraySpec AnalyzeDeferredShapeSpecList(const parser::DeferredShapeSpecList &); ArraySpec Analyze(const parser::ComponentArraySpec &); ArraySpec Analyze(const parser::CoarraySpec &); private: SemanticsContext &context_; ArraySpec arraySpec_; template void Analyze(const std::list &list) { for (const auto &elem : list) { Analyze(elem); } } void Analyze(const parser::AssumedShapeSpec &); void Analyze(const parser::ExplicitShapeSpec &); void Analyze(const parser::AssumedImpliedSpec &); void Analyze(const parser::DeferredShapeSpecList &); void Analyze(const parser::AssumedRankSpec &); void MakeExplicit(const std::optional &, const parser::SpecificationExpr &); void MakeImplied(const std::optional &); void MakeDeferred(int); Bound GetBound(const std::optional &); Bound GetBound(const parser::SpecificationExpr &); }; ArraySpec AnalyzeArraySpec( SemanticsContext &context, const parser::ArraySpec &arraySpec) { return ArraySpecAnalyzer{context}.Analyze(arraySpec); } ArraySpec AnalyzeArraySpec( SemanticsContext &context, const parser::ComponentArraySpec &arraySpec) { return ArraySpecAnalyzer{context}.Analyze(arraySpec); } ArraySpec AnalyzeDeferredShapeSpecList(SemanticsContext &context, const parser::DeferredShapeSpecList &deferredShapeSpecs) { return ArraySpecAnalyzer{context}.AnalyzeDeferredShapeSpecList( deferredShapeSpecs); } ArraySpec AnalyzeCoarraySpec( SemanticsContext &context, const parser::CoarraySpec &coarraySpec) { return ArraySpecAnalyzer{context}.Analyze(coarraySpec); } ArraySpec ArraySpecAnalyzer::Analyze(const parser::ComponentArraySpec &x) { std::visit([this](const auto &y) { Analyze(y); }, x.u); CHECK(!arraySpec_.empty()); return arraySpec_; } ArraySpec ArraySpecAnalyzer::Analyze(const parser::ArraySpec &x) { std::visit(common::visitors{ [&](const parser::AssumedSizeSpec &y) { Analyze(std::get>(y.t)); Analyze(std::get(y.t)); }, [&](const parser::ImpliedShapeSpec &y) { Analyze(y.v); }, [&](const auto &y) { Analyze(y); }, }, x.u); CHECK(!arraySpec_.empty()); return arraySpec_; } ArraySpec ArraySpecAnalyzer::AnalyzeDeferredShapeSpecList( const parser::DeferredShapeSpecList &x) { Analyze(x); CHECK(!arraySpec_.empty()); return arraySpec_; } ArraySpec ArraySpecAnalyzer::Analyze(const parser::CoarraySpec &x) { std::visit( common::visitors{ [&](const parser::DeferredCoshapeSpecList &y) { MakeDeferred(y.v); }, [&](const parser::ExplicitCoshapeSpec &y) { Analyze(std::get>(y.t)); MakeImplied( std::get>(y.t)); }, }, x.u); CHECK(!arraySpec_.empty()); return arraySpec_; } void ArraySpecAnalyzer::Analyze(const parser::AssumedShapeSpec &x) { arraySpec_.push_back(ShapeSpec::MakeAssumed(GetBound(x.v))); } void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeSpec &x) { MakeExplicit(std::get>(x.t), std::get(x.t)); } void ArraySpecAnalyzer::Analyze(const parser::AssumedImpliedSpec &x) { MakeImplied(x.v); } void ArraySpecAnalyzer::Analyze(const parser::DeferredShapeSpecList &x) { MakeDeferred(x.v); } void ArraySpecAnalyzer::Analyze(const parser::AssumedRankSpec &) { arraySpec_.push_back(ShapeSpec::MakeAssumedRank()); } void ArraySpecAnalyzer::MakeExplicit( const std::optional &lb, const parser::SpecificationExpr &ub) { arraySpec_.push_back(ShapeSpec::MakeExplicit(GetBound(lb), GetBound(ub))); } void ArraySpecAnalyzer::MakeImplied( const std::optional &lb) { arraySpec_.push_back(ShapeSpec::MakeImplied(GetBound(lb))); } void ArraySpecAnalyzer::MakeDeferred(int n) { for (int i = 0; i < n; ++i) { arraySpec_.push_back(ShapeSpec::MakeDeferred()); } } Bound ArraySpecAnalyzer::GetBound( const std::optional &x) { return x ? GetBound(*x) : Bound{1}; } Bound ArraySpecAnalyzer::GetBound(const parser::SpecificationExpr &x) { MaybeSubscriptIntExpr expr; if (MaybeExpr maybeExpr{AnalyzeExpr(context_, x.v)}) { if (auto *intExpr{evaluate::UnwrapExpr(*maybeExpr)}) { expr = evaluate::Fold(context_.foldingContext(), evaluate::ConvertToType( std::move(*intExpr))); } } return Bound{std::move(expr)}; } // If SAVE is set on src, set it on all members of dst static void PropagateSaveAttr( const EquivalenceObject &src, EquivalenceSet &dst) { if (src.symbol.attrs().test(Attr::SAVE)) { for (auto &obj : dst) { obj.symbol.attrs().set(Attr::SAVE); } } } static void PropagateSaveAttr(const EquivalenceSet &src, EquivalenceSet &dst) { if (!src.empty()) { PropagateSaveAttr(src.front(), dst); } } void EquivalenceSets::AddToSet(const parser::Designator &designator) { if (CheckDesignator(designator)) { Symbol &symbol{*currObject_.symbol}; if (!currSet_.empty()) { // check this symbol against first of set for compatibility Symbol &first{currSet_.front().symbol}; CheckCanEquivalence(designator.source, first, symbol) && CheckCanEquivalence(designator.source, symbol, first); } auto subscripts{currObject_.subscripts}; if (subscripts.empty() && symbol.IsObjectArray()) { // record a whole array as its first element for (const ShapeSpec &spec : symbol.get().shape()) { auto &lbound{spec.lbound().GetExplicit().value()}; subscripts.push_back(evaluate::ToInt64(lbound).value()); } } auto substringStart{currObject_.substringStart}; currSet_.emplace_back( symbol, subscripts, substringStart, designator.source); PropagateSaveAttr(currSet_.back(), currSet_); } currObject_ = {}; } void EquivalenceSets::FinishSet(const parser::CharBlock &source) { std::set existing; // indices of sets intersecting this one for (auto &obj : currSet_) { auto it{objectToSet_.find(obj)}; if (it != objectToSet_.end()) { existing.insert(it->second); // symbol already in this set } } if (existing.empty()) { sets_.push_back({}); // create a new equivalence set MergeInto(source, currSet_, sets_.size() - 1); } else { auto it{existing.begin()}; std::size_t dstIndex{*it}; MergeInto(source, currSet_, dstIndex); while (++it != existing.end()) { MergeInto(source, sets_[*it], dstIndex); } } currSet_.clear(); } // Report an error if sym1 and sym2 cannot be in the same equivalence set. bool EquivalenceSets::CheckCanEquivalence( const parser::CharBlock &source, const Symbol &sym1, const Symbol &sym2) { std::optional msg; const DeclTypeSpec *type1{sym1.GetType()}; const DeclTypeSpec *type2{sym2.GetType()}; bool isNum1{IsNumericSequenceType(type1)}; bool isNum2{IsNumericSequenceType(type2)}; bool isChar1{IsCharacterSequenceType(type1)}; bool isChar2{IsCharacterSequenceType(type2)}; if (sym1.attrs().test(Attr::PROTECTED) && !sym2.attrs().test(Attr::PROTECTED)) { // C8114 msg = "Equivalence set cannot contain '%s'" " with PROTECTED attribute and '%s' without"_err_en_US; } else if (isNum1) { if (isChar2) { if (context_.ShouldWarn( LanguageFeature::EquivalenceNumericWithCharacter)) { msg = "Equivalence set contains '%s' that is numeric sequence " "type and '%s' that is character"_en_US; } } else if (!isNum2) { // C8110 msg = "Equivalence set cannot contain '%s'" " that is numeric sequence type and '%s' that is not"_err_en_US; } } else if (isChar1) { if (isNum2) { if (context_.ShouldWarn( LanguageFeature::EquivalenceNumericWithCharacter)) { msg = "Equivalence set contains '%s' that is character sequence " "type and '%s' that is numeric"_en_US; } } else if (!isChar2) { // C8111 msg = "Equivalence set cannot contain '%s'" " that is character sequence type and '%s' that is not"_err_en_US; } } else if (!isNum2 && !isChar2 && *type1 != *type2) { // C8112, C8113 msg = "Equivalence set cannot contain '%s' and '%s' with different types" " that are neither numeric nor character sequence types"_err_en_US; } if (msg) { context_.Say(source, std::move(*msg), sym1.name(), sym2.name()); return false; } return true; } // Move objects from src to sets_[dstIndex] void EquivalenceSets::MergeInto(const parser::CharBlock &source, EquivalenceSet &src, std::size_t dstIndex) { EquivalenceSet &dst{sets_[dstIndex]}; PropagateSaveAttr(dst, src); for (const auto &obj : src) { dst.push_back(obj); objectToSet_[obj] = dstIndex; } PropagateSaveAttr(src, dst); src.clear(); } // If set has an object with this symbol, return it. const EquivalenceObject *EquivalenceSets::Find( const EquivalenceSet &set, const Symbol &symbol) { for (const auto &obj : set) { if (obj.symbol == symbol) { return &obj; } } return nullptr; } bool EquivalenceSets::CheckDesignator(const parser::Designator &designator) { return std::visit( common::visitors{ [&](const parser::DataRef &x) { return CheckDataRef(designator.source, x); }, [&](const parser::Substring &x) { const auto &dataRef{std::get(x.t)}; const auto &range{std::get(x.t)}; bool ok{CheckDataRef(designator.source, dataRef)}; if (const auto &lb{std::get<0>(range.t)}) { ok &= CheckSubstringBound(lb->thing.thing.value(), true); } else { currObject_.substringStart = 1; } if (const auto &ub{std::get<1>(range.t)}) { ok &= CheckSubstringBound(ub->thing.thing.value(), false); } return ok; }, }, designator.u); } bool EquivalenceSets::CheckDataRef( const parser::CharBlock &source, const parser::DataRef &x) { return std::visit( common::visitors{ [&](const parser::Name &name) { return CheckObject(name); }, [&](const common::Indirection &) { context_.Say(source, // C8107 "Derived type component '%s' is not allowed in an equivalence set"_err_en_US, source); return false; }, [&](const common::Indirection &elem) { bool ok{CheckDataRef(source, elem.value().base)}; for (const auto &subscript : elem.value().subscripts) { ok &= std::visit( common::visitors{ [&](const parser::SubscriptTriplet &) { context_.Say(source, // C924, R872 "Array section '%s' is not allowed in an equivalence set"_err_en_US, source); return false; }, [&](const parser::IntExpr &y) { return CheckArrayBound(y.thing.value()); }, }, subscript.u); } return ok; }, [&](const common::Indirection &) { context_.Say(source, // C924 (R872) "Coindexed object '%s' is not allowed in an equivalence set"_err_en_US, source); return false; }, }, x.u); } static bool InCommonWithBind(const Symbol &symbol) { if (const auto *details{symbol.detailsIf()}) { const Symbol *commonBlock{details->commonBlock()}; return commonBlock && commonBlock->attrs().test(Attr::BIND_C); } else { return false; } } // If symbol can't be in equivalence set report error and return false; bool EquivalenceSets::CheckObject(const parser::Name &name) { if (!name.symbol) { return false; // an error has already occurred } currObject_.symbol = name.symbol; parser::MessageFixedText msg{"", 0}; const Symbol &symbol{*name.symbol}; if (symbol.owner().IsDerivedType()) { // C8107 msg = "Derived type component '%s'" " is not allowed in an equivalence set"_err_en_US; } else if (IsDummy(symbol)) { // C8106 msg = "Dummy argument '%s' is not allowed in an equivalence set"_err_en_US; } else if (symbol.IsFuncResult()) { // C8106 msg = "Function result '%s' is not allow in an equivalence set"_err_en_US; } else if (IsPointer(symbol)) { // C8106 msg = "Pointer '%s' is not allowed in an equivalence set"_err_en_US; } else if (IsAllocatable(symbol)) { // C8106 msg = "Allocatable variable '%s'" " is not allowed in an equivalence set"_err_en_US; } else if (symbol.Corank() > 0) { // C8106 msg = "Coarray '%s' is not allowed in an equivalence set"_err_en_US; } else if (symbol.has()) { // C8115 msg = "Use-associated variable '%s'" " is not allowed in an equivalence set"_err_en_US; } else if (symbol.attrs().test(Attr::BIND_C)) { // C8106 msg = "Variable '%s' with BIND attribute" " is not allowed in an equivalence set"_err_en_US; } else if (symbol.attrs().test(Attr::TARGET)) { // C8108 msg = "Variable '%s' with TARGET attribute" " is not allowed in an equivalence set"_err_en_US; } else if (IsNamedConstant(symbol)) { // C8106 msg = "Named constant '%s' is not allowed in an equivalence set"_err_en_US; } else if (InCommonWithBind(symbol)) { // C8106 msg = "Variable '%s' in common block with BIND attribute" " is not allowed in an equivalence set"_err_en_US; } else if (const auto *type{symbol.GetType()}) { if (const auto *derived{type->AsDerived()}) { if (const auto *comp{FindUltimateComponent( *derived, IsAllocatableOrPointer)}) { // C8106 msg = IsPointer(*comp) ? "Derived type object '%s' with pointer ultimate component" " is not allowed in an equivalence set"_err_en_US : "Derived type object '%s' with allocatable ultimate component" " is not allowed in an equivalence set"_err_en_US; } else if (!derived->typeSymbol().get().sequence()) { msg = "Nonsequence derived type object '%s'" " is not allowed in an equivalence set"_err_en_US; } } else if (IsAutomaticObject(symbol)) { msg = "Automatic object '%s'" " is not allowed in an equivalence set"_err_en_US; } } if (!msg.text().empty()) { context_.Say(name.source, std::move(msg), name.source); return false; } return true; } bool EquivalenceSets::CheckArrayBound(const parser::Expr &bound) { MaybeExpr expr{ evaluate::Fold(context_.foldingContext(), AnalyzeExpr(context_, bound))}; if (!expr) { return false; } if (expr->Rank() > 0) { context_.Say(bound.source, // C924, R872 "Array with vector subscript '%s' is not allowed in an equivalence set"_err_en_US, bound.source); return false; } auto subscript{evaluate::ToInt64(*expr)}; if (!subscript) { context_.Say(bound.source, // C8109 "Array with nonconstant subscript '%s' is not allowed in an equivalence set"_err_en_US, bound.source); return false; } currObject_.subscripts.push_back(*subscript); return true; } bool EquivalenceSets::CheckSubstringBound( const parser::Expr &bound, bool isStart) { MaybeExpr expr{ evaluate::Fold(context_.foldingContext(), AnalyzeExpr(context_, bound))}; if (!expr) { return false; } auto subscript{evaluate::ToInt64(*expr)}; if (!subscript) { context_.Say(bound.source, // C8109 "Substring with nonconstant bound '%s' is not allowed in an equivalence set"_err_en_US, bound.source); return false; } if (!isStart) { auto start{currObject_.substringStart}; if (*subscript < (start ? *start : 1)) { context_.Say(bound.source, // C8116 "Substring with zero length is not allowed in an equivalence set"_err_en_US); return false; } } else if (*subscript != 1) { currObject_.substringStart = *subscript; } return true; } bool EquivalenceSets::IsCharacterSequenceType(const DeclTypeSpec *type) { return IsSequenceType(type, [&](const IntrinsicTypeSpec &type) { auto kind{evaluate::ToInt64(type.kind())}; return type.category() == TypeCategory::Character && kind && kind.value() == context_.GetDefaultKind(TypeCategory::Character); }); } // Numeric or logical type of default kind or DOUBLE PRECISION or DOUBLE COMPLEX bool EquivalenceSets::IsDefaultKindNumericType(const IntrinsicTypeSpec &type) { if (auto kind{evaluate::ToInt64(type.kind())}) { auto category{type.category()}; auto defaultKind{context_.GetDefaultKind(category)}; switch (category) { case TypeCategory::Integer: case TypeCategory::Logical: return *kind == defaultKind; case TypeCategory::Real: case TypeCategory::Complex: return *kind == defaultKind || *kind == context_.doublePrecisionKind(); default: return false; } } return false; } bool EquivalenceSets::IsNumericSequenceType(const DeclTypeSpec *type) { return IsSequenceType(type, [&](const IntrinsicTypeSpec &type) { return IsDefaultKindNumericType(type); }); } // Is type an intrinsic type that satisfies predicate or a sequence type // whose components do. bool EquivalenceSets::IsSequenceType(const DeclTypeSpec *type, std::function predicate) { if (!type) { return false; } else if (const IntrinsicTypeSpec * intrinsic{type->AsIntrinsic()}) { return predicate(*intrinsic); } else if (const DerivedTypeSpec * derived{type->AsDerived()}) { for (const auto &pair : *derived->typeSymbol().scope()) { const Symbol &component{*pair.second}; if (IsAllocatableOrPointer(component) || !IsSequenceType(component.GetType(), predicate)) { return false; } } return true; } else { return false; } } } // namespace Fortran::semantics