| //===-- 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/idioms.h" | |
| #include "flang/Common/indirection.h" | |
| #include "flang/Evaluate/fold.h" | |
| #include "flang/Evaluate/tools.h" | |
| #include "flang/Evaluate/traverse.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 "flang/Support/Fortran-features.h" | |
| #include "flang/Support/Fortran.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include <variant> | |
| namespace Fortran::semantics { | |
| using common::LanguageFeature; | |
| using common::LogicalOperator; | |
| using common::NumericOperator; | |
| using common::RelationalOperator; | |
| using IntrinsicOperator = parser::DefinedOperator::IntrinsicOperator; | |
| 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 WithSeverity( | |
| const parser::MessageFixedText &msg, parser::Severity severity) { | |
| return parser::MessageFixedText{ | |
| msg.text().begin(), msg.text().size(), severity}; | |
| } | |
| 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 (llvm::is_contained(names, str)) { | |
| return true; | |
| } | |
| } | |
| for (int i{0}; i != common::RelationalOperator_enumSize; ++i) { | |
| auto names{context.languageFeatures().GetNames(RelationalOperator{i})}; | |
| if (llvm::is_contained(names, str)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| 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<GenericDetails>()}) { | |
| 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_ = common::visit( | |
| common::visitors{ | |
| [&](const parser::Name &y) -> GenericKind { | |
| parseName_ = &y; | |
| symbolName_ = y.source; | |
| return GenericKind::OtherKind::Name; | |
| }, | |
| [&](const parser::DefinedOperator &y) { | |
| return common::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 common::DefinedIo::ReadFormatted; | |
| }, | |
| [&](const parser::GenericSpec::ReadUnformatted &) -> GenericKind { | |
| return common::DefinedIo::ReadUnformatted; | |
| }, | |
| [&](const parser::GenericSpec::WriteFormatted &) -> GenericKind { | |
| return common::DefinedIo::WriteFormatted; | |
| }, | |
| [&](const parser::GenericSpec::WriteUnformatted &) -> GenericKind { | |
| return common::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 &); | |
| // Bounds of a zero-size explicit-shape bounds array (F2023). The entity is | |
| // scalar, so these are dropped from the shape, but they remain specification | |
| // expressions to be validated during declaration checking. | |
| std::vector<Bound> TakeDroppedBoundsToCheck() { | |
| return std::move(droppedBoundsToCheck_); | |
| } | |
| private: | |
| SemanticsContext &context_; | |
| ArraySpec arraySpec_; | |
| // Set when an explicit-shape-bounds-spec with a zero-size bounds array | |
| // legitimately produces a scalar (rank 0), leaving arraySpec_ empty. | |
| bool zeroRankExplicitBounds_{false}; | |
| // Bounds dropped from a scalar (zero-size bounds array) declaration's shape, | |
| // retained as specification expressions to be validated during declaration | |
| // checking. | |
| std::vector<Bound> droppedBoundsToCheck_; | |
| template <typename T> void Analyze(const std::list<T> &list) { | |
| for (const auto &elem : list) { | |
| Analyze(elem); | |
| } | |
| } | |
| void Analyze(const parser::AssumedShapeSpec &); | |
| void Analyze(const parser::AssumedShapeBoundsSpec &); | |
| void Analyze(const parser::ExplicitShapeSpec &); | |
| void Analyze(const parser::ExplicitShapeBoundsSpec &); | |
| void Analyze(const parser::AssumedImpliedSpec &); | |
| void Analyze(const parser::DeferredShapeSpecList &); | |
| void Analyze(const parser::AssumedRankSpec &); | |
| void MakeExplicit(const std::optional<parser::SpecificationExpr> &, | |
| const parser::SpecificationExpr &); | |
| void MakeImplied(const std::optional<parser::SpecificationExpr> &); | |
| void MakeDeferred(int); | |
| Bound GetBound(const std::optional<parser::SpecificationExpr> &); | |
| Bound GetBound(const parser::SpecificationExpr &); | |
| struct ExplicitShapeBoundsResult { | |
| Bound ubound; | |
| std::optional<Bound> lbound; | |
| std::int64_t numDims; | |
| }; | |
| std::optional<ExplicitShapeBoundsResult> CheckExplicitShapeBoundsSpec( | |
| const parser::ExplicitShapeBoundsSpec &x); | |
| }; | |
| ArraySpec AnalyzeArraySpec(SemanticsContext &context, | |
| const parser::ArraySpec &arraySpec, | |
| std::vector<Bound> &droppedBoundsToCheck) { | |
| ArraySpecAnalyzer analyzer{context}; | |
| ArraySpec result{analyzer.Analyze(arraySpec)}; | |
| droppedBoundsToCheck = analyzer.TakeDroppedBoundsToCheck(); | |
| return result; | |
| } | |
| 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) { | |
| common::visit([this](const auto &y) { Analyze(y); }, x.u); | |
| CHECK(!arraySpec_.empty()); | |
| return arraySpec_; | |
| } | |
| static bool shouldRewriteShapeSpecListToExplicitBounds( | |
| SemanticsContext &context, const parser::ArraySpec &x) { | |
| auto &explicitShapeSpecList{ | |
| std::get<std::list<parser::ExplicitShapeSpec>>(x.u)}; | |
| if (explicitShapeSpecList.size() != 1) { | |
| return false; | |
| } | |
| auto &explicitShapeSpec{explicitShapeSpecList.front()}; | |
| const auto &upperBound{std::get<1>(explicitShapeSpec.t)}; | |
| const auto &lowerBoundOpt{std::get<0>(explicitShapeSpec.t)}; | |
| bool foundArray{false}; | |
| if (MaybeExpr analyzedExpr = | |
| AnalyzeExpr(context, parser::UnwrapRef<parser::Expr>(upperBound)); | |
| analyzedExpr && (analyzedExpr->Rank() > 0)) { | |
| foundArray = true; | |
| } | |
| if (lowerBoundOpt) { | |
| const auto &lowerBound{*lowerBoundOpt}; | |
| if (MaybeExpr analyzedExpr = | |
| AnalyzeExpr(context, parser::UnwrapRef<parser::Expr>(lowerBound)); | |
| analyzedExpr && (analyzedExpr->Rank() > 0)) { | |
| foundArray = true; | |
| } | |
| } | |
| return foundArray; | |
| } | |
| static bool shouldRewriteAssumedShapeSpecListToAssumedBounds( | |
| SemanticsContext &context, const parser::ArraySpec &x) { | |
| auto &assumedShapeSpecList{ | |
| std::get<std::list<parser::AssumedShapeSpec>>(x.u)}; | |
| if (assumedShapeSpecList.size() != 1) { | |
| return false; | |
| } | |
| auto &assumedShapeSpec{assumedShapeSpecList.front()}; | |
| const auto &lowerBoundOpt{assumedShapeSpec.v}; | |
| bool foundArray{false}; | |
| if (lowerBoundOpt) { | |
| const auto &lowerBound{*lowerBoundOpt}; | |
| if (MaybeExpr analyzedExpr = | |
| AnalyzeExpr(context, parser::UnwrapRef<parser::Expr>(lowerBound)); | |
| analyzedExpr && (analyzedExpr->Rank() > 0)) { | |
| foundArray = true; | |
| } | |
| } | |
| return foundArray; | |
| } | |
| static void rewriteShapeSpecListToExplicitBounds(const parser::ArraySpec &x) { | |
| auto &explicitShapeSpecList{std::get<std::list<parser::ExplicitShapeSpec>>( | |
| const_cast<parser::ArraySpec &>(x).u)}; | |
| auto &mutableArraySpec{const_cast<parser::ArraySpec &>(x)}; | |
| auto &mutableExplicitShapeSpec{explicitShapeSpecList.front()}; | |
| auto &mutableUpperBound{std::get<1>(mutableExplicitShapeSpec.t)}; | |
| parser::IntExpr upperIntExpr{std::move(mutableUpperBound.v.thing)}; | |
| auto &mutableLowerBound{std::get<0>(mutableExplicitShapeSpec.t)}; | |
| std::optional<parser::IntExpr> lowerIntExpr; | |
| if (mutableLowerBound) { | |
| lowerIntExpr = std::move(mutableLowerBound->v.thing); | |
| } | |
| parser::ExplicitShapeBoundsSpec boundsSpec{ | |
| std::move(lowerIntExpr), std::move(upperIntExpr)}; | |
| mutableArraySpec.u = std::move(boundsSpec); | |
| } | |
| static void rewriteAssumedShapeSpecListToAssumedBounds( | |
| const parser::ArraySpec &x) { | |
| auto &assumedShapeSpecList{std::get<std::list<parser::AssumedShapeSpec>>( | |
| const_cast<parser::ArraySpec &>(x).u)}; | |
| auto &mutableArraySpec{const_cast<parser::ArraySpec &>(x)}; | |
| auto &mutableAssumedShapeSpec{assumedShapeSpecList.front()}; | |
| auto &lowerBoundOpt{mutableAssumedShapeSpec.v}; | |
| CHECK(lowerBoundOpt.has_value()); | |
| parser::IntExpr lowerIntExpr{std::move(lowerBoundOpt->v.thing)}; | |
| parser::AssumedShapeBoundsSpec boundsSpec{std::move(lowerIntExpr)}; | |
| mutableArraySpec.u = std::move(boundsSpec); | |
| } | |
| ArraySpec ArraySpecAnalyzer::Analyze(const parser::ArraySpec &x) { | |
| // This node is rewritten manually here, as opposed to using RewriteParseTree, | |
| // because RewriteParseTree is called after ResolveNames in | |
| // PerformStatementSemantics, at which point we would have already | |
| // aborted due to semantic errors before getting a chance to rewrite. | |
| if (std::get_if<std::list<parser::ExplicitShapeSpec>>(&x.u) && | |
| shouldRewriteShapeSpecListToExplicitBounds(context_, x)) { | |
| rewriteShapeSpecListToExplicitBounds(x); | |
| } | |
| if (std::get_if<std::list<parser::AssumedShapeSpec>>(&x.u) && | |
| shouldRewriteAssumedShapeSpecListToAssumedBounds(context_, x)) { | |
| rewriteAssumedShapeSpecListToAssumedBounds(x); | |
| } | |
| common::visit(common::visitors{ | |
| [&](const parser::AssumedSizeSpec &y) { | |
| Analyze( | |
| std::get<std::list<parser::ExplicitShapeSpec>>(y.t)); | |
| Analyze(std::get<parser::AssumedImpliedSpec>(y.t)); | |
| }, | |
| [&](const parser::ImpliedShapeSpec &y) { Analyze(y.v); }, | |
| [&](const auto &y) { Analyze(y); }, | |
| }, | |
| x.u); | |
| // arraySpec_ may legitimately be empty when an explicit-shape-bounds-spec | |
| // has a zero-size bounds array, which declares a scalar (rank 0). It may | |
| // also be empty on an error path, where a fatal diagnostic has already been | |
| // emitted and we are only continuing far enough to surface it. | |
| CHECK(context_.AnyFatalError() || !arraySpec_.empty() || | |
| zeroRankExplicitBounds_); | |
| return arraySpec_; | |
| } | |
| ArraySpec ArraySpecAnalyzer::AnalyzeDeferredShapeSpecList( | |
| const parser::DeferredShapeSpecList &x) { | |
| Analyze(x); | |
| CHECK(!arraySpec_.empty()); | |
| return arraySpec_; | |
| } | |
| ArraySpec ArraySpecAnalyzer::Analyze(const parser::CoarraySpec &x) { | |
| common::visit( | |
| common::visitors{ | |
| [&](const parser::DeferredCoshapeSpecList &y) { MakeDeferred(y.v); }, | |
| [&](const parser::ExplicitCoshapeSpec &y) { | |
| Analyze(std::get<std::list<parser::ExplicitShapeSpec>>(y.t)); | |
| MakeImplied( | |
| std::get<std::optional<parser::SpecificationExpr>>(y.t)); | |
| }, | |
| }, | |
| x.u); | |
| CHECK(!arraySpec_.empty()); | |
| return arraySpec_; | |
| } | |
| void ArraySpecAnalyzer::Analyze(const parser::AssumedShapeBoundsSpec &x) { | |
| context_.Say("Analyze overload for AssumedShapeBoundsSpec"_todo_en_US); | |
| // prevent CHECK abort in Analyze(ArraySpec), otherwise it'll abort before | |
| // printing error message | |
| arraySpec_.push_back(ShapeSpec::MakeAssumedShape(Bound{1})); | |
| } | |
| void ArraySpecAnalyzer::Analyze(const parser::AssumedShapeSpec &x) { | |
| arraySpec_.push_back(ShapeSpec::MakeAssumedShape(GetBound(x.v))); | |
| } | |
| void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeSpec &x) { | |
| MakeExplicit(std::get<std::optional<parser::SpecificationExpr>>(x.t), | |
| std::get<parser::SpecificationExpr>(x.t)); | |
| } | |
| std::optional<ArraySpecAnalyzer::ExplicitShapeBoundsResult> | |
| ArraySpecAnalyzer::CheckExplicitShapeBoundsSpec( | |
| const parser::ExplicitShapeBoundsSpec &x) { | |
| const auto &lowerBoundOpt{std::get<0>(x.t)}; | |
| const auto &upperBound{std::get<1>(x.t)}; | |
| // Analyze, validate, fold, and wrap one bound expression in a Bound. | |
| // Returns the Bound paired with the extent of the bound: std::nullopt | |
| // for a scalar bound (which broadcasts to every dimension) or, for a | |
| // rank-1 array bound, its constant extent (which may be zero). | |
| // hasError should not be set to true unless there was an error | |
| // diagnostic emitted beforehand. | |
| bool hasError{false}; | |
| auto analyzeBound = [&](const auto &parseBound, bool isUpper) | |
| -> std::optional<std::pair<Bound, std::optional<std::int64_t>>> { | |
| MaybeExpr expr{AnalyzeExpr(context_, parseBound)}; | |
| // Analyzing the parser::Integer<> wrapper enforces the INTEGER type | |
| // constraint (C885) and emits a diagnostic for a non-INTEGER bound, | |
| // returning std::nullopt. | |
| if (!expr) { | |
| hasError = true; | |
| return std::nullopt; | |
| } | |
| if (expr->Rank() > 1) { | |
| context_.Say(parser::FindSourceLocation(parseBound), | |
| "Integer array used as %s bounds in DECLARATION must be rank-1 " | |
| "but is rank-%d"_err_en_US, | |
| isUpper ? "upper" : "lower", expr->Rank()); | |
| hasError = true; | |
| return std::nullopt; | |
| } | |
| auto folded{evaluate::Fold(context_.foldingContext(), std::move(*expr))}; | |
| // The parser::Integer<> constraint enforced above guarantees an INTEGER | |
| // type, so unwrapping the folded result as an integer expression must | |
| // succeed. | |
| const auto *someInt{evaluate::UnwrapExpr<SomeIntExpr>(folded)}; | |
| CHECK(someInt); | |
| auto asSI{evaluate::Fold(context_.foldingContext(), | |
| evaluate::ConvertToType<evaluate::SubscriptInteger>( | |
| common::Clone(*someInt)))}; | |
| if (folded.Rank() == 0) { | |
| // Scalar bound: broadcasts to every dimension. | |
| return std::make_pair(Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, | |
| std::optional<std::int64_t>{}); | |
| } | |
| // Rank-1: must have constant extent. | |
| auto extents{ | |
| evaluate::GetConstantExtents(context_.foldingContext(), folded)}; | |
| if (!extents) { | |
| context_.Say(parser::FindSourceLocation(parseBound), | |
| "Rank-1 integer array used as %s bounds in DECLARATION must " | |
| "have constant size"_err_en_US, | |
| isUpper ? "upper" : "lower"); | |
| hasError = true; | |
| return std::nullopt; | |
| } | |
| return std::make_pair(Bound{MaybeSubscriptIntExpr{std::move(asSI)}}, | |
| std::optional<std::int64_t>{(*extents)[0]}); | |
| }; | |
| // Upper bound (required) | |
| auto ubResult{analyzeBound(upperBound, /*isUpper=*/true)}; | |
| // Lower bound (optional) | |
| std::optional<std::pair<Bound, std::optional<std::int64_t>>> lbResult; | |
| if (lowerBoundOpt) { | |
| lbResult = analyzeBound(*lowerBoundOpt, /*isUpper=*/false); | |
| } | |
| if (hasError) { | |
| return std::nullopt; | |
| } | |
| // A bound is rank-1 (array-valued) iff it has a concrete extent; a scalar | |
| // bound (or an omitted lower bound) has none and merely broadcasts. | |
| std::optional<std::int64_t> ubExtent{ubResult->second}; | |
| std::optional<std::int64_t> lbExtent; | |
| if (lbResult) { | |
| lbExtent = lbResult->second; | |
| } | |
| bool ubIsRank1{ubExtent.has_value()}; | |
| bool lbIsRank1{lbExtent.has_value()}; | |
| // C832: when both bounds are rank-1 arrays, they must have the same size. | |
| // This must include the case where one of the sizes is zero. | |
| if (ubIsRank1 && lbIsRank1 && *ubExtent != *lbExtent) { | |
| context_.Say(parser::FindSourceLocation(x), | |
| "DECLARATION bounds integer rank-1 arrays must have the same size; " | |
| "lower bounds has %jd elements, upper bounds has %jd elements"_err_en_US, | |
| *lbExtent, *ubExtent); | |
| return std::nullopt; | |
| } | |
| // The rank of the entity is the size of whichever bound is the rank-1 | |
| // array (they are equal when both are). A zero-size array yields rank 0, | |
| // i.e. the entity is scalar. | |
| std::int64_t numDims{ubIsRank1 ? *ubExtent : (lbIsRank1 ? *lbExtent : 0)}; | |
| // numDims is the rank determined from the bounds array(s) (already validated | |
| // above to agree in size when both are rank-1). Reject a rank above the | |
| // maximum here, in signed 64-bit arithmetic, before numDims is narrowed to | |
| // int and used to size the ArraySpec -- this avoids the signed-int overflow | |
| // and avoids building an enormous ArraySpec for an already-invalid | |
| // declaration. | |
| if (numDims > common::maxRank) { | |
| context_.Say(parser::FindSourceLocation(x), | |
| "DECLARATION rank-1 integer array bound(s) imply rank %jd, which is " | |
| "greater than the maximum " | |
| "supported rank %d"_err_en_US, | |
| static_cast<std::intmax_t>(numDims), common::maxRank); | |
| hasError = true; | |
| return std::nullopt; | |
| } | |
| std::optional<Bound> lb; | |
| if (lbResult) { | |
| lb.emplace(std::move(lbResult->first)); | |
| } | |
| return ExplicitShapeBoundsResult{ | |
| std::move(ubResult->first), std::move(lb), numDims}; | |
| } | |
| void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) { | |
| auto result{CheckExplicitShapeBoundsSpec(x)}; | |
| // Every path that yields no result has already emitted a fatal diagnostic. | |
| // Leave arraySpec_ empty and return; the CHECK in Analyze(ArraySpec) permits | |
| // an empty spec once a fatal error has been recorded, so the real (user) | |
| // error messages surface instead of an internal-error abort. | |
| if (!result) { | |
| CHECK(context_.AnyFatalError()); | |
| return; | |
| } | |
| // For rank-1 bounds, emit N ShapeSpecs each wrapping a scalar | |
| // RankOneBoundElement that extracts element [dim] from the rank-1 | |
| // expression. This makes all downstream consumers see scalar bounds. | |
| int numDims = static_cast<int>(result->numDims); | |
| if (numDims == 0) { | |
| // A zero-size bounds array declares a scalar (rank 0); leave arraySpec_ | |
| // empty and record that the empty result is intentional. The bounds are | |
| // not part of the shape, but they are still specification expressions; | |
| // stash them so declaration checking validates them once the scope is | |
| // fully resolved (see ObjectEntityDetails::droppedBoundsToCheck()). | |
| zeroRankExplicitBounds_ = true; | |
| droppedBoundsToCheck_.push_back(std::move(result->ubound)); | |
| if (result->lbound) { | |
| droppedBoundsToCheck_.push_back(std::move(*result->lbound)); | |
| } | |
| return; | |
| } | |
| for (int dim = 0; dim < numDims; ++dim) { | |
| // Upper bound | |
| MaybeSubscriptIntExpr ubExpr; | |
| if (auto &ubOrig = result->ubound.GetExplicit()) { | |
| if (ubOrig->Rank() > 0) { | |
| ubExpr = SubscriptIntExpr{ | |
| evaluate::RankOneBoundElement{common::Clone(*ubOrig), dim}}; | |
| } else { | |
| ubExpr = common::Clone(*ubOrig); | |
| } | |
| } | |
| // Lower bound | |
| MaybeSubscriptIntExpr lbExpr; | |
| if (result->lbound) { | |
| if (auto &lbOrig = result->lbound->GetExplicit()) { | |
| if (lbOrig->Rank() > 0) { | |
| lbExpr = SubscriptIntExpr{ | |
| evaluate::RankOneBoundElement{common::Clone(*lbOrig), dim}}; | |
| } else { | |
| lbExpr = common::Clone(*lbOrig); | |
| } | |
| } | |
| } | |
| Bound lb{lbExpr ? std::move(lbExpr) | |
| : MaybeSubscriptIntExpr{SubscriptIntExpr{1}}}; | |
| Bound ub{std::move(ubExpr)}; | |
| arraySpec_.push_back(ShapeSpec::MakeExplicit(std::move(lb), std::move(ub))); | |
| } | |
| } | |
| 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<parser::SpecificationExpr> &lb, | |
| const parser::SpecificationExpr &ub) { | |
| arraySpec_.push_back(ShapeSpec::MakeExplicit(GetBound(lb), GetBound(ub))); | |
| } | |
| void ArraySpecAnalyzer::MakeImplied( | |
| const std::optional<parser::SpecificationExpr> &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<parser::SpecificationExpr> &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<SomeIntExpr>(*maybeExpr)}) { | |
| expr = evaluate::Fold(context_.foldingContext(), | |
| evaluate::ConvertToType<evaluate::SubscriptInteger>( | |
| std::move(*intExpr))); | |
| } | |
| } | |
| return Bound{std::move(expr)}; | |
| } | |
| // If src is SAVE (explicitly or implicitly), | |
| // set SAVE attribute on all members of dst. | |
| static void PropagateSaveAttr( | |
| const EquivalenceObject &src, EquivalenceSet &dst) { | |
| if (IsSaved(src.symbol)) { | |
| for (auto &obj : dst) { | |
| if (!obj.symbol.attrs().test(Attr::SAVE)) { | |
| obj.symbol.attrs().set(Attr::SAVE); | |
| // If the other equivalenced symbol itself is not SAVE, | |
| // then adding SAVE here implies that it has to be implicit. | |
| obj.symbol.implicitAttrs().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)) { | |
| if (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()) { | |
| if (const ArraySpec * shape{symbol->GetShape()}; | |
| shape && shape->IsExplicitShape()) { | |
| // record a whole array as its first element | |
| for (const ShapeSpec &spec : *shape) { | |
| if (auto lbound{spec.lbound().GetExplicit()}) { | |
| if (auto lbValue{evaluate::ToInt64(*lbound)}) { | |
| subscripts.push_back(*lbValue); | |
| continue; | |
| } | |
| } | |
| subscripts.clear(); // error recovery | |
| break; | |
| } | |
| } | |
| } | |
| 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<std::size_t> 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 or warning 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<common::LanguageFeature> feature; | |
| std::optional<parser::MessageFixedText> msg; | |
| const DeclTypeSpec *type1{sym1.GetType()}; | |
| const DeclTypeSpec *type2{sym2.GetType()}; | |
| bool isDefaultNum1{IsDefaultNumericSequenceType(type1)}; | |
| bool isAnyNum1{IsAnyNumericSequenceType(type1)}; | |
| bool isDefaultNum2{IsDefaultNumericSequenceType(type2)}; | |
| bool isAnyNum2{IsAnyNumericSequenceType(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 ((isDefaultNum1 && isDefaultNum2) || (isChar1 && isChar2)) { | |
| // ok & standard conforming | |
| } else if (!(isAnyNum1 || isChar1) && | |
| !(isAnyNum2 || isChar2)) { // C8110 - C8113 | |
| if (AreTkCompatibleTypes(type1, type2)) { | |
| msg = | |
| "nonstandard: Equivalence set contains '%s' and '%s' with same type that is neither numeric nor character sequence type"_port_en_US; | |
| feature = LanguageFeature::EquivalenceSameNonSequence; | |
| } else { | |
| msg = "Equivalence set cannot contain '%s' and '%s' with distinct types " | |
| "that are not both numeric or character sequence types"_err_en_US; | |
| } | |
| } else if (isAnyNum1) { | |
| if (isChar2) { | |
| msg = | |
| "nonstandard: Equivalence set contains '%s' that is numeric sequence type and '%s' that is character"_port_en_US; | |
| feature = LanguageFeature::EquivalenceNumericWithCharacter; | |
| } else if (isAnyNum2) { | |
| if (isDefaultNum1) { | |
| msg = | |
| "nonstandard: Equivalence set contains '%s' that is a default " | |
| "numeric sequence type and '%s' that is numeric with non-default kind"_port_en_US; | |
| } else if (!isDefaultNum2) { | |
| msg = "nonstandard: Equivalence set contains '%s' and '%s' that are " | |
| "numeric sequence types with non-default kinds"_port_en_US; | |
| } | |
| feature = LanguageFeature::EquivalenceNonDefaultNumeric; | |
| } | |
| } | |
| if (msg) { | |
| if (feature) { | |
| context_.Warn( | |
| *feature, source, std::move(*msg), sym1.name(), sym2.name()); | |
| } else { | |
| 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 common::visit( | |
| common::visitors{ | |
| [&](const parser::DataRef &x) { | |
| return CheckDataRef(designator.source, x); | |
| }, | |
| [&](const parser::Substring &x) { | |
| const auto &dataRef{std::get<parser::DataRef>(x.t)}; | |
| const auto &range{std::get<parser::SubstringRange>(x.t)}; | |
| bool ok{CheckDataRef(designator.source, dataRef)}; | |
| if (const auto &lb{std::get<0>(range.t)}) { | |
| ok &= CheckSubstringBound( | |
| parser::UnwrapRef<parser::Expr>(lb), true); | |
| } else { | |
| currObject_.substringStart = 1; | |
| } | |
| if (const auto &ub{std::get<1>(range.t)}) { | |
| ok &= CheckSubstringBound( | |
| parser::UnwrapRef<parser::Expr>(ub), false); | |
| } | |
| return ok; | |
| }, | |
| }, | |
| designator.u); | |
| } | |
| bool EquivalenceSets::CheckDataRef( | |
| const parser::CharBlock &source, const parser::DataRef &x) { | |
| return common::visit( | |
| common::visitors{ | |
| [&](const parser::Name &name) { return CheckObject(name); }, | |
| [&](const common::Indirection<parser::StructureComponent> &) { | |
| context_.Say(source, // C8107 | |
| "Derived type component '%s' is not allowed in an equivalence set"_err_en_US, | |
| source); | |
| return false; | |
| }, | |
| [&](const common::Indirection<parser::ArrayElement> &elem) { | |
| bool ok{CheckDataRef(source, elem.value().Base())}; | |
| for (const auto &subscript : elem.value().Subscripts()) { | |
| ok &= common::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( | |
| parser::UnwrapRef<parser::Expr>(y)); | |
| }, | |
| }, | |
| subscript.u); | |
| } | |
| return ok; | |
| }, | |
| [&](const common::Indirection<parser::CoindexedNamedObject> &) { | |
| context_.Say(source, // C924 (R872) | |
| "Coindexed object '%s' is not allowed in an equivalence set"_err_en_US, | |
| source); | |
| return false; | |
| }, | |
| }, | |
| x.u); | |
| } | |
| bool EquivalenceSets::CheckObject(const parser::Name &name) { | |
| currObject_.symbol = name.symbol; | |
| return currObject_.symbol != nullptr; | |
| } | |
| 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())}) { | |
| switch (type.category()) { | |
| case TypeCategory::Integer: | |
| case TypeCategory::Logical: | |
| return *kind == context_.GetDefaultKind(TypeCategory::Integer); | |
| case TypeCategory::Real: | |
| case TypeCategory::Complex: | |
| return *kind == context_.GetDefaultKind(TypeCategory::Real) || | |
| *kind == context_.doublePrecisionKind(); | |
| default: | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| bool EquivalenceSets::IsDefaultNumericSequenceType(const DeclTypeSpec *type) { | |
| return IsSequenceType(type, [&](const IntrinsicTypeSpec &type) { | |
| return IsDefaultKindNumericType(type); | |
| }); | |
| } | |
| bool EquivalenceSets::IsAnyNumericSequenceType(const DeclTypeSpec *type) { | |
| return IsSequenceType(type, [&](const IntrinsicTypeSpec &type) { | |
| return type.category() == TypeCategory::Logical || | |
| common::IsNumericTypeCategory(type.category()); | |
| }); | |
| } | |
| // Is type an intrinsic type that satisfies predicate or a sequence type | |
| // whose components do. | |
| bool EquivalenceSets::IsSequenceType(const DeclTypeSpec *type, | |
| std::function<bool(const IntrinsicTypeSpec &)> 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; | |
| } | |
| } | |
| // MapSubprogramToNewSymbols() relies on the following recursive symbol/scope | |
| // copying infrastructure to duplicate an interface's symbols and map all | |
| // of the symbol references in their contained expressions and interfaces | |
| // to the new symbols. | |
| struct SymbolAndTypeMappings { | |
| std::map<const Symbol *, const Symbol *> symbolMap; | |
| std::map<const DeclTypeSpec *, const DeclTypeSpec *> typeMap; | |
| }; | |
| class SymbolMapper : public evaluate::AnyTraverse<SymbolMapper, bool> { | |
| public: | |
| using Base = evaluate::AnyTraverse<SymbolMapper, bool>; | |
| SymbolMapper(Scope &scope, SymbolAndTypeMappings &map) | |
| : Base{*this}, scope_{scope}, map_{map} {} | |
| using Base::operator(); | |
| bool operator()(const SymbolRef &ref) { | |
| if (const Symbol *mapped{MapSymbol(*ref)}) { | |
| const_cast<SymbolRef &>(ref) = *mapped; | |
| } else if (ref->has<UseDetails>()) { | |
| CopySymbol(&*ref); | |
| } | |
| return false; | |
| } | |
| bool operator()(const Symbol &x) { | |
| if (MapSymbol(x)) { | |
| DIE("SymbolMapper hit symbol outside SymbolRef"); | |
| } | |
| return false; | |
| } | |
| void MapSymbolExprs(Symbol &); | |
| Symbol *CopySymbol(const Symbol *); | |
| private: | |
| void MapParamValue(ParamValue ¶m) { (*this)(param.GetExplicit()); } | |
| void MapBound(Bound &bound) { (*this)(bound.GetExplicit()); } | |
| void MapShapeSpec(ShapeSpec &spec) { | |
| MapBound(spec.lbound()); | |
| MapBound(spec.ubound()); | |
| } | |
| const Symbol *MapSymbol(const Symbol &) const; | |
| const Symbol *MapSymbol(const Symbol *) const; | |
| const DeclTypeSpec *MapType(const DeclTypeSpec &); | |
| const DeclTypeSpec *MapType(const DeclTypeSpec *); | |
| const Symbol *MapInterface(const Symbol *); | |
| Scope &scope_; | |
| SymbolAndTypeMappings &map_; | |
| }; | |
| Symbol *SymbolMapper::CopySymbol(const Symbol *symbol) { | |
| if (symbol) { | |
| if (auto *subp{symbol->detailsIf<SubprogramDetails>()}) { | |
| if (subp->isInterface()) { | |
| if (auto pair{scope_.try_emplace(symbol->name(), symbol->attrs())}; | |
| pair.second) { | |
| Symbol ©{*pair.first->second}; | |
| map_.symbolMap[symbol] = © | |
| copy.set(symbol->test(Symbol::Flag::Subroutine) | |
| ? Symbol::Flag::Subroutine | |
| : Symbol::Flag::Function); | |
| Scope &newScope{scope_.MakeScope(Scope::Kind::Subprogram, ©)}; | |
| copy.set_scope(&newScope); | |
| copy.set_details(SubprogramDetails{}); | |
| auto &newSubp{copy.get<SubprogramDetails>()}; | |
| newSubp.set_isInterface(true); | |
| newSubp.set_isDummy(subp->isDummy()); | |
| newSubp.set_defaultIgnoreTKR(subp->defaultIgnoreTKR()); | |
| MapSubprogramToNewSymbols(*symbol, copy, newScope, &map_); | |
| return © | |
| } | |
| } | |
| } else if (Symbol * copy{scope_.CopySymbol(*symbol)}) { | |
| map_.symbolMap[symbol] = copy; | |
| return copy; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void SymbolMapper::MapSymbolExprs(Symbol &symbol) { | |
| common::visit( | |
| common::visitors{[&](ObjectEntityDetails &object) { | |
| if (const DeclTypeSpec * type{object.type()}) { | |
| if (const DeclTypeSpec * newType{MapType(*type)}) { | |
| object.ReplaceType(*newType); | |
| } | |
| } | |
| for (ShapeSpec &spec : object.shape()) { | |
| MapShapeSpec(spec); | |
| } | |
| for (ShapeSpec &spec : object.coshape()) { | |
| MapShapeSpec(spec); | |
| } | |
| }, | |
| [&](ProcEntityDetails &proc) { | |
| if (const Symbol * | |
| mappedSymbol{MapInterface(proc.rawProcInterface())}) { | |
| proc.set_procInterfaces( | |
| *mappedSymbol, BypassGeneric(mappedSymbol->GetUltimate())); | |
| } else if (const DeclTypeSpec * mappedType{MapType(proc.type())}) { | |
| if (proc.type()) { | |
| CHECK(*proc.type() == *mappedType); | |
| } else { | |
| proc.set_type(*mappedType); | |
| } | |
| } | |
| if (proc.init()) { | |
| if (const Symbol * mapped{MapSymbol(*proc.init())}) { | |
| proc.set_init(*mapped); | |
| } | |
| } | |
| }, | |
| [&](const HostAssocDetails &hostAssoc) { | |
| if (const Symbol * mapped{MapSymbol(hostAssoc.symbol())}) { | |
| symbol.set_details(HostAssocDetails{*mapped}); | |
| } | |
| }, | |
| [](const auto &) {}}, | |
| symbol.details()); | |
| } | |
| const Symbol *SymbolMapper::MapSymbol(const Symbol &symbol) const { | |
| if (auto iter{map_.symbolMap.find(&symbol)}; iter != map_.symbolMap.end()) { | |
| return iter->second; | |
| } | |
| return nullptr; | |
| } | |
| const Symbol *SymbolMapper::MapSymbol(const Symbol *symbol) const { | |
| return symbol ? MapSymbol(*symbol) : nullptr; | |
| } | |
| const DeclTypeSpec *SymbolMapper::MapType(const DeclTypeSpec &type) { | |
| if (auto iter{map_.typeMap.find(&type)}; iter != map_.typeMap.end()) { | |
| return iter->second; | |
| } | |
| const DeclTypeSpec *newType{nullptr}; | |
| if (type.category() == DeclTypeSpec::Category::Character) { | |
| const CharacterTypeSpec &charType{type.characterTypeSpec()}; | |
| if (charType.length().GetExplicit()) { | |
| ParamValue newLen{charType.length()}; | |
| (*this)(newLen.GetExplicit()); | |
| newType = &scope_.MakeCharacterType( | |
| std::move(newLen), KindExpr{charType.kind()}); | |
| } | |
| } else if (const DerivedTypeSpec *derived{type.AsDerived()}) { | |
| if (!derived->parameters().empty()) { | |
| DerivedTypeSpec newDerived{derived->name(), derived->typeSymbol()}; | |
| newDerived.CookParameters(scope_.context().foldingContext()); | |
| for (const auto &[paramName, paramValue] : derived->parameters()) { | |
| ParamValue newParamValue{paramValue}; | |
| MapParamValue(newParamValue); | |
| newDerived.AddParamValue(paramName, std::move(newParamValue)); | |
| } | |
| // Scope::InstantiateDerivedTypes() instantiates it later. | |
| newType = &scope_.MakeDerivedType(type.category(), std::move(newDerived)); | |
| } | |
| } | |
| if (newType) { | |
| map_.typeMap[&type] = newType; | |
| } | |
| return newType; | |
| } | |
| const DeclTypeSpec *SymbolMapper::MapType(const DeclTypeSpec *type) { | |
| return type ? MapType(*type) : nullptr; | |
| } | |
| const Symbol *SymbolMapper::MapInterface(const Symbol *interface) { | |
| if (const Symbol *mapped{MapSymbol(interface)}) { | |
| return mapped; | |
| } | |
| if (interface) { | |
| if (&interface->owner() != &scope_) { | |
| return interface; | |
| } else if (const auto *subp{interface->detailsIf<SubprogramDetails>()}; | |
| subp && subp->isInterface()) { | |
| return CopySymbol(interface); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void MapSubprogramToNewSymbols(const Symbol &oldSymbol, Symbol &newSymbol, | |
| Scope &newScope, SymbolAndTypeMappings *mappings) { | |
| SymbolAndTypeMappings newMappings; | |
| if (!mappings) { | |
| mappings = &newMappings; | |
| } | |
| mappings->symbolMap[&oldSymbol] = &newSymbol; | |
| const auto &oldDetails{oldSymbol.get<SubprogramDetails>()}; | |
| auto &newDetails{newSymbol.get<SubprogramDetails>()}; | |
| SymbolMapper mapper{newScope, *mappings}; | |
| for (const Symbol *dummyArg : oldDetails.dummyArgs()) { | |
| if (!dummyArg) { | |
| newDetails.add_alternateReturn(); | |
| } else if (Symbol * copy{mapper.CopySymbol(dummyArg)}) { | |
| copy->set(Symbol::Flag::Implicit, false); | |
| newDetails.add_dummyArg(*copy); | |
| mappings->symbolMap[dummyArg] = copy; | |
| } | |
| } | |
| if (oldDetails.isFunction()) { | |
| newScope.erase(newSymbol.name()); | |
| const Symbol &result{oldDetails.result()}; | |
| if (Symbol * copy{mapper.CopySymbol(&result)}) { | |
| newDetails.set_result(*copy); | |
| mappings->symbolMap[&result] = copy; | |
| } | |
| } | |
| for (auto &[_, ref] : newScope) { | |
| mapper.MapSymbolExprs(*ref); | |
| } | |
| newScope.InstantiateDerivedTypes(); | |
| } | |
| std::string GetReductionFortranId(const parser::CharBlock &mangledName) { | |
| llvm::StringRef name{mangledName.begin(), mangledName.size()}; | |
| if (!name.starts_with("op.")) { | |
| return name.str(); | |
| } | |
| llvm::StringRef suffix{name.drop_front(3)}; | |
| // Intrinsic arithmetic operators: op.+ -> operator(+) | |
| if (suffix == "+" || suffix == "-" || suffix == "*") { | |
| return ("operator(" + suffix + ")").str(); | |
| } | |
| // Intrinsic logical operators (mangled uppercase, scope uses lowercase) | |
| if (suffix == "AND") { | |
| return "operator(.and.)"; | |
| } | |
| if (suffix == "OR") { | |
| return "operator(.or.)"; | |
| } | |
| if (suffix == "EQV") { | |
| return "operator(.eqv.)"; | |
| } | |
| if (suffix == "NEQV") { | |
| return "operator(.neqv.)"; | |
| } | |
| // Defined operators: op.combine. -> .combine. | |
| // MangleDefinedOperator prepends "op" to the operator name (e.g., | |
| // ".combine.") so after stripping "op.", the suffix ends with '.' for defined | |
| // operators. | |
| if (!suffix.empty() && suffix.back() == '.') { | |
| return ("." + suffix).str(); | |
| } | |
| // Named functions: op.max -> max | |
| return suffix.str(); | |
| } | |
| } // namespace Fortran::semantics |