diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h index 7daeeba507f6..75cf4fe53664 100644 --- a/flang/include/flang/Semantics/expression.h +++ b/flang/include/flang/Semantics/expression.h @@ -1,480 +1,488 @@ //===-- include/flang/Semantics/expression.h --------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #ifndef FORTRAN_SEMANTICS_EXPRESSION_H_ #define FORTRAN_SEMANTICS_EXPRESSION_H_ #include "semantics.h" #include "flang/Common/Fortran.h" #include "flang/Common/indirection.h" +#include "flang/Common/restorer.h" #include "flang/Evaluate/characteristics.h" #include "flang/Evaluate/check-expression.h" #include "flang/Evaluate/expression.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-visitor.h" #include "flang/Parser/parse-tree.h" #include "flang/Parser/tools.h" #include #include #include #include using namespace Fortran::parser::literals; namespace Fortran::parser { struct SourceLocationFindingVisitor { template bool Pre(const A &x) { if constexpr (HasSource::value) { source.ExtendToCover(x.source); return false; } else { return true; } } template void Post(const A &) {} void Post(const CharBlock &at) { source.ExtendToCover(at); } CharBlock source; }; template CharBlock FindSourceLocation(const A &x) { SourceLocationFindingVisitor visitor; Walk(x, visitor); return visitor.source; } } // namespace Fortran::parser using namespace Fortran::parser::literals; // The expression semantic analysis code has its implementation in // namespace Fortran::evaluate, but the exposed API to it is in the // namespace Fortran::semantics (below). // // The ExpressionAnalyzer wraps a SemanticsContext reference // and implements constraint checking on expressions using the // parse tree node wrappers that mirror the grammar annotations used // in the Fortran standard (i.e., scalar-, constant-, &c.). namespace Fortran::evaluate { class IntrinsicProcTable; struct SetExprHelper { explicit SetExprHelper(GenericExprWrapper &&expr) : expr_{std::move(expr)} {} void Set(parser::TypedExpr &x) { x.Reset(new GenericExprWrapper{std::move(expr_)}, evaluate::GenericExprWrapper::Deleter); } void Set(const parser::Expr &x) { Set(x.typedExpr); } void Set(const parser::Variable &x) { Set(x.typedExpr); } void Set(const parser::DataStmtConstant &x) { Set(x.typedExpr); } template void Set(const common::Indirection &x) { Set(x.value()); } template void Set(const T &x) { if constexpr (ConstraintTrait) { Set(x.thing); } else if constexpr (WrapperTrait) { Set(x.v); } } GenericExprWrapper expr_; }; template void ResetExpr(const T &x) { SetExprHelper{GenericExprWrapper{/* error indicator */}}.Set(x); } template void SetExpr(const T &x, Expr &&expr) { SetExprHelper{GenericExprWrapper{std::move(expr)}}.Set(x); } class ExpressionAnalyzer { public: using MaybeExpr = std::optional>; explicit ExpressionAnalyzer(semantics::SemanticsContext &sc) : context_{sc} {} ExpressionAnalyzer(semantics::SemanticsContext &sc, FoldingContext &fc) : context_{sc}, foldingContext_{fc} {} ExpressionAnalyzer(ExpressionAnalyzer &) = default; semantics::SemanticsContext &context() const { return context_; } FoldingContext &GetFoldingContext() const { return foldingContext_; } parser::ContextualMessages &GetContextualMessages() { return foldingContext_.messages(); } template parser::Message *Say(A &&...args) { return GetContextualMessages().Say(std::forward(args)...); } template parser::Message *SayAt(const T &parsed, A &&...args) { return Say(parser::FindSourceLocation(parsed), std::forward(args)...); } int GetDefaultKind(common::TypeCategory); DynamicType GetDefaultKindOfType(common::TypeCategory); // Return false and emit error if these checks fail: bool CheckIntrinsicKind(TypeCategory, std::int64_t kind); bool CheckIntrinsicSize(TypeCategory, std::int64_t size); // Manage a set of active implied DO loops. bool AddImpliedDo(parser::CharBlock, int kind); void RemoveImpliedDo(parser::CharBlock); // When the argument is the name of an active implied DO index, returns // its INTEGER kind type parameter. std::optional IsImpliedDo(parser::CharBlock) const; + // Allows a whole assumed-size array to appear for the lifetime of + // the returned value. + common::Restorer AllowWholeAssumedSizeArray() { + return common::ScopedSet(isWholeAssumedSizeArrayOk_, true); + } + Expr AnalyzeKindSelector(common::TypeCategory category, const std::optional &); MaybeExpr Analyze(const parser::Expr &); MaybeExpr Analyze(const parser::Variable &); MaybeExpr Analyze(const parser::Designator &); MaybeExpr Analyze(const parser::DataStmtValue &); template MaybeExpr Analyze(const common::Indirection &x) { return Analyze(x.value()); } template MaybeExpr Analyze(const std::optional &x) { if (x) { return Analyze(*x); } else { return std::nullopt; } } // Implement constraint-checking wrappers from the Fortran grammar. template MaybeExpr Analyze(const parser::Scalar &x) { auto result{Analyze(x.thing)}; if (result) { if (int rank{result->Rank()}; rank != 0) { SayAt(x, "Must be a scalar value, but is a rank-%d array"_err_en_US, rank); ResetExpr(x); return std::nullopt; } } return result; } template MaybeExpr Analyze(const parser::Constant &x) { auto restorer{ GetFoldingContext().messages().SetLocation(FindSourceLocation(x))}; auto result{Analyze(x.thing)}; if (result) { *result = Fold(std::move(*result)); if (!IsConstantExpr(*result)) { // C886, C887, C713 SayAt(x, "Must be a constant value"_err_en_US); ResetExpr(x); return std::nullopt; } else { // Save folded expression for later use SetExpr(x, common::Clone(*result)); } } return result; } template MaybeExpr Analyze(const parser::Integer &x) { auto result{Analyze(x.thing)}; if (!EnforceTypeConstraint( parser::FindSourceLocation(x), result, TypeCategory::Integer)) { ResetExpr(x); return std::nullopt; } return result; } template MaybeExpr Analyze(const parser::Logical &x) { auto result{Analyze(x.thing)}; if (!EnforceTypeConstraint( parser::FindSourceLocation(x), result, TypeCategory::Logical)) { ResetExpr(x); return std::nullopt; } return result; } template MaybeExpr Analyze(const parser::DefaultChar &x) { auto result{Analyze(x.thing)}; if (!EnforceTypeConstraint(parser::FindSourceLocation(x), result, TypeCategory::Character, true /* default kind */)) { ResetExpr(x); return std::nullopt; } return result; } MaybeExpr Analyze(const parser::Name &); MaybeExpr Analyze(const parser::DataRef &dr) { return Analyze(dr); } MaybeExpr Analyze(const parser::StructureComponent &); MaybeExpr Analyze(const parser::SignedIntLiteralConstant &); MaybeExpr Analyze(const parser::SignedRealLiteralConstant &); MaybeExpr Analyze(const parser::SignedComplexLiteralConstant &); MaybeExpr Analyze(const parser::StructureConstructor &); MaybeExpr Analyze(const parser::InitialDataTarget &); void Analyze(const parser::CallStmt &); const Assignment *Analyze(const parser::AssignmentStmt &); const Assignment *Analyze(const parser::PointerAssignmentStmt &); protected: int IntegerTypeSpecKind(const parser::IntegerTypeSpec &); private: MaybeExpr Analyze(const parser::IntLiteralConstant &); MaybeExpr Analyze(const parser::RealLiteralConstant &); MaybeExpr Analyze(const parser::ComplexPart &); MaybeExpr Analyze(const parser::ComplexLiteralConstant &); MaybeExpr Analyze(const parser::LogicalLiteralConstant &); MaybeExpr Analyze(const parser::CharLiteralConstant &); MaybeExpr Analyze(const parser::HollerithLiteralConstant &); MaybeExpr Analyze(const parser::BOZLiteralConstant &); MaybeExpr Analyze(const parser::NamedConstant &); MaybeExpr Analyze(const parser::NullInit &); MaybeExpr Analyze(const parser::DataStmtConstant &); MaybeExpr Analyze(const parser::Substring &); MaybeExpr Analyze(const parser::ArrayElement &); MaybeExpr Analyze(const parser::CoindexedNamedObject &); MaybeExpr Analyze(const parser::CharLiteralConstantSubstring &); MaybeExpr Analyze(const parser::ArrayConstructor &); MaybeExpr Analyze(const parser::FunctionReference &, std::optional * = nullptr); MaybeExpr Analyze(const parser::Expr::Parentheses &); MaybeExpr Analyze(const parser::Expr::UnaryPlus &); MaybeExpr Analyze(const parser::Expr::Negate &); MaybeExpr Analyze(const parser::Expr::NOT &); MaybeExpr Analyze(const parser::Expr::PercentLoc &); MaybeExpr Analyze(const parser::Expr::DefinedUnary &); MaybeExpr Analyze(const parser::Expr::Power &); MaybeExpr Analyze(const parser::Expr::Multiply &); MaybeExpr Analyze(const parser::Expr::Divide &); MaybeExpr Analyze(const parser::Expr::Add &); MaybeExpr Analyze(const parser::Expr::Subtract &); MaybeExpr Analyze(const parser::Expr::ComplexConstructor &); MaybeExpr Analyze(const parser::Expr::Concat &); MaybeExpr Analyze(const parser::Expr::LT &); MaybeExpr Analyze(const parser::Expr::LE &); MaybeExpr Analyze(const parser::Expr::EQ &); MaybeExpr Analyze(const parser::Expr::NE &); MaybeExpr Analyze(const parser::Expr::GE &); MaybeExpr Analyze(const parser::Expr::GT &); MaybeExpr Analyze(const parser::Expr::AND &); MaybeExpr Analyze(const parser::Expr::OR &); MaybeExpr Analyze(const parser::Expr::EQV &); MaybeExpr Analyze(const parser::Expr::NEQV &); MaybeExpr Analyze(const parser::Expr::DefinedBinary &); template MaybeExpr Analyze(const A &x) { return Analyze(x.u); // default case } template MaybeExpr Analyze(const std::variant &u) { return std::visit( [&](const auto &x) { using Ty = std::decay_t; // Function references might turn out to be misparsed structure // constructors; we have to try generic procedure resolution // first to be sure. if constexpr (common::IsTypeInList) { std::optional ctor; MaybeExpr result; if constexpr (std::is_same_v>) { result = Analyze(x.value(), &ctor); } else if constexpr (std::is_same_v) { result = Analyze(x, &ctor); } else { return Analyze(x); } if (ctor) { // A misparsed function reference is really a structure // constructor. Repair the parse tree in situ. const_cast &>(u) = std::move(*ctor); } return result; } return Analyze(x); }, u); } // Analysis subroutines int AnalyzeKindParam( const std::optional &, int defaultKind); template MaybeExpr ExprOrVariable(const PARSED &); template MaybeExpr IntLiteralConstant(const PARSED &); MaybeExpr AnalyzeString(std::string &&, int kind); std::optional> AsSubscript(MaybeExpr &&); std::optional> TripletPart( const std::optional &); std::optional AnalyzeSectionSubscript( const parser::SectionSubscript &); std::vector AnalyzeSectionSubscripts( const std::list &); MaybeExpr Designate(DataRef &&); MaybeExpr CompleteSubscripts(ArrayRef &&); MaybeExpr ApplySubscripts(DataRef &&, std::vector &&); MaybeExpr TopLevelChecks(DataRef &&); std::optional> GetSubstringBound( const std::optional &); MaybeExpr AnalyzeDefinedOp(const parser::Name &, ActualArguments &&); struct CalleeAndArguments { // A non-component function reference may constitute a misparsed // structure constructor, in which case its derived type's Symbol // will appear here. std::variant u; ActualArguments arguments; }; std::optional AnalyzeProcedureComponentRef( const parser::ProcComponentRef &, ActualArguments &&); std::optional CheckCall( parser::CharBlock, const ProcedureDesignator &, ActualArguments &); using AdjustActuals = std::optional>; bool ResolveForward(const Symbol &); const Symbol *ResolveGeneric(const Symbol &, const ActualArguments &, const AdjustActuals &, bool mightBeStructureConstructor = false); void EmitGenericResolutionError(const Symbol &); std::optional GetCalleeAndArguments(const parser::Name &, ActualArguments &&, bool isSubroutine = false, bool mightBeStructureConstructor = false); std::optional GetCalleeAndArguments( const parser::ProcedureDesignator &, ActualArguments &&, bool isSubroutine, bool mightBeStructureConstructor = false); void CheckForBadRecursion(parser::CharBlock, const semantics::Symbol &); bool EnforceTypeConstraint(parser::CharBlock, const MaybeExpr &, TypeCategory, bool defaultKind = false); MaybeExpr MakeFunctionRef( parser::CharBlock, ProcedureDesignator &&, ActualArguments &&); MaybeExpr MakeFunctionRef(parser::CharBlock intrinsic, ActualArguments &&); template T Fold(T &&expr) { return evaluate::Fold(foldingContext_, std::move(expr)); } semantics::SemanticsContext &context_; FoldingContext &foldingContext_{context_.foldingContext()}; std::map impliedDos_; // values are INTEGER kinds bool fatalErrors_{false}; + bool isWholeAssumedSizeArrayOk_{false}; friend class ArgumentAnalyzer; }; inline bool AreConformable(int leftRank, int rightRank) { return leftRank == 0 || rightRank == 0 || leftRank == rightRank; } template bool AreConformable(const L &left, const R &right) { return AreConformable(left.Rank(), right.Rank()); } template void ConformabilityCheck( parser::ContextualMessages &context, const L &left, const R &right) { if (!AreConformable(left, right)) { context.Say("left operand has rank %d, right operand has rank %d"_err_en_US, left.Rank(), right.Rank()); } } } // namespace Fortran::evaluate namespace Fortran::semantics { // Semantic analysis of one expression, variable, or designator. template std::optional> AnalyzeExpr( SemanticsContext &context, const A &expr) { return evaluate::ExpressionAnalyzer{context}.Analyze(expr); } // Semantic analysis of an intrinsic type's KIND parameter expression. evaluate::Expr AnalyzeKindSelector( SemanticsContext &, common::TypeCategory, const std::optional &); void AnalyzeCallStmt(SemanticsContext &, const parser::CallStmt &); const evaluate::Assignment *AnalyzeAssignmentStmt( SemanticsContext &, const parser::AssignmentStmt &); const evaluate::Assignment *AnalyzePointerAssignmentStmt( SemanticsContext &, const parser::PointerAssignmentStmt &); // Semantic analysis of all expressions in a parse tree, which becomes // decorated with typed representations for top-level expressions. class ExprChecker { public: explicit ExprChecker(SemanticsContext &); template bool Pre(const A &) { return true; } template void Post(const A &) {} bool Walk(const parser::Program &); bool Pre(const parser::Expr &x) { exprAnalyzer_.Analyze(x); return false; } bool Pre(const parser::Variable &x) { exprAnalyzer_.Analyze(x); return false; } bool Pre(const parser::DataStmtValue &x) { exprAnalyzer_.Analyze(x); return false; } bool Pre(const parser::DataImpliedDo &); bool Pre(const parser::CallStmt &x) { AnalyzeCallStmt(context_, x); return false; } bool Pre(const parser::AssignmentStmt &x) { AnalyzeAssignmentStmt(context_, x); return false; } bool Pre(const parser::PointerAssignmentStmt &x) { AnalyzePointerAssignmentStmt(context_, x); return false; } template bool Pre(const parser::Scalar &x) { exprAnalyzer_.Analyze(x); return false; } template bool Pre(const parser::Constant &x) { exprAnalyzer_.Analyze(x); return false; } template bool Pre(const parser::Integer &x) { exprAnalyzer_.Analyze(x); return false; } template bool Pre(const parser::Logical &x) { exprAnalyzer_.Analyze(x); return false; } template bool Pre(const parser::DefaultChar &x) { exprAnalyzer_.Analyze(x); return false; } private: SemanticsContext &context_; evaluate::ExpressionAnalyzer exprAnalyzer_{context_}; }; } // namespace Fortran::semantics #endif // FORTRAN_SEMANTICS_EXPRESSION_H_ diff --git a/flang/lib/Semantics/assignment.cpp b/flang/lib/Semantics/assignment.cpp index 0b765c72fdd7..090aae0af8cb 100644 --- a/flang/lib/Semantics/assignment.cpp +++ b/flang/lib/Semantics/assignment.cpp @@ -1,296 +1,291 @@ //===-- lib/Semantics/assignment.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 "assignment.h" #include "pointer-assignment.h" #include "flang/Common/idioms.h" #include "flang/Common/restorer.h" #include "flang/Evaluate/characteristics.h" #include "flang/Evaluate/expression.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/tools.h" #include "flang/Parser/message.h" #include "flang/Parser/parse-tree-visitor.h" #include "flang/Parser/parse-tree.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include #include #include #include using namespace Fortran::parser::literals; namespace Fortran::semantics { class AssignmentContext { public: explicit AssignmentContext(SemanticsContext &context) : context_{context} {} AssignmentContext(AssignmentContext &&) = default; AssignmentContext(const AssignmentContext &) = delete; bool operator==(const AssignmentContext &x) const { return this == &x; } template void PushWhereContext(const A &); void PopWhereContext(); void Analyze(const parser::AssignmentStmt &); void Analyze(const parser::PointerAssignmentStmt &); void Analyze(const parser::ConcurrentControl &); private: bool CheckForPureContext(const SomeExpr &lhs, const SomeExpr &rhs, parser::CharBlock rhsSource, bool isPointerAssignment); void CheckShape(parser::CharBlock, const SomeExpr *); template parser::Message *Say(parser::CharBlock at, A &&...args) { return &context_.Say(at, std::forward(args)...); } evaluate::FoldingContext &foldingContext() { return context_.foldingContext(); } SemanticsContext &context_; int whereDepth_{0}; // number of WHEREs currently nested in // shape of masks in LHS of assignments in current WHERE: std::vector> whereExtents_; }; void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) { if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) { const SomeExpr &lhs{assignment->lhs}; const SomeExpr &rhs{assignment->rhs}; auto lhsLoc{std::get(stmt.t).GetSource()}; auto rhsLoc{std::get(stmt.t).source}; - auto shape{evaluate::GetShape(foldingContext(), lhs)}; - if (shape && !shape->empty() && !shape->back().has_value()) { // C1014 - Say(lhsLoc, - "Left-hand side of assignment may not be a whole assumed-size array"_err_en_US); - } if (CheckForPureContext(lhs, rhs, rhsLoc, false)) { const Scope &scope{context_.FindScope(lhsLoc)}; if (auto whyNot{WhyNotModifiable(lhsLoc, lhs, scope, true)}) { if (auto *msg{Say(lhsLoc, "Left-hand side of assignment is not modifiable"_err_en_US)}) { // C1158 msg->Attach(*whyNot); } } } if (whereDepth_ > 0) { CheckShape(lhsLoc, &lhs); } } } void AssignmentContext::Analyze(const parser::PointerAssignmentStmt &stmt) { CHECK(whereDepth_ == 0); if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) { const SomeExpr &lhs{assignment->lhs}; const SomeExpr &rhs{assignment->rhs}; CheckForPureContext(lhs, rhs, std::get(stmt.t).source, true); auto restorer{ foldingContext().messages().SetLocation(context_.location().value())}; CheckPointerAssignment(foldingContext(), *assignment); } } // C1594 checks static bool IsPointerDummyOfPureFunction(const Symbol &x) { return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) && x.owner().symbol() && IsFunction(*x.owner().symbol()); } static const char *WhyBaseObjectIsSuspicious( const Symbol &x, const Scope &scope) { // See C1594, first paragraph. These conditions enable checks on both // left-hand and right-hand sides in various circumstances. if (IsHostAssociated(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 bool CheckDefinabilityInPureScope(parser::ContextualMessages &messages, const Symbol &lhs, const Scope &context, const Scope &pure) { if (pure.symbol()) { if (const char *why{WhyBaseObjectIsSuspicious(lhs, context)}) { evaluate::SayWithDeclaration(messages, lhs, "Pure subprogram '%s' may not define '%s' because it is %s"_err_en_US, pure.symbol()->name(), lhs.name(), why); return false; } } return true; } static std::optional GetPointerComponentDesignatorName( const SomeExpr &expr) { if (const auto *derived{ evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(expr))}) { UltimateComponentIterator ultimates{*derived}; if (auto pointer{ std::find_if(ultimates.begin(), ultimates.end(), IsPointer)}) { return pointer.BuildResultDesignatorName(); } } return std::nullopt; } // Checks C1594(5,6); false if check fails bool CheckCopyabilityInPureScope(parser::ContextualMessages &messages, const SomeExpr &expr, const Scope &scope) { if (const Symbol * base{GetFirstSymbol(expr)}) { if (const char *why{WhyBaseObjectIsSuspicious(*base, scope)}) { if (auto pointer{GetPointerComponentDesignatorName(expr)}) { evaluate::SayWithDeclaration(messages, *base, "A pure subprogram may not copy the value of '%s' because it is %s" " and has the POINTER component '%s'"_err_en_US, base->name(), why, *pointer); return false; } } } return true; } bool AssignmentContext::CheckForPureContext(const SomeExpr &lhs, const SomeExpr &rhs, parser::CharBlock source, bool isPointerAssignment) { const Scope &scope{context_.FindScope(source)}; if (const Scope * pure{FindPureProcedureContaining(scope)}) { parser::ContextualMessages messages{ context_.location().value(), &context_.messages()}; if (evaluate::ExtractCoarrayRef(lhs)) { messages.Say( "A pure subprogram may not define a coindexed object"_err_en_US); } else if (const Symbol * base{GetFirstSymbol(lhs)}) { if (const auto *assoc{base->detailsIf()}) { auto dataRef{ExtractDataRef(assoc->expr(), true)}; // ASSOCIATE(a=>x) -- check x, not a, for "a=..." base = dataRef ? &dataRef->GetFirstSymbol() : nullptr; } if (base && !CheckDefinabilityInPureScope(messages, *base, scope, *pure)) { return false; } } if (isPointerAssignment) { if (const Symbol * base{GetFirstSymbol(rhs)}) { if (const char *why{ WhyBaseObjectIsSuspicious(*base, scope)}) { // C1594(3) evaluate::SayWithDeclaration(messages, *base, "A pure subprogram may not use '%s' as the target of pointer assignment because it is %s"_err_en_US, base->name(), why); return false; } } } else if (auto type{evaluate::DynamicType::From(lhs)}) { // C1596 checks for polymorphic deallocation in a pure subprogram // due to automatic reallocation on assignment if (type->IsPolymorphic()) { context_.Say( "Deallocation of polymorphic object is not permitted in a pure subprogram"_err_en_US); return false; } if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(type)}) { if (auto bad{FindPolymorphicAllocatableNonCoarrayUltimateComponent( *derived)}) { evaluate::SayWithDeclaration(messages, *bad, "Deallocation of polymorphic non-coarray component '%s' is not permitted in a pure subprogram"_err_en_US, bad.BuildResultDesignatorName()); return false; } else { return CheckCopyabilityInPureScope(messages, rhs, scope); } } } } return true; } // 10.2.3.1(2) The masks and LHS of assignments must all have the same shape void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) { if (auto shape{evaluate::GetShape(foldingContext(), expr)}) { std::size_t size{shape->size()}; if (whereDepth_ == 0) { whereExtents_.resize(size); } else if (whereExtents_.size() != size) { Say(at, "Must have rank %zd to match prior mask or assignment of" " WHERE construct"_err_en_US, whereExtents_.size()); return; } for (std::size_t i{0}; i < size; ++i) { if (std::optional extent{evaluate::ToInt64((*shape)[i])}) { if (!whereExtents_[i]) { whereExtents_[i] = *extent; } else if (*whereExtents_[i] != *extent) { Say(at, "Dimension %d must have extent %jd to match prior mask or" " assignment of WHERE construct"_err_en_US, i + 1, *whereExtents_[i]); } } } } } template void AssignmentContext::PushWhereContext(const A &x) { const auto &expr{std::get(x.t)}; CheckShape(expr.thing.value().source, GetExpr(expr)); ++whereDepth_; } void AssignmentContext::PopWhereContext() { --whereDepth_; if (whereDepth_ == 0) { whereExtents_.clear(); } } AssignmentChecker::~AssignmentChecker() {} AssignmentChecker::AssignmentChecker(SemanticsContext &context) : context_{new AssignmentContext{context}} {} void AssignmentChecker::Enter(const parser::AssignmentStmt &x) { context_.value().Analyze(x); } void AssignmentChecker::Enter(const parser::PointerAssignmentStmt &x) { context_.value().Analyze(x); } void AssignmentChecker::Enter(const parser::WhereStmt &x) { context_.value().PushWhereContext(x); } void AssignmentChecker::Leave(const parser::WhereStmt &) { context_.value().PopWhereContext(); } void AssignmentChecker::Enter(const parser::WhereConstructStmt &x) { context_.value().PushWhereContext(x); } void AssignmentChecker::Leave(const parser::EndWhereStmt &) { context_.value().PopWhereContext(); } void AssignmentChecker::Enter(const parser::MaskedElsewhereStmt &x) { context_.value().PushWhereContext(x); } void AssignmentChecker::Leave(const parser::MaskedElsewhereStmt &) { context_.value().PopWhereContext(); } } // namespace Fortran::semantics template class Fortran::common::Indirection< Fortran::semantics::AssignmentContext>; diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp index 26702f6c48bf..9095951389f2 100644 --- a/flang/lib/Semantics/check-io.cpp +++ b/flang/lib/Semantics/check-io.cpp @@ -1,952 +1,944 @@ //===-- lib/Semantics/check-io.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 "check-io.h" #include "flang/Common/format.h" #include "flang/Parser/tools.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/tools.h" #include namespace Fortran::semantics { // TODO: C1234, C1235 -- defined I/O constraints class FormatErrorReporter { public: FormatErrorReporter(SemanticsContext &context, const parser::CharBlock &formatCharBlock, int errorAllowance = 3) : context_{context}, formatCharBlock_{formatCharBlock}, errorAllowance_{errorAllowance} {} bool Say(const common::FormatMessage &); private: SemanticsContext &context_; const parser::CharBlock &formatCharBlock_; int errorAllowance_; // initialized to maximum number of errors to report }; bool FormatErrorReporter::Say(const common::FormatMessage &msg) { if (!msg.isError && !context_.warnOnNonstandardUsage()) { return false; } parser::MessageFormattedText text{ parser::MessageFixedText(msg.text, strlen(msg.text), msg.isError), msg.arg}; if (formatCharBlock_.size()) { // The input format is a folded expression. Error markers span the full // original unfolded expression in formatCharBlock_. context_.Say(formatCharBlock_, text); } else { // The input format is a source expression. Error markers have an offset // and length relative to the beginning of formatCharBlock_. parser::CharBlock messageCharBlock{ parser::CharBlock(formatCharBlock_.begin() + msg.offset, msg.length)}; context_.Say(messageCharBlock, text); } return msg.isError && --errorAllowance_ <= 0; } void IoChecker::Enter( const parser::Statement> &stmt) { if (!stmt.label) { context_.Say("Format statement must be labeled"_err_en_US); // C1301 } const char *formatStart{static_cast( std::memchr(stmt.source.begin(), '(', stmt.source.size()))}; parser::CharBlock reporterCharBlock{formatStart, static_cast(0)}; FormatErrorReporter reporter{context_, reporterCharBlock}; auto reporterWrapper{[&](const auto &msg) { return reporter.Say(msg); }}; switch (context_.GetDefaultKind(TypeCategory::Character)) { case 1: { common::FormatValidator validator{formatStart, stmt.source.size() - (formatStart - stmt.source.begin()), reporterWrapper}; validator.Check(); break; } case 2: { // TODO: Get this to work. common::FormatValidator validator{ /*???*/ nullptr, /*???*/ 0, reporterWrapper}; validator.Check(); break; } case 4: { // TODO: Get this to work. common::FormatValidator validator{ /*???*/ nullptr, /*???*/ 0, reporterWrapper}; validator.Check(); break; } default: CRASH_NO_CASE; } } void IoChecker::Enter(const parser::ConnectSpec &spec) { // ConnectSpec context FileNameExpr if (std::get_if(&spec.u)) { SetSpecifier(IoSpecKind::File); } } void IoChecker::Enter(const parser::ConnectSpec::CharExpr &spec) { IoSpecKind specKind{}; using ParseKind = parser::ConnectSpec::CharExpr::Kind; switch (std::get(spec.t)) { case ParseKind::Access: specKind = IoSpecKind::Access; break; case ParseKind::Action: specKind = IoSpecKind::Action; break; case ParseKind::Asynchronous: specKind = IoSpecKind::Asynchronous; break; case ParseKind::Blank: specKind = IoSpecKind::Blank; break; case ParseKind::Decimal: specKind = IoSpecKind::Decimal; break; case ParseKind::Delim: specKind = IoSpecKind::Delim; break; case ParseKind::Encoding: specKind = IoSpecKind::Encoding; break; case ParseKind::Form: specKind = IoSpecKind::Form; break; case ParseKind::Pad: specKind = IoSpecKind::Pad; break; case ParseKind::Position: specKind = IoSpecKind::Position; break; case ParseKind::Round: specKind = IoSpecKind::Round; break; case ParseKind::Sign: specKind = IoSpecKind::Sign; break; case ParseKind::Carriagecontrol: specKind = IoSpecKind::Carriagecontrol; break; case ParseKind::Convert: specKind = IoSpecKind::Convert; break; case ParseKind::Dispose: specKind = IoSpecKind::Dispose; break; } SetSpecifier(specKind); if (const std::optional charConst{GetConstExpr( std::get(spec.t))}) { std::string s{parser::ToUpperCaseLetters(*charConst)}; if (specKind == IoSpecKind::Access) { flags_.set(Flag::KnownAccess); flags_.set(Flag::AccessDirect, s == "DIRECT"); flags_.set(Flag::AccessStream, s == "STREAM"); } CheckStringValue(specKind, *charConst, parser::FindSourceLocation(spec)); if (specKind == IoSpecKind::Carriagecontrol && (s == "FORTRAN" || s == "NONE")) { context_.Say(parser::FindSourceLocation(spec), "Unimplemented %s value '%s'"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind)), *charConst); } } } void IoChecker::Enter(const parser::ConnectSpec::Newunit &var) { CheckForDefinableVariable(var, "NEWUNIT"); SetSpecifier(IoSpecKind::Newunit); } void IoChecker::Enter(const parser::ConnectSpec::Recl &spec) { SetSpecifier(IoSpecKind::Recl); if (const std::optional recl{ GetConstExpr(spec)}) { if (*recl <= 0) { context_.Say(parser::FindSourceLocation(spec), "RECL value (%jd) must be positive"_err_en_US, *recl); // 12.5.6.15 } } } void IoChecker::Enter(const parser::EndLabel &) { SetSpecifier(IoSpecKind::End); } void IoChecker::Enter(const parser::EorLabel &) { SetSpecifier(IoSpecKind::Eor); } void IoChecker::Enter(const parser::ErrLabel &) { SetSpecifier(IoSpecKind::Err); } void IoChecker::Enter(const parser::FileUnitNumber &) { SetSpecifier(IoSpecKind::Unit); flags_.set(Flag::NumberUnit); } void IoChecker::Enter(const parser::Format &spec) { SetSpecifier(IoSpecKind::Fmt); flags_.set(Flag::FmtOrNml); std::visit( common::visitors{ [&](const parser::Label &) { flags_.set(Flag::LabelFmt); }, [&](const parser::Star &) { flags_.set(Flag::StarFmt); }, [&](const parser::Expr &format) { const SomeExpr *expr{GetExpr(format)}; if (!expr) { return; } auto type{expr->GetType()}; if (!type || (type->category() != TypeCategory::Integer && type->category() != TypeCategory::Character) || type->kind() != context_.defaultKinds().GetDefaultKind(type->category())) { context_.Say(format.source, "Format expression must be default character or integer"_err_en_US); return; } if (type->category() == TypeCategory::Integer) { flags_.set(Flag::AssignFmt); if (expr->Rank() != 0 || !IsVariable(*expr)) { context_.Say(format.source, "Assigned format label must be a scalar variable"_err_en_US); } return; } flags_.set(Flag::CharFmt); const std::optional constantFormat{ GetConstExpr(format)}; if (!constantFormat) { return; } // validate constant format -- 12.6.2.2 bool isFolded{constantFormat->size() != format.source.size() - 2}; parser::CharBlock reporterCharBlock{isFolded ? parser::CharBlock{format.source} : parser::CharBlock{format.source.begin() + 1, static_cast(0)}}; FormatErrorReporter reporter{context_, reporterCharBlock}; auto reporterWrapper{ [&](const auto &msg) { return reporter.Say(msg); }}; switch (context_.GetDefaultKind(TypeCategory::Character)) { case 1: { common::FormatValidator validator{constantFormat->c_str(), constantFormat->length(), reporterWrapper, stmt_}; validator.Check(); break; } case 2: { // TODO: Get this to work. (Maybe combine with earlier instance?) common::FormatValidator validator{ /*???*/ nullptr, /*???*/ 0, reporterWrapper, stmt_}; validator.Check(); break; } case 4: { // TODO: Get this to work. (Maybe combine with earlier instance?) common::FormatValidator validator{ /*???*/ nullptr, /*???*/ 0, reporterWrapper, stmt_}; validator.Check(); break; } default: CRASH_NO_CASE; } }, }, spec.u); } void IoChecker::Enter(const parser::IdExpr &) { SetSpecifier(IoSpecKind::Id); } void IoChecker::Enter(const parser::IdVariable &spec) { SetSpecifier(IoSpecKind::Id); const auto *expr{GetExpr(spec)}; if (!expr || !expr->GetType()) { return; } CheckForDefinableVariable(spec, "ID"); int kind{expr->GetType()->kind()}; int defaultKind{context_.GetDefaultKind(TypeCategory::Integer)}; if (kind < defaultKind) { context_.Say( "ID kind (%d) is smaller than default INTEGER kind (%d)"_err_en_US, std::move(kind), std::move(defaultKind)); // C1229 } } void IoChecker::Enter(const parser::InputItem &spec) { flags_.set(Flag::DataList); const parser::Variable *var{std::get_if(&spec.u)}; if (!var) { return; } CheckForDefinableVariable(*var, "Input"); - const auto &name{GetLastName(*var)}; - const auto *expr{GetExpr(*var)}; - if (name.symbol && IsAssumedSizeArray(*name.symbol) && expr && - !evaluate::IsArrayElement(*GetExpr(*var))) { - context_.Say(name.source, - "Whole assumed size array '%s' may not be an input item"_err_en_US, - name.source); // C1231 - } } void IoChecker::Enter(const parser::InquireSpec &spec) { // InquireSpec context FileNameExpr if (std::get_if(&spec.u)) { SetSpecifier(IoSpecKind::File); } } void IoChecker::Enter(const parser::InquireSpec::CharVar &spec) { IoSpecKind specKind{}; using ParseKind = parser::InquireSpec::CharVar::Kind; switch (std::get(spec.t)) { case ParseKind::Access: specKind = IoSpecKind::Access; break; case ParseKind::Action: specKind = IoSpecKind::Action; break; case ParseKind::Asynchronous: specKind = IoSpecKind::Asynchronous; break; case ParseKind::Blank: specKind = IoSpecKind::Blank; break; case ParseKind::Decimal: specKind = IoSpecKind::Decimal; break; case ParseKind::Delim: specKind = IoSpecKind::Delim; break; case ParseKind::Direct: specKind = IoSpecKind::Direct; break; case ParseKind::Encoding: specKind = IoSpecKind::Encoding; break; case ParseKind::Form: specKind = IoSpecKind::Form; break; case ParseKind::Formatted: specKind = IoSpecKind::Formatted; break; case ParseKind::Iomsg: specKind = IoSpecKind::Iomsg; break; case ParseKind::Name: specKind = IoSpecKind::Name; break; case ParseKind::Pad: specKind = IoSpecKind::Pad; break; case ParseKind::Position: specKind = IoSpecKind::Position; break; case ParseKind::Read: specKind = IoSpecKind::Read; break; case ParseKind::Readwrite: specKind = IoSpecKind::Readwrite; break; case ParseKind::Round: specKind = IoSpecKind::Round; break; case ParseKind::Sequential: specKind = IoSpecKind::Sequential; break; case ParseKind::Sign: specKind = IoSpecKind::Sign; break; case ParseKind::Status: specKind = IoSpecKind::Status; break; case ParseKind::Stream: specKind = IoSpecKind::Stream; break; case ParseKind::Unformatted: specKind = IoSpecKind::Unformatted; break; case ParseKind::Write: specKind = IoSpecKind::Write; break; case ParseKind::Carriagecontrol: specKind = IoSpecKind::Carriagecontrol; break; case ParseKind::Convert: specKind = IoSpecKind::Convert; break; case ParseKind::Dispose: specKind = IoSpecKind::Dispose; break; } CheckForDefinableVariable(std::get(spec.t), parser::ToUpperCaseLetters(common::EnumToString(specKind))); SetSpecifier(specKind); } void IoChecker::Enter(const parser::InquireSpec::IntVar &spec) { IoSpecKind specKind{}; using ParseKind = parser::InquireSpec::IntVar::Kind; switch (std::get(spec.t)) { case ParseKind::Iostat: specKind = IoSpecKind::Iostat; break; case ParseKind::Nextrec: specKind = IoSpecKind::Nextrec; break; case ParseKind::Number: specKind = IoSpecKind::Number; break; case ParseKind::Pos: specKind = IoSpecKind::Pos; break; case ParseKind::Recl: specKind = IoSpecKind::Recl; break; case ParseKind::Size: specKind = IoSpecKind::Size; break; } CheckForDefinableVariable(std::get(spec.t), parser::ToUpperCaseLetters(common::EnumToString(specKind))); SetSpecifier(specKind); } void IoChecker::Enter(const parser::InquireSpec::LogVar &spec) { IoSpecKind specKind{}; using ParseKind = parser::InquireSpec::LogVar::Kind; switch (std::get(spec.t)) { case ParseKind::Exist: specKind = IoSpecKind::Exist; break; case ParseKind::Named: specKind = IoSpecKind::Named; break; case ParseKind::Opened: specKind = IoSpecKind::Opened; break; case ParseKind::Pending: specKind = IoSpecKind::Pending; break; } SetSpecifier(specKind); } void IoChecker::Enter(const parser::IoControlSpec &spec) { // IoControlSpec context Name flags_.set(Flag::IoControlList); if (std::holds_alternative(spec.u)) { SetSpecifier(IoSpecKind::Nml); flags_.set(Flag::FmtOrNml); } } void IoChecker::Enter(const parser::IoControlSpec::Asynchronous &spec) { SetSpecifier(IoSpecKind::Asynchronous); if (const std::optional charConst{ GetConstExpr(spec)}) { flags_.set( Flag::AsynchronousYes, parser::ToUpperCaseLetters(*charConst) == "YES"); CheckStringValue(IoSpecKind::Asynchronous, *charConst, parser::FindSourceLocation(spec)); // C1223 } } void IoChecker::Enter(const parser::IoControlSpec::CharExpr &spec) { IoSpecKind specKind{}; using ParseKind = parser::IoControlSpec::CharExpr::Kind; switch (std::get(spec.t)) { case ParseKind::Advance: specKind = IoSpecKind::Advance; break; case ParseKind::Blank: specKind = IoSpecKind::Blank; break; case ParseKind::Decimal: specKind = IoSpecKind::Decimal; break; case ParseKind::Delim: specKind = IoSpecKind::Delim; break; case ParseKind::Pad: specKind = IoSpecKind::Pad; break; case ParseKind::Round: specKind = IoSpecKind::Round; break; case ParseKind::Sign: specKind = IoSpecKind::Sign; break; } SetSpecifier(specKind); if (const std::optional charConst{GetConstExpr( std::get(spec.t))}) { if (specKind == IoSpecKind::Advance) { flags_.set( Flag::AdvanceYes, parser::ToUpperCaseLetters(*charConst) == "YES"); } CheckStringValue(specKind, *charConst, parser::FindSourceLocation(spec)); } } void IoChecker::Enter(const parser::IoControlSpec::Pos &) { SetSpecifier(IoSpecKind::Pos); } void IoChecker::Enter(const parser::IoControlSpec::Rec &) { SetSpecifier(IoSpecKind::Rec); } void IoChecker::Enter(const parser::IoControlSpec::Size &var) { CheckForDefinableVariable(var, "SIZE"); SetSpecifier(IoSpecKind::Size); } void IoChecker::Enter(const parser::IoUnit &spec) { if (const parser::Variable * var{std::get_if(&spec.u)}) { if (stmt_ == IoStmtKind::Write) { CheckForDefinableVariable(*var, "Internal file"); } if (const auto *expr{GetExpr(*var)}) { if (HasVectorSubscript(*expr)) { context_.Say(parser::FindSourceLocation(*var), // C1201 "Internal file must not have a vector subscript"_err_en_US); } else if (!ExprTypeKindIsDefault(*expr, context_)) { // This may be too restrictive; other kinds may be valid. context_.Say(parser::FindSourceLocation(*var), // C1202 "Invalid character kind for an internal file variable"_err_en_US); } } SetSpecifier(IoSpecKind::Unit); flags_.set(Flag::InternalUnit); } else if (std::get_if(&spec.u)) { SetSpecifier(IoSpecKind::Unit); flags_.set(Flag::StarUnit); } } void IoChecker::Enter(const parser::MsgVariable &var) { if (stmt_ == IoStmtKind::None) { // allocate, deallocate, image control CheckForDefinableVariable(var, "ERRMSG"); return; } CheckForDefinableVariable(var, "IOMSG"); SetSpecifier(IoSpecKind::Iomsg); } void IoChecker::Enter(const parser::OutputItem &item) { flags_.set(Flag::DataList); if (const auto *x{std::get_if(&item.u)}) { if (const auto *expr{GetExpr(*x)}) { if (IsProcedurePointer(*expr)) { context_.Say(parser::FindSourceLocation(*x), "Output item must not be a procedure pointer"_err_en_US); // C1233 } } } } void IoChecker::Enter(const parser::StatusExpr &spec) { SetSpecifier(IoSpecKind::Status); if (const std::optional charConst{ GetConstExpr(spec)}) { // Status values for Open and Close are different. std::string s{parser::ToUpperCaseLetters(*charConst)}; if (stmt_ == IoStmtKind::Open) { flags_.set(Flag::KnownStatus); flags_.set(Flag::StatusNew, s == "NEW"); flags_.set(Flag::StatusReplace, s == "REPLACE"); flags_.set(Flag::StatusScratch, s == "SCRATCH"); // CheckStringValue compares for OPEN Status string values. CheckStringValue( IoSpecKind::Status, *charConst, parser::FindSourceLocation(spec)); return; } CHECK(stmt_ == IoStmtKind::Close); if (s != "DELETE" && s != "KEEP") { context_.Say(parser::FindSourceLocation(spec), "Invalid STATUS value '%s'"_err_en_US, *charConst); } } } void IoChecker::Enter(const parser::StatVariable &var) { if (stmt_ == IoStmtKind::None) { // allocate, deallocate, image control CheckForDefinableVariable(var, "STAT"); return; } CheckForDefinableVariable(var, "IOSTAT"); SetSpecifier(IoSpecKind::Iostat); } void IoChecker::Leave(const parser::BackspaceStmt &) { CheckForPureSubprogram(); CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1240 Done(); } void IoChecker::Leave(const parser::CloseStmt &) { CheckForPureSubprogram(); CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1208 Done(); } void IoChecker::Leave(const parser::EndfileStmt &) { CheckForPureSubprogram(); CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1240 Done(); } void IoChecker::Leave(const parser::FlushStmt &) { CheckForPureSubprogram(); CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1243 Done(); } void IoChecker::Leave(const parser::InquireStmt &stmt) { if (std::get_if>(&stmt.u)) { CheckForPureSubprogram(); // Inquire by unit or by file (vs. by output list). CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit) || specifierSet_.test(IoSpecKind::File), "UNIT number or FILE"); // C1246 CheckForProhibitedSpecifier(IoSpecKind::File, IoSpecKind::Unit); // C1246 CheckForRequiredSpecifier(IoSpecKind::Id, IoSpecKind::Pending); // C1248 } Done(); } void IoChecker::Leave(const parser::OpenStmt &) { CheckForPureSubprogram(); CheckForRequiredSpecifier(specifierSet_.test(IoSpecKind::Unit) || specifierSet_.test(IoSpecKind::Newunit), "UNIT or NEWUNIT"); // C1204, C1205 CheckForProhibitedSpecifier( IoSpecKind::Newunit, IoSpecKind::Unit); // C1204, C1205 CheckForRequiredSpecifier(flags_.test(Flag::StatusNew), "STATUS='NEW'", IoSpecKind::File); // 12.5.6.10 CheckForRequiredSpecifier(flags_.test(Flag::StatusReplace), "STATUS='REPLACE'", IoSpecKind::File); // 12.5.6.10 CheckForProhibitedSpecifier(flags_.test(Flag::StatusScratch), "STATUS='SCRATCH'", IoSpecKind::File); // 12.5.6.10 if (flags_.test(Flag::KnownStatus)) { CheckForRequiredSpecifier(IoSpecKind::Newunit, specifierSet_.test(IoSpecKind::File) || flags_.test(Flag::StatusScratch), "FILE or STATUS='SCRATCH'"); // 12.5.6.12 } else { CheckForRequiredSpecifier(IoSpecKind::Newunit, specifierSet_.test(IoSpecKind::File) || specifierSet_.test(IoSpecKind::Status), "FILE or STATUS"); // 12.5.6.12 } if (flags_.test(Flag::KnownAccess)) { CheckForRequiredSpecifier(flags_.test(Flag::AccessDirect), "ACCESS='DIRECT'", IoSpecKind::Recl); // 12.5.6.15 CheckForProhibitedSpecifier(flags_.test(Flag::AccessStream), "STATUS='STREAM'", IoSpecKind::Recl); // 12.5.6.15 } Done(); } void IoChecker::Leave(const parser::PrintStmt &) { CheckForPureSubprogram(); Done(); } static void CheckForDoVariableInNamelist(const Symbol &namelist, SemanticsContext &context, parser::CharBlock namelistLocation) { const auto &details{namelist.GetUltimate().get()}; for (const Symbol &object : details.objects()) { context.CheckIndexVarRedefine(namelistLocation, object); } } static void CheckForDoVariableInNamelistSpec( const parser::ReadStmt &readStmt, SemanticsContext &context) { const std::list &controls{readStmt.controls}; for (const auto &control : controls) { if (const auto *namelist{std::get_if(&control.u)}) { if (const Symbol * symbol{namelist->symbol}) { CheckForDoVariableInNamelist(*symbol, context, namelist->source); } } } } static void CheckForDoVariable( const parser::ReadStmt &readStmt, SemanticsContext &context) { CheckForDoVariableInNamelistSpec(readStmt, context); const std::list &items{readStmt.items}; for (const auto &item : items) { if (const parser::Variable * variable{std::get_if(&item.u)}) { context.CheckIndexVarRedefine(*variable); } } } void IoChecker::Leave(const parser::ReadStmt &readStmt) { if (!flags_.test(Flag::InternalUnit)) { CheckForPureSubprogram(); } CheckForDoVariable(readStmt, context_); if (!flags_.test(Flag::IoControlList)) { Done(); return; } LeaveReadWrite(); CheckForProhibitedSpecifier(IoSpecKind::Delim); // C1212 CheckForProhibitedSpecifier(IoSpecKind::Sign); // C1212 CheckForProhibitedSpecifier(IoSpecKind::Rec, IoSpecKind::End); // C1220 CheckForRequiredSpecifier(IoSpecKind::Eor, specifierSet_.test(IoSpecKind::Advance) && !flags_.test(Flag::AdvanceYes), "ADVANCE with value 'NO'"); // C1222 + 12.6.2.1p2 CheckForRequiredSpecifier(IoSpecKind::Blank, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227 CheckForRequiredSpecifier( IoSpecKind::Pad, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227 Done(); } void IoChecker::Leave(const parser::RewindStmt &) { CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1240 CheckForPureSubprogram(); Done(); } void IoChecker::Leave(const parser::WaitStmt &) { CheckForRequiredSpecifier( flags_.test(Flag::NumberUnit), "UNIT number"); // C1237 CheckForPureSubprogram(); Done(); } void IoChecker::Leave(const parser::WriteStmt &) { if (!flags_.test(Flag::InternalUnit)) { CheckForPureSubprogram(); } LeaveReadWrite(); CheckForProhibitedSpecifier(IoSpecKind::Blank); // C1213 CheckForProhibitedSpecifier(IoSpecKind::End); // C1213 CheckForProhibitedSpecifier(IoSpecKind::Eor); // C1213 CheckForProhibitedSpecifier(IoSpecKind::Pad); // C1213 CheckForProhibitedSpecifier(IoSpecKind::Size); // C1213 CheckForRequiredSpecifier( IoSpecKind::Sign, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227 CheckForRequiredSpecifier(IoSpecKind::Delim, flags_.test(Flag::StarFmt) || specifierSet_.test(IoSpecKind::Nml), "FMT=* or NML"); // C1228 Done(); } void IoChecker::LeaveReadWrite() const { CheckForRequiredSpecifier(IoSpecKind::Unit); // C1211 CheckForProhibitedSpecifier(IoSpecKind::Nml, IoSpecKind::Rec); // C1216 CheckForProhibitedSpecifier(IoSpecKind::Nml, IoSpecKind::Fmt); // C1216 CheckForProhibitedSpecifier( IoSpecKind::Nml, flags_.test(Flag::DataList), "a data list"); // C1216 CheckForProhibitedSpecifier(flags_.test(Flag::InternalUnit), "UNIT=internal-file", IoSpecKind::Pos); // C1219 CheckForProhibitedSpecifier(flags_.test(Flag::InternalUnit), "UNIT=internal-file", IoSpecKind::Rec); // C1219 CheckForProhibitedSpecifier( flags_.test(Flag::StarUnit), "UNIT=*", IoSpecKind::Pos); // C1219 CheckForProhibitedSpecifier( flags_.test(Flag::StarUnit), "UNIT=*", IoSpecKind::Rec); // C1219 CheckForProhibitedSpecifier( IoSpecKind::Rec, flags_.test(Flag::StarFmt), "FMT=*"); // C1220 CheckForRequiredSpecifier(IoSpecKind::Advance, flags_.test(Flag::CharFmt) || flags_.test(Flag::LabelFmt) || flags_.test(Flag::AssignFmt), "an explicit format"); // C1221 CheckForProhibitedSpecifier(IoSpecKind::Advance, flags_.test(Flag::InternalUnit), "UNIT=internal-file"); // C1221 CheckForRequiredSpecifier(flags_.test(Flag::AsynchronousYes), "ASYNCHRONOUS='YES'", flags_.test(Flag::NumberUnit), "UNIT=number"); // C1224 CheckForRequiredSpecifier(IoSpecKind::Id, flags_.test(Flag::AsynchronousYes), "ASYNCHRONOUS='YES'"); // C1225 CheckForProhibitedSpecifier(IoSpecKind::Pos, IoSpecKind::Rec); // C1226 CheckForRequiredSpecifier(IoSpecKind::Decimal, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227 CheckForRequiredSpecifier(IoSpecKind::Round, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227 } void IoChecker::SetSpecifier(IoSpecKind specKind) { if (stmt_ == IoStmtKind::None) { // FMT may appear on PRINT statements, which don't have any checks. // [IO]MSG and [IO]STAT parse symbols are shared with non-I/O statements. return; } // C1203, C1207, C1210, C1236, C1239, C1242, C1245 if (specifierSet_.test(specKind)) { context_.Say("Duplicate %s specifier"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind))); } specifierSet_.set(specKind); } void IoChecker::CheckStringValue(IoSpecKind specKind, const std::string &value, const parser::CharBlock &source) const { static std::unordered_map> specValues{ {IoSpecKind::Access, {"DIRECT", "SEQUENTIAL", "STREAM"}}, {IoSpecKind::Action, {"READ", "READWRITE", "WRITE"}}, {IoSpecKind::Advance, {"NO", "YES"}}, {IoSpecKind::Asynchronous, {"NO", "YES"}}, {IoSpecKind::Blank, {"NULL", "ZERO"}}, {IoSpecKind::Decimal, {"COMMA", "POINT"}}, {IoSpecKind::Delim, {"APOSTROPHE", "NONE", "QUOTE"}}, {IoSpecKind::Encoding, {"DEFAULT", "UTF-8"}}, {IoSpecKind::Form, {"FORMATTED", "UNFORMATTED"}}, {IoSpecKind::Pad, {"NO", "YES"}}, {IoSpecKind::Position, {"APPEND", "ASIS", "REWIND"}}, {IoSpecKind::Round, {"COMPATIBLE", "DOWN", "NEAREST", "PROCESSOR_DEFINED", "UP", "ZERO"}}, {IoSpecKind::Sign, {"PLUS", "PROCESSOR_DEFINED", "SUPPRESS"}}, {IoSpecKind::Status, // Open values; Close values are {"DELETE", "KEEP"}. {"NEW", "OLD", "REPLACE", "SCRATCH", "UNKNOWN"}}, {IoSpecKind::Carriagecontrol, {"LIST", "FORTRAN", "NONE"}}, {IoSpecKind::Convert, {"BIG_ENDIAN", "LITTLE_ENDIAN", "NATIVE"}}, {IoSpecKind::Dispose, {"DELETE", "KEEP"}}, }; if (!specValues.at(specKind).count(parser::ToUpperCaseLetters(value))) { context_.Say(source, "Invalid %s value '%s'"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind)), value); } } // CheckForRequiredSpecifier and CheckForProhibitedSpecifier functions // need conditions to check, and string arguments to insert into a message. // An IoSpecKind provides both an absence/presence condition and a string // argument (its name). A (condition, string) pair provides an arbitrary // condition and an arbitrary string. void IoChecker::CheckForRequiredSpecifier(IoSpecKind specKind) const { if (!specifierSet_.test(specKind)) { context_.Say("%s statement must have a %s specifier"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(stmt_)), parser::ToUpperCaseLetters(common::EnumToString(specKind))); } } void IoChecker::CheckForRequiredSpecifier( bool condition, const std::string &s) const { if (!condition) { context_.Say("%s statement must have a %s specifier"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(stmt_)), s); } } void IoChecker::CheckForRequiredSpecifier( IoSpecKind specKind1, IoSpecKind specKind2) const { if (specifierSet_.test(specKind1) && !specifierSet_.test(specKind2)) { context_.Say("If %s appears, %s must also appear"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind1)), parser::ToUpperCaseLetters(common::EnumToString(specKind2))); } } void IoChecker::CheckForRequiredSpecifier( IoSpecKind specKind, bool condition, const std::string &s) const { if (specifierSet_.test(specKind) && !condition) { context_.Say("If %s appears, %s must also appear"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind)), s); } } void IoChecker::CheckForRequiredSpecifier( bool condition, const std::string &s, IoSpecKind specKind) const { if (condition && !specifierSet_.test(specKind)) { context_.Say("If %s appears, %s must also appear"_err_en_US, s, parser::ToUpperCaseLetters(common::EnumToString(specKind))); } } void IoChecker::CheckForRequiredSpecifier(bool condition1, const std::string &s1, bool condition2, const std::string &s2) const { if (condition1 && !condition2) { context_.Say("If %s appears, %s must also appear"_err_en_US, s1, s2); } } void IoChecker::CheckForProhibitedSpecifier(IoSpecKind specKind) const { if (specifierSet_.test(specKind)) { context_.Say("%s statement must not have a %s specifier"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(stmt_)), parser::ToUpperCaseLetters(common::EnumToString(specKind))); } } void IoChecker::CheckForProhibitedSpecifier( IoSpecKind specKind1, IoSpecKind specKind2) const { if (specifierSet_.test(specKind1) && specifierSet_.test(specKind2)) { context_.Say("If %s appears, %s must not appear"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind1)), parser::ToUpperCaseLetters(common::EnumToString(specKind2))); } } void IoChecker::CheckForProhibitedSpecifier( IoSpecKind specKind, bool condition, const std::string &s) const { if (specifierSet_.test(specKind) && condition) { context_.Say("If %s appears, %s must not appear"_err_en_US, parser::ToUpperCaseLetters(common::EnumToString(specKind)), s); } } void IoChecker::CheckForProhibitedSpecifier( bool condition, const std::string &s, IoSpecKind specKind) const { if (condition && specifierSet_.test(specKind)) { context_.Say("If %s appears, %s must not appear"_err_en_US, s, parser::ToUpperCaseLetters(common::EnumToString(specKind))); } } template void IoChecker::CheckForDefinableVariable( const A &var, const std::string &s) const { const Symbol *sym{ GetFirstName(*parser::Unwrap(var)).symbol}; if (WhyNotModifiable(*sym, context_.FindScope(*context_.location()))) { context_.Say(parser::FindSourceLocation(var), "%s variable '%s' must be definable"_err_en_US, s, sym->name()); } } void IoChecker::CheckForPureSubprogram() const { // C1597 CHECK(context_.location()); if (FindPureProcedureContaining(context_.FindScope(*context_.location()))) { context_.Say("External I/O is not allowed in a pure subprogram"_err_en_US); } } } // namespace Fortran::semantics diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp index 5a2a7df9fb98..661024f6990d 100644 --- a/flang/lib/Semantics/expression.cpp +++ b/flang/lib/Semantics/expression.cpp @@ -1,3193 +1,3212 @@ //===-- lib/Semantics/expression.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "flang/Semantics/expression.h" #include "check-call.h" #include "pointer-assignment.h" #include "resolve-names.h" #include "flang/Common/idioms.h" #include "flang/Evaluate/common.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/tools.h" #include "flang/Parser/characters.h" #include "flang/Parser/dump-parse-tree.h" #include "flang/Parser/parse-tree-visitor.h" #include "flang/Parser/parse-tree.h" #include "flang/Semantics/scope.h" #include "flang/Semantics/semantics.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include // Typedef for optional generic expressions (ubiquitous in this file) using MaybeExpr = std::optional>; // Much of the code that implements semantic analysis of expressions is // tightly coupled with their typed representations in lib/Evaluate, // and appears here in namespace Fortran::evaluate for convenience. namespace Fortran::evaluate { using common::LanguageFeature; using common::NumericOperator; using common::TypeCategory; static inline std::string ToUpperCase(const std::string &str) { return parser::ToUpperCaseLetters(str); } struct DynamicTypeWithLength : public DynamicType { explicit DynamicTypeWithLength(const DynamicType &t) : DynamicType{t} {} std::optional> LEN() const; std::optional> length; }; std::optional> DynamicTypeWithLength::LEN() const { if (length) { return length; } if (auto *lengthParam{charLength()}) { if (const auto &len{lengthParam->GetExplicit()}) { return ConvertToType(common::Clone(*len)); } } return std::nullopt; // assumed or deferred length } static std::optional AnalyzeTypeSpec( const std::optional &spec) { if (spec) { if (const semantics::DeclTypeSpec * typeSpec{spec->declTypeSpec}) { // Name resolution sets TypeSpec::declTypeSpec only when it's valid // (viz., an intrinsic type with valid known kind or a non-polymorphic // & non-ABSTRACT derived type). if (const semantics::IntrinsicTypeSpec * intrinsic{typeSpec->AsIntrinsic()}) { TypeCategory category{intrinsic->category()}; if (auto optKind{ToInt64(intrinsic->kind())}) { int kind{static_cast(*optKind)}; if (category == TypeCategory::Character) { const semantics::CharacterTypeSpec &cts{ typeSpec->characterTypeSpec()}; const semantics::ParamValue &len{cts.length()}; // N.B. CHARACTER(LEN=*) is allowed in type-specs in ALLOCATE() & // type guards, but not in array constructors. return DynamicTypeWithLength{DynamicType{kind, len}}; } else { return DynamicTypeWithLength{DynamicType{category, kind}}; } } } else if (const semantics::DerivedTypeSpec * derived{typeSpec->AsDerived()}) { return DynamicTypeWithLength{DynamicType{*derived}}; } } } return std::nullopt; } class ArgumentAnalyzer { public: explicit ArgumentAnalyzer(ExpressionAnalyzer &context) : context_{context}, isProcedureCall_{false} {} ArgumentAnalyzer(ExpressionAnalyzer &context, parser::CharBlock source, bool isProcedureCall = false) : context_{context}, source_{source}, isProcedureCall_{isProcedureCall} {} bool fatalErrors() const { return fatalErrors_; } ActualArguments &&GetActuals() { CHECK(!fatalErrors_); return std::move(actuals_); } const Expr &GetExpr(std::size_t i) const { return DEREF(actuals_.at(i).value().UnwrapExpr()); } Expr &&MoveExpr(std::size_t i) { return std::move(DEREF(actuals_.at(i).value().UnwrapExpr())); } void Analyze(const common::Indirection &x) { Analyze(x.value()); } void Analyze(const parser::Expr &x) { actuals_.emplace_back(AnalyzeExpr(x)); fatalErrors_ |= !actuals_.back(); } void Analyze(const parser::Variable &); void Analyze(const parser::ActualArgSpec &, bool isSubroutine); void ConvertBOZ(std::size_t i, std::optional otherType); bool IsIntrinsicRelational(RelationalOperator) const; bool IsIntrinsicLogical() const; bool IsIntrinsicNumeric(NumericOperator) const; bool IsIntrinsicConcat() const; bool CheckConformance() const; // Find and return a user-defined operator or report an error. // The provided message is used if there is no such operator. MaybeExpr TryDefinedOp( const char *, parser::MessageFixedText &&, bool isUserOp = false); template MaybeExpr TryDefinedOp(E opr, parser::MessageFixedText &&msg) { return TryDefinedOp( context_.context().languageFeatures().GetNames(opr), std::move(msg)); } // Find and return a user-defined assignment std::optional TryDefinedAssignment(); std::optional GetDefinedAssignmentProc(); std::optional GetType(std::size_t) const; void Dump(llvm::raw_ostream &); private: MaybeExpr TryDefinedOp( std::vector, parser::MessageFixedText &&); MaybeExpr TryBoundOp(const Symbol &, int passIndex); std::optional AnalyzeExpr(const parser::Expr &); + MaybeExpr AnalyzeExprOrWholeAssumedSizeArray(const parser::Expr &); bool AreConformable() const; const Symbol *FindBoundOp(parser::CharBlock, int passIndex); void AddAssignmentConversion( const DynamicType &lhsType, const DynamicType &rhsType); bool OkLogicalIntegerAssignment(TypeCategory lhs, TypeCategory rhs); int GetRank(std::size_t) const; bool IsBOZLiteral(std::size_t i) const { return std::holds_alternative(GetExpr(i).u); } void SayNoMatch(const std::string &, bool isAssignment = false); std::string TypeAsFortran(std::size_t); bool AnyUntypedOperand(); ExpressionAnalyzer &context_; ActualArguments actuals_; parser::CharBlock source_; bool fatalErrors_{false}; const bool isProcedureCall_; // false for user-defined op or assignment const Symbol *sawDefinedOp_{nullptr}; }; // Wraps a data reference in a typed Designator<>, and a procedure // or procedure pointer reference in a ProcedureDesignator. MaybeExpr ExpressionAnalyzer::Designate(DataRef &&ref) { const Symbol &symbol{ref.GetLastSymbol().GetUltimate()}; if (semantics::IsProcedure(symbol)) { if (auto *component{std::get_if(&ref.u)}) { return Expr{ProcedureDesignator{std::move(*component)}}; } else if (!std::holds_alternative(ref.u)) { DIE("unexpected alternative in DataRef"); } else if (!symbol.attrs().test(semantics::Attr::INTRINSIC)) { return Expr{ProcedureDesignator{symbol}}; } else if (auto interface{context_.intrinsics().IsSpecificIntrinsicFunction( symbol.name().ToString())}) { SpecificIntrinsic intrinsic{ symbol.name().ToString(), std::move(*interface)}; intrinsic.isRestrictedSpecific = interface->isRestrictedSpecific; return Expr{ProcedureDesignator{std::move(intrinsic)}}; } else { Say("'%s' is not a specific intrinsic procedure"_err_en_US, symbol.name()); return std::nullopt; } } else if (auto dyType{DynamicType::From(symbol)}) { return TypedWrapper(*dyType, std::move(ref)); } return std::nullopt; } // Some subscript semantic checks must be deferred until all of the // subscripts are in hand. MaybeExpr ExpressionAnalyzer::CompleteSubscripts(ArrayRef &&ref) { const Symbol &symbol{ref.GetLastSymbol().GetUltimate()}; const auto *object{symbol.detailsIf()}; int symbolRank{symbol.Rank()}; int subscripts{static_cast(ref.size())}; if (subscripts == 0) { // nothing to check } else if (subscripts != symbolRank) { if (symbolRank != 0) { Say("Reference to rank-%d object '%s' has %d subscripts"_err_en_US, symbolRank, symbol.name(), subscripts); } return std::nullopt; } else if (Component * component{ref.base().UnwrapComponent()}) { int baseRank{component->base().Rank()}; if (baseRank > 0) { int subscriptRank{0}; for (const auto &expr : ref.subscript()) { subscriptRank += expr.Rank(); } if (subscriptRank > 0) { Say("Subscripts of component '%s' of rank-%d derived type " "array have rank %d but must all be scalar"_err_en_US, symbol.name(), baseRank, subscriptRank); return std::nullopt; } } } else if (object) { // C928 & C1002 if (Triplet * last{std::get_if(&ref.subscript().back().u)}) { if (!last->upper() && object->IsAssumedSize()) { Say("Assumed-size array '%s' must have explicit final " "subscript upper bound value"_err_en_US, symbol.name()); return std::nullopt; } } } return Designate(DataRef{std::move(ref)}); } // Applies subscripts to a data reference. MaybeExpr ExpressionAnalyzer::ApplySubscripts( DataRef &&dataRef, std::vector &&subscripts) { return std::visit( common::visitors{ [&](SymbolRef &&symbol) { return CompleteSubscripts(ArrayRef{symbol, std::move(subscripts)}); }, [&](Component &&c) { return CompleteSubscripts( ArrayRef{std::move(c), std::move(subscripts)}); }, [&](auto &&) -> MaybeExpr { DIE("bad base for ArrayRef"); return std::nullopt; }, }, std::move(dataRef.u)); } // Top-level checks for data references. MaybeExpr ExpressionAnalyzer::TopLevelChecks(DataRef &&dataRef) { if (Component * component{std::get_if(&dataRef.u)}) { const Symbol &symbol{component->GetLastSymbol()}; int componentRank{symbol.Rank()}; if (componentRank > 0) { int baseRank{component->base().Rank()}; if (baseRank > 0) { Say("Reference to whole rank-%d component '%%%s' of " "rank-%d array of derived type is not allowed"_err_en_US, componentRank, symbol.name(), baseRank); } } } return Designate(std::move(dataRef)); } // Parse tree correction after a substring S(j:k) was misparsed as an // array section. N.B. Fortran substrings have to have a range, not a // single index. static void FixMisparsedSubstring(const parser::Designator &d) { auto &mutate{const_cast(d)}; if (auto *dataRef{std::get_if(&mutate.u)}) { if (auto *ae{std::get_if>( &dataRef->u)}) { parser::ArrayElement &arrElement{ae->value()}; if (!arrElement.subscripts.empty()) { auto iter{arrElement.subscripts.begin()}; if (auto *triplet{std::get_if(&iter->u)}) { if (!std::get<2>(triplet->t) /* no stride */ && ++iter == arrElement.subscripts.end() /* one subscript */) { if (Symbol * symbol{std::visit( common::visitors{ [](parser::Name &n) { return n.symbol; }, [](common::Indirection &sc) { return sc.value().component.symbol; }, [](auto &) -> Symbol * { return nullptr; }, }, arrElement.base.u)}) { const Symbol &ultimate{symbol->GetUltimate()}; if (const semantics::DeclTypeSpec * type{ultimate.GetType()}) { if (!ultimate.IsObjectArray() && type->category() == semantics::DeclTypeSpec::Character) { // The ambiguous S(j:k) was parsed as an array section // reference, but it's now clear that it's a substring. // Fix the parse tree in situ. mutate.u = arrElement.ConvertToSubstring(); } } } } } } } } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Designator &d) { auto restorer{GetContextualMessages().SetLocation(d.source)}; FixMisparsedSubstring(d); // These checks have to be deferred to these "top level" data-refs where // we can be sure that there are no following subscripts (yet). // Substrings have already been run through TopLevelChecks() and // won't be returned by ExtractDataRef(). if (MaybeExpr result{Analyze(d.u)}) { if (std::optional dataRef{ExtractDataRef(std::move(result))}) { return TopLevelChecks(std::move(*dataRef)); } return result; } return std::nullopt; } // A utility subroutine to repackage optional expressions of various levels // of type specificity as fully general MaybeExpr values. template common::IfNoLvalue AsMaybeExpr(A &&x) { return AsGenericExpr(std::move(x)); } template MaybeExpr AsMaybeExpr(std::optional &&x) { if (x) { return AsMaybeExpr(std::move(*x)); } return std::nullopt; } // Type kind parameter values for literal constants. int ExpressionAnalyzer::AnalyzeKindParam( const std::optional &kindParam, int defaultKind) { if (!kindParam) { return defaultKind; } return std::visit( common::visitors{ [](std::uint64_t k) { return static_cast(k); }, [&](const parser::Scalar< parser::Integer>> &n) { if (MaybeExpr ie{Analyze(n)}) { if (std::optional i64{ToInt64(*ie)}) { int iv = *i64; if (iv == *i64) { return iv; } } } return defaultKind; }, }, kindParam->u); } // Common handling of parser::IntLiteralConstant and SignedIntLiteralConstant struct IntTypeVisitor { using Result = MaybeExpr; using Types = IntegerTypes; template Result Test() { if (T::kind >= kind) { const char *p{digits.begin()}; auto value{T::Scalar::Read(p, 10, true /*signed*/)}; if (!value.overflow) { if (T::kind > kind) { if (!isDefaultKind || !analyzer.context().IsEnabled(LanguageFeature::BigIntLiterals)) { return std::nullopt; } else if (analyzer.context().ShouldWarn( LanguageFeature::BigIntLiterals)) { analyzer.Say(digits, "Integer literal is too large for default INTEGER(KIND=%d); " "assuming INTEGER(KIND=%d)"_en_US, kind, T::kind); } } return Expr{ Expr{Expr{Constant{std::move(value.value)}}}}; } } return std::nullopt; } ExpressionAnalyzer &analyzer; parser::CharBlock digits; int kind; bool isDefaultKind; }; template MaybeExpr ExpressionAnalyzer::IntLiteralConstant(const PARSED &x) { const auto &kindParam{std::get>(x.t)}; bool isDefaultKind{!kindParam}; int kind{AnalyzeKindParam(kindParam, GetDefaultKind(TypeCategory::Integer))}; if (CheckIntrinsicKind(TypeCategory::Integer, kind)) { auto digits{std::get(x.t)}; if (MaybeExpr result{common::SearchTypes( IntTypeVisitor{*this, digits, kind, isDefaultKind})}) { return result; } else if (isDefaultKind) { Say(digits, "Integer literal is too large for any allowable " "kind of INTEGER"_err_en_US); } else { Say(digits, "Integer literal is too large for INTEGER(KIND=%d)"_err_en_US, kind); } } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::IntLiteralConstant &x) { auto restorer{ GetContextualMessages().SetLocation(std::get(x.t))}; return IntLiteralConstant(x); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::SignedIntLiteralConstant &x) { auto restorer{GetContextualMessages().SetLocation(x.source)}; return IntLiteralConstant(x); } template Constant ReadRealLiteral( parser::CharBlock source, FoldingContext &context) { const char *p{source.begin()}; auto valWithFlags{Scalar::Read(p, context.rounding())}; CHECK(p == source.end()); RealFlagWarnings(context, valWithFlags.flags, "conversion of REAL literal"); auto value{valWithFlags.value}; if (context.flushSubnormalsToZero()) { value = value.FlushSubnormalToZero(); } return {value}; } struct RealTypeVisitor { using Result = std::optional>; using Types = RealTypes; RealTypeVisitor(int k, parser::CharBlock lit, FoldingContext &ctx) : kind{k}, literal{lit}, context{ctx} {} template Result Test() { if (kind == T::kind) { return {AsCategoryExpr(ReadRealLiteral(literal, context))}; } return std::nullopt; } int kind; parser::CharBlock literal; FoldingContext &context; }; // Reads a real literal constant and encodes it with the right kind. MaybeExpr ExpressionAnalyzer::Analyze(const parser::RealLiteralConstant &x) { // Use a local message context around the real literal for better // provenance on any messages. auto restorer{GetContextualMessages().SetLocation(x.real.source)}; // If a kind parameter appears, it defines the kind of the literal and the // letter used in an exponent part must be 'E' (e.g., the 'E' in // "6.02214E+23"). In the absence of an explicit kind parameter, any // exponent letter determines the kind. Otherwise, defaults apply. auto &defaults{context_.defaultKinds()}; int defaultKind{defaults.GetDefaultKind(TypeCategory::Real)}; const char *end{x.real.source.end()}; char expoLetter{' '}; std::optional letterKind; for (const char *p{x.real.source.begin()}; p < end; ++p) { if (parser::IsLetter(*p)) { expoLetter = *p; switch (expoLetter) { case 'e': letterKind = defaults.GetDefaultKind(TypeCategory::Real); break; case 'd': letterKind = defaults.doublePrecisionKind(); break; case 'q': letterKind = defaults.quadPrecisionKind(); break; default: Say("Unknown exponent letter '%c'"_err_en_US, expoLetter); } break; } } if (letterKind) { defaultKind = *letterKind; } // C716 requires 'E' as an exponent, but this is more useful auto kind{AnalyzeKindParam(x.kind, defaultKind)}; if (letterKind && kind != *letterKind && expoLetter != 'e') { Say("Explicit kind parameter on real constant disagrees with " "exponent letter '%c'"_en_US, expoLetter); } auto result{common::SearchTypes( RealTypeVisitor{kind, x.real.source, GetFoldingContext()})}; if (!result) { // C717 Say("Unsupported REAL(KIND=%d)"_err_en_US, kind); } return AsMaybeExpr(std::move(result)); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::SignedRealLiteralConstant &x) { if (auto result{Analyze(std::get(x.t))}) { auto &realExpr{std::get>(result->u)}; if (auto sign{std::get>(x.t)}) { if (sign == parser::Sign::Negative) { return AsGenericExpr(-std::move(realExpr)); } } return result; } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze( const parser::SignedComplexLiteralConstant &x) { auto result{Analyze(std::get(x.t))}; if (!result) { return std::nullopt; } else if (std::get(x.t) == parser::Sign::Negative) { return AsGenericExpr(-std::move(std::get>(result->u))); } else { return result; } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::ComplexPart &x) { return Analyze(x.u); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::ComplexLiteralConstant &z) { return AsMaybeExpr( ConstructComplex(GetContextualMessages(), Analyze(std::get<0>(z.t)), Analyze(std::get<1>(z.t)), GetDefaultKind(TypeCategory::Real))); } // CHARACTER literal processing. MaybeExpr ExpressionAnalyzer::AnalyzeString(std::string &&string, int kind) { if (!CheckIntrinsicKind(TypeCategory::Character, kind)) { return std::nullopt; } switch (kind) { case 1: return AsGenericExpr(Constant>{ parser::DecodeString( string, true)}); case 2: return AsGenericExpr(Constant>{ parser::DecodeString( string, true)}); case 4: return AsGenericExpr(Constant>{ parser::DecodeString( string, true)}); default: CRASH_NO_CASE; } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::CharLiteralConstant &x) { int kind{ AnalyzeKindParam(std::get>(x.t), 1)}; auto value{std::get(x.t)}; return AnalyzeString(std::move(value), kind); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::HollerithLiteralConstant &x) { int kind{GetDefaultKind(TypeCategory::Character)}; auto value{x.v}; return AnalyzeString(std::move(value), kind); } // .TRUE. and .FALSE. of various kinds MaybeExpr ExpressionAnalyzer::Analyze(const parser::LogicalLiteralConstant &x) { auto kind{AnalyzeKindParam(std::get>(x.t), GetDefaultKind(TypeCategory::Logical))}; bool value{std::get(x.t)}; auto result{common::SearchTypes( TypeKindVisitor{ kind, std::move(value)})}; if (!result) { Say("unsupported LOGICAL(KIND=%d)"_err_en_US, kind); // C728 } return result; } // BOZ typeless literals MaybeExpr ExpressionAnalyzer::Analyze(const parser::BOZLiteralConstant &x) { const char *p{x.v.c_str()}; std::uint64_t base{16}; switch (*p++) { case 'b': base = 2; break; case 'o': base = 8; break; case 'z': break; case 'x': break; default: CRASH_NO_CASE; } CHECK(*p == '"'); ++p; auto value{BOZLiteralConstant::Read(p, base, false /*unsigned*/)}; if (*p != '"') { Say("Invalid digit ('%c') in BOZ literal '%s'"_err_en_US, *p, x.v); // C7107, C7108 return std::nullopt; } if (value.overflow) { Say("BOZ literal '%s' too large"_err_en_US, x.v); return std::nullopt; } return AsGenericExpr(std::move(value.value)); } // Names and named constants MaybeExpr ExpressionAnalyzer::Analyze(const parser::Name &n) { if (std::optional kind{IsImpliedDo(n.source)}) { return AsMaybeExpr(ConvertToKind( *kind, AsExpr(ImpliedDoIndex{n.source}))); } else if (context_.HasError(n)) { return std::nullopt; } else if (!n.symbol) { SayAt(n, "Internal error: unresolved name '%s'"_err_en_US, n.source); return std::nullopt; } else { const Symbol &ultimate{n.symbol->GetUltimate()}; if (ultimate.has()) { // A bare reference to a derived type parameter (within a parameterized // derived type definition) return Fold(ConvertToType( ultimate, AsGenericExpr(TypeParamInquiry{std::nullopt, ultimate}))); } else { if (n.symbol->attrs().test(semantics::Attr::VOLATILE)) { if (const semantics::Scope * pure{semantics::FindPureProcedureContaining( context_.FindScope(n.source))}) { SayAt(n, "VOLATILE variable '%s' may not be referenced in pure subprogram '%s'"_err_en_US, n.source, DEREF(pure->symbol()).name()); n.symbol->attrs().reset(semantics::Attr::VOLATILE); } } + if (!isWholeAssumedSizeArrayOk_ && + semantics::IsAssumedSizeArray(*n.symbol)) { // C1002, C1014, C1231 + AttachDeclaration( + SayAt(n, + "Whole assumed-size array '%s' may not appear here without subscripts"_err_en_US, + n.source), + *n.symbol); + } return Designate(DataRef{*n.symbol}); } } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::NamedConstant &n) { if (MaybeExpr value{Analyze(n.v)}) { Expr folded{Fold(std::move(*value))}; if (IsConstantExpr(folded)) { return folded; } Say(n.v.source, "must be a constant"_err_en_US); // C718 } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::NullInit &x) { return Expr{NullPointer{}}; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::InitialDataTarget &x) { return Analyze(x.value()); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::DataStmtValue &x) { if (const auto &repeat{ std::get>(x.t)}) { x.repetitions = -1; if (MaybeExpr expr{Analyze(repeat->u)}) { Expr folded{Fold(std::move(*expr))}; if (auto value{ToInt64(folded)}) { if (*value >= 0) { // C882 x.repetitions = *value; } else { Say(FindSourceLocation(repeat), "Repeat count (%jd) for data value must not be negative"_err_en_US, *value); } } } } return Analyze(std::get(x.t)); } // Substring references std::optional> ExpressionAnalyzer::GetSubstringBound( const std::optional &bound) { if (bound) { if (MaybeExpr expr{Analyze(*bound)}) { if (expr->Rank() > 1) { Say("substring bound expression has rank %d"_err_en_US, expr->Rank()); } if (auto *intExpr{std::get_if>(&expr->u)}) { if (auto *ssIntExpr{std::get_if>(&intExpr->u)}) { return {std::move(*ssIntExpr)}; } return {Expr{ Convert{ std::move(*intExpr)}}}; } else { Say("substring bound expression is not INTEGER"_err_en_US); } } } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Substring &ss) { if (MaybeExpr baseExpr{Analyze(std::get(ss.t))}) { if (std::optional dataRef{ExtractDataRef(std::move(*baseExpr))}) { if (MaybeExpr newBaseExpr{TopLevelChecks(std::move(*dataRef))}) { if (std::optional checked{ ExtractDataRef(std::move(*newBaseExpr))}) { const parser::SubstringRange &range{ std::get(ss.t)}; std::optional> first{ GetSubstringBound(std::get<0>(range.t))}; std::optional> last{ GetSubstringBound(std::get<1>(range.t))}; const Symbol &symbol{checked->GetLastSymbol()}; if (std::optional dynamicType{ DynamicType::From(symbol)}) { if (dynamicType->category() == TypeCategory::Character) { return WrapperHelper(dynamicType->kind(), Substring{std::move(checked.value()), std::move(first), std::move(last)}); } } Say("substring may apply only to CHARACTER"_err_en_US); } } } } return std::nullopt; } // CHARACTER literal substrings MaybeExpr ExpressionAnalyzer::Analyze( const parser::CharLiteralConstantSubstring &x) { const parser::SubstringRange &range{std::get(x.t)}; std::optional> lower{ GetSubstringBound(std::get<0>(range.t))}; std::optional> upper{ GetSubstringBound(std::get<1>(range.t))}; if (MaybeExpr string{Analyze(std::get(x.t))}) { if (auto *charExpr{std::get_if>(&string->u)}) { Expr length{ std::visit([](const auto &ckExpr) { return ckExpr.LEN().value(); }, charExpr->u)}; if (!lower) { lower = Expr{1}; } if (!upper) { upper = Expr{ static_cast(ToInt64(length).value())}; } return std::visit( [&](auto &&ckExpr) -> MaybeExpr { using Result = ResultType; auto *cp{std::get_if>(&ckExpr.u)}; CHECK(DEREF(cp).size() == 1); StaticDataObject::Pointer staticData{StaticDataObject::Create()}; staticData->set_alignment(Result::kind) .set_itemBytes(Result::kind) .Push(cp->GetScalarValue().value()); Substring substring{std::move(staticData), std::move(lower.value()), std::move(upper.value())}; return AsGenericExpr( Expr{Designator{std::move(substring)}}); }, std::move(charExpr->u)); } } return std::nullopt; } // Subscripted array references std::optional> ExpressionAnalyzer::AsSubscript( MaybeExpr &&expr) { if (expr) { if (expr->Rank() > 1) { Say("Subscript expression has rank %d greater than 1"_err_en_US, expr->Rank()); } if (auto *intExpr{std::get_if>(&expr->u)}) { if (auto *ssIntExpr{std::get_if>(&intExpr->u)}) { return std::move(*ssIntExpr); } else { return Expr{ Convert{ std::move(*intExpr)}}; } } else { Say("Subscript expression is not INTEGER"_err_en_US); } } return std::nullopt; } std::optional> ExpressionAnalyzer::TripletPart( const std::optional &s) { if (s) { return AsSubscript(Analyze(*s)); } else { return std::nullopt; } } std::optional ExpressionAnalyzer::AnalyzeSectionSubscript( const parser::SectionSubscript &ss) { return std::visit( common::visitors{ [&](const parser::SubscriptTriplet &t) -> std::optional { const auto &lower{std::get<0>(t.t)}; const auto &upper{std::get<1>(t.t)}; const auto &stride{std::get<2>(t.t)}; auto result{Triplet{ TripletPart(lower), TripletPart(upper), TripletPart(stride)}}; if ((lower && !result.lower()) || (upper && !result.upper())) { return std::nullopt; } else { return std::make_optional(result); } }, [&](const auto &s) -> std::optional { if (auto subscriptExpr{AsSubscript(Analyze(s))}) { return Subscript{std::move(*subscriptExpr)}; } else { return std::nullopt; } }, }, ss.u); } // Empty result means an error occurred std::vector ExpressionAnalyzer::AnalyzeSectionSubscripts( const std::list &sss) { bool error{false}; std::vector subscripts; for (const auto &s : sss) { if (auto subscript{AnalyzeSectionSubscript(s)}) { subscripts.emplace_back(std::move(*subscript)); } else { error = true; } } return !error ? subscripts : std::vector{}; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::ArrayElement &ae) { - if (MaybeExpr baseExpr{Analyze(ae.base)}) { + MaybeExpr baseExpr; + { + auto restorer{AllowWholeAssumedSizeArray()}; + baseExpr = Analyze(ae.base); + } + if (baseExpr) { if (ae.subscripts.empty()) { // will be converted to function call later or error reported return std::nullopt; } else if (baseExpr->Rank() == 0) { if (const Symbol * symbol{GetLastSymbol(*baseExpr)}) { if (!context_.HasError(symbol)) { Say("'%s' is not an array"_err_en_US, symbol->name()); context_.SetError(*symbol); } } } else if (std::optional dataRef{ ExtractDataRef(std::move(*baseExpr))}) { return ApplySubscripts( std::move(*dataRef), AnalyzeSectionSubscripts(ae.subscripts)); } else { Say("Subscripts may be applied only to an object, component, or array constant"_err_en_US); } } // error was reported: analyze subscripts without reporting more errors auto restorer{GetContextualMessages().DiscardMessages()}; AnalyzeSectionSubscripts(ae.subscripts); return std::nullopt; } // Type parameter inquiries apply to data references, but don't depend // on any trailing (co)subscripts. static NamedEntity IgnoreAnySubscripts(Designator &&designator) { return std::visit( common::visitors{ [](SymbolRef &&symbol) { return NamedEntity{symbol}; }, [](Component &&component) { return NamedEntity{std::move(component)}; }, [](ArrayRef &&arrayRef) { return std::move(arrayRef.base()); }, [](CoarrayRef &&coarrayRef) { return NamedEntity{coarrayRef.GetLastSymbol()}; }, }, std::move(designator.u)); } // Components of parent derived types are explicitly represented as such. static std::optional CreateComponent( DataRef &&base, const Symbol &component, const semantics::Scope &scope) { if (&component.owner() == &scope) { return Component{std::move(base), component}; } if (const semantics::Scope * parentScope{scope.GetDerivedTypeParent()}) { if (const Symbol * parentComponent{parentScope->GetSymbol()}) { return CreateComponent( DataRef{Component{std::move(base), *parentComponent}}, component, *parentScope); } } return std::nullopt; } // Derived type component references and type parameter inquiries MaybeExpr ExpressionAnalyzer::Analyze(const parser::StructureComponent &sc) { MaybeExpr base{Analyze(sc.base)}; if (!base) { return std::nullopt; } Symbol *sym{sc.component.symbol}; if (context_.HasError(sym)) { return std::nullopt; } const auto &name{sc.component.source}; if (auto *dtExpr{UnwrapExpr>(*base)}) { const auto *dtSpec{GetDerivedTypeSpec(dtExpr->GetType())}; if (sym->detailsIf()) { if (auto *designator{UnwrapExpr>(*dtExpr)}) { if (std::optional dyType{DynamicType::From(*sym)}) { if (dyType->category() == TypeCategory::Integer) { return Fold(ConvertToType(*dyType, AsGenericExpr(TypeParamInquiry{ IgnoreAnySubscripts(std::move(*designator)), *sym}))); } } Say(name, "Type parameter is not INTEGER"_err_en_US); } else { Say(name, "A type parameter inquiry must be applied to " "a designator"_err_en_US); } } else if (!dtSpec || !dtSpec->scope()) { CHECK(context_.AnyFatalError() || !foldingContext_.messages().empty()); return std::nullopt; } else if (std::optional dataRef{ ExtractDataRef(std::move(*dtExpr))}) { if (auto component{ CreateComponent(std::move(*dataRef), *sym, *dtSpec->scope())}) { return Designate(DataRef{std::move(*component)}); } else { Say(name, "Component is not in scope of derived TYPE(%s)"_err_en_US, dtSpec->typeSymbol().name()); } } else { Say(name, "Base of component reference must be a data reference"_err_en_US); } } else if (auto *details{sym->detailsIf()}) { // special part-ref: %re, %im, %kind, %len // Type errors are detected and reported in semantics. using MiscKind = semantics::MiscDetails::Kind; MiscKind kind{details->kind()}; if (kind == MiscKind::ComplexPartRe || kind == MiscKind::ComplexPartIm) { if (auto *zExpr{std::get_if>(&base->u)}) { if (std::optional dataRef{ExtractDataRef(std::move(*zExpr))}) { Expr realExpr{std::visit( [&](const auto &z) { using PartType = typename ResultType::Part; auto part{kind == MiscKind::ComplexPartRe ? ComplexPart::Part::RE : ComplexPart::Part::IM}; return AsCategoryExpr(Designator{ ComplexPart{std::move(*dataRef), part}}); }, zExpr->u)}; return AsGenericExpr(std::move(realExpr)); } } } else if (kind == MiscKind::KindParamInquiry || kind == MiscKind::LenParamInquiry) { // Convert x%KIND -> intrinsic KIND(x), x%LEN -> intrinsic LEN(x) return MakeFunctionRef( name, ActualArguments{ActualArgument{std::move(*base)}}); } else { DIE("unexpected MiscDetails::Kind"); } } else { Say(name, "derived type required before component reference"_err_en_US); } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::CoindexedNamedObject &x) { if (auto maybeDataRef{ExtractDataRef(Analyze(x.base))}) { DataRef *dataRef{&*maybeDataRef}; std::vector subscripts; SymbolVector reversed; if (auto *aRef{std::get_if(&dataRef->u)}) { subscripts = std::move(aRef->subscript()); reversed.push_back(aRef->GetLastSymbol()); if (Component * component{aRef->base().UnwrapComponent()}) { dataRef = &component->base(); } else { dataRef = nullptr; } } if (dataRef) { while (auto *component{std::get_if(&dataRef->u)}) { reversed.push_back(component->GetLastSymbol()); dataRef = &component->base(); } if (auto *baseSym{std::get_if(&dataRef->u)}) { reversed.push_back(*baseSym); } else { Say("Base of coindexed named object has subscripts or cosubscripts"_err_en_US); } } std::vector> cosubscripts; bool cosubsOk{true}; for (const auto &cosub : std::get>(x.imageSelector.t)) { MaybeExpr coex{Analyze(cosub)}; if (auto *intExpr{UnwrapExpr>(coex)}) { cosubscripts.push_back( ConvertToType(std::move(*intExpr))); } else { cosubsOk = false; } } if (cosubsOk && !reversed.empty()) { int numCosubscripts{static_cast(cosubscripts.size())}; const Symbol &symbol{reversed.front()}; if (numCosubscripts != symbol.Corank()) { Say("'%s' has corank %d, but coindexed reference has %d cosubscripts"_err_en_US, symbol.name(), symbol.Corank(), numCosubscripts); } } for (const auto &imageSelSpec : std::get>(x.imageSelector.t)) { std::visit( common::visitors{ [&](const auto &x) { Analyze(x.v); }, }, imageSelSpec.u); } // Reverse the chain of symbols so that the base is first and coarray // ultimate component is last. if (cosubsOk) { return Designate( DataRef{CoarrayRef{SymbolVector{reversed.crbegin(), reversed.crend()}, std::move(subscripts), std::move(cosubscripts)}}); } } return std::nullopt; } int ExpressionAnalyzer::IntegerTypeSpecKind( const parser::IntegerTypeSpec &spec) { Expr value{ AnalyzeKindSelector(TypeCategory::Integer, spec.v)}; if (auto kind{ToInt64(value)}) { return static_cast(*kind); } SayAt(spec, "Constant INTEGER kind value required here"_err_en_US); return GetDefaultKind(TypeCategory::Integer); } // Array constructors // Inverts a collection of generic ArrayConstructorValues that // all happen to have the same actual type T into one ArrayConstructor. template ArrayConstructorValues MakeSpecific( ArrayConstructorValues &&from) { ArrayConstructorValues to; for (ArrayConstructorValue &x : from) { std::visit( common::visitors{ [&](common::CopyableIndirection> &&expr) { auto *typed{UnwrapExpr>(expr.value())}; to.Push(std::move(DEREF(typed))); }, [&](ImpliedDo &&impliedDo) { to.Push(ImpliedDo{impliedDo.name(), std::move(impliedDo.lower()), std::move(impliedDo.upper()), std::move(impliedDo.stride()), MakeSpecific(std::move(impliedDo.values()))}); }, }, std::move(x.u)); } return to; } class ArrayConstructorContext { public: ArrayConstructorContext( ExpressionAnalyzer &c, std::optional &&t) : exprAnalyzer_{c}, type_{std::move(t)} {} void Add(const parser::AcValue &); MaybeExpr ToExpr(); // These interfaces allow *this to be used as a type visitor argument to // common::SearchTypes() to convert the array constructor to a typed // expression in ToExpr(). using Result = MaybeExpr; using Types = AllTypes; template Result Test() { if (type_ && type_->category() == T::category) { if constexpr (T::category == TypeCategory::Derived) { if (type_->IsUnlimitedPolymorphic()) { return std::nullopt; } else { return AsMaybeExpr(ArrayConstructor{type_->GetDerivedTypeSpec(), MakeSpecific(std::move(values_))}); } } else if (type_->kind() == T::kind) { if constexpr (T::category == TypeCategory::Character) { if (auto len{type_->LEN()}) { return AsMaybeExpr(ArrayConstructor{ *std::move(len), MakeSpecific(std::move(values_))}); } } else { return AsMaybeExpr( ArrayConstructor{MakeSpecific(std::move(values_))}); } } } return std::nullopt; } private: void Push(MaybeExpr &&); template std::optional>> GetSpecificIntExpr( const A &x) { if (MaybeExpr y{exprAnalyzer_.Analyze(x)}) { Expr *intExpr{UnwrapExpr>(*y)}; return ConvertToType>( std::move(DEREF(intExpr))); } return std::nullopt; } // Nested array constructors all reference the same ExpressionAnalyzer, // which represents the nest of active implied DO loop indices. ExpressionAnalyzer &exprAnalyzer_; std::optional type_; bool explicitType_{type_.has_value()}; std::optional constantLength_; ArrayConstructorValues values_; bool messageDisplayedOnce{false}; }; void ArrayConstructorContext::Push(MaybeExpr &&x) { if (!x) { return; } if (auto dyType{x->GetType()}) { DynamicTypeWithLength xType{*dyType}; if (Expr * charExpr{UnwrapExpr>(*x)}) { CHECK(xType.category() == TypeCategory::Character); xType.length = std::visit([](const auto &kc) { return kc.LEN(); }, charExpr->u); } if (!type_) { // If there is no explicit type-spec in an array constructor, the type // of the array is the declared type of all of the elements, which must // be well-defined and all match. // TODO: Possible language extension: use the most general type of // the values as the type of a numeric constructed array, convert all // of the other values to that type. Alternative: let the first value // determine the type, and convert the others to that type. CHECK(!explicitType_); type_ = std::move(xType); constantLength_ = ToInt64(type_->length); values_.Push(std::move(*x)); } else if (!explicitType_) { if (static_cast(*type_) == static_cast(xType)) { values_.Push(std::move(*x)); if (auto thisLen{ToInt64(xType.LEN())}) { if (constantLength_) { if (exprAnalyzer_.context().warnOnNonstandardUsage() && *thisLen != *constantLength_) { exprAnalyzer_.Say( "Character literal in array constructor without explicit " "type has different length than earlier element"_en_US); } if (*thisLen > *constantLength_) { // Language extension: use the longest literal to determine the // length of the array constructor's character elements, not the // first, when there is no explicit type. *constantLength_ = *thisLen; type_->length = xType.LEN(); } } else { constantLength_ = *thisLen; type_->length = xType.LEN(); } } } else { if (!messageDisplayedOnce) { exprAnalyzer_.Say( "Values in array constructor must have the same declared type " "when no explicit type appears"_err_en_US); // C7110 messageDisplayedOnce = true; } } } else { if (auto cast{ConvertToType(*type_, std::move(*x))}) { values_.Push(std::move(*cast)); } else { exprAnalyzer_.Say( "Value in array constructor of type '%s' could not " "be converted to the type of the array '%s'"_err_en_US, x->GetType()->AsFortran(), type_->AsFortran()); // C7111, C7112 } } } } void ArrayConstructorContext::Add(const parser::AcValue &x) { using IntType = ResultType; std::visit( common::visitors{ [&](const parser::AcValue::Triplet &triplet) { // Transform l:u(:s) into (_,_=l,u(,s)) with an anonymous index '_' std::optional> lower{ GetSpecificIntExpr(std::get<0>(triplet.t))}; std::optional> upper{ GetSpecificIntExpr(std::get<1>(triplet.t))}; std::optional> stride{ GetSpecificIntExpr(std::get<2>(triplet.t))}; if (lower && upper) { if (!stride) { stride = Expr{1}; } if (!type_) { type_ = DynamicTypeWithLength{IntType::GetType()}; } auto v{std::move(values_)}; parser::CharBlock anonymous; Push(Expr{ Expr{Expr{ImpliedDoIndex{anonymous}}}}); std::swap(v, values_); values_.Push(ImpliedDo{anonymous, std::move(*lower), std::move(*upper), std::move(*stride), std::move(v)}); } }, [&](const common::Indirection &expr) { auto restorer{exprAnalyzer_.GetContextualMessages().SetLocation( expr.value().source)}; if (MaybeExpr v{exprAnalyzer_.Analyze(expr.value())}) { if (auto exprType{v->GetType()}) { if (exprType->IsUnlimitedPolymorphic()) { exprAnalyzer_.Say( "Cannot have an unlimited polymorphic value in an " "array constructor"_err_en_US); // C7113 } } Push(std::move(*v)); } }, [&](const common::Indirection &impliedDo) { const auto &control{ std::get(impliedDo.value().t)}; const auto &bounds{ std::get(control.t)}; exprAnalyzer_.Analyze(bounds.name); parser::CharBlock name{bounds.name.thing.thing.source}; const Symbol *symbol{bounds.name.thing.thing.symbol}; int kind{IntType::kind}; if (const auto dynamicType{DynamicType::From(symbol)}) { kind = dynamicType->kind(); } if (exprAnalyzer_.AddImpliedDo(name, kind)) { std::optional> lower{ GetSpecificIntExpr(bounds.lower)}; std::optional> upper{ GetSpecificIntExpr(bounds.upper)}; if (lower && upper) { std::optional> stride{ GetSpecificIntExpr(bounds.step)}; auto v{std::move(values_)}; for (const auto &value : std::get>(impliedDo.value().t)) { Add(value); } if (!stride) { stride = Expr{1}; } std::swap(v, values_); values_.Push(ImpliedDo{name, std::move(*lower), std::move(*upper), std::move(*stride), std::move(v)}); } exprAnalyzer_.RemoveImpliedDo(name); } else { exprAnalyzer_.SayAt(name, "Implied DO index is active in surrounding implied DO loop " "and may not have the same name"_err_en_US); // C7115 } }, }, x.u); } MaybeExpr ArrayConstructorContext::ToExpr() { return common::SearchTypes(std::move(*this)); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::ArrayConstructor &array) { const parser::AcSpec &acSpec{array.v}; ArrayConstructorContext acContext{*this, AnalyzeTypeSpec(acSpec.type)}; for (const parser::AcValue &value : acSpec.values) { acContext.Add(value); } return acContext.ToExpr(); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::StructureConstructor &structure) { auto &parsedType{std::get(structure.t)}; parser::CharBlock typeName{std::get(parsedType.t).source}; if (!parsedType.derivedTypeSpec) { return std::nullopt; } const auto &spec{*parsedType.derivedTypeSpec}; const Symbol &typeSymbol{spec.typeSymbol()}; if (!spec.scope() || !typeSymbol.has()) { return std::nullopt; // error recovery } const auto &typeDetails{typeSymbol.get()}; const Symbol *parentComponent{typeDetails.GetParentComponent(*spec.scope())}; if (typeSymbol.attrs().test(semantics::Attr::ABSTRACT)) { // C796 AttachDeclaration(Say(typeName, "ABSTRACT derived type '%s' may not be used in a " "structure constructor"_err_en_US, typeName), typeSymbol); // C7114 } // This iterator traverses all of the components in the derived type and its // parents. The symbols for whole parent components appear after their // own components and before the components of the types that extend them. // E.g., TYPE :: A; REAL X; END TYPE // TYPE, EXTENDS(A) :: B; REAL Y; END TYPE // produces the component list X, A, Y. // The order is important below because a structure constructor can // initialize X or A by name, but not both. auto components{semantics::OrderedComponentIterator{spec}}; auto nextAnonymous{components.begin()}; std::set unavailable; bool anyKeyword{false}; StructureConstructor result{spec}; bool checkConflicts{true}; // until we hit one auto &messages{GetContextualMessages()}; for (const auto &component : std::get>(structure.t)) { const parser::Expr &expr{ std::get(component.t).v.value()}; parser::CharBlock source{expr.source}; auto restorer{messages.SetLocation(source)}; const Symbol *symbol{nullptr}; MaybeExpr value{Analyze(expr)}; std::optional valueType{DynamicType::From(value)}; if (const auto &kw{std::get>(component.t)}) { anyKeyword = true; source = kw->v.source; symbol = kw->v.symbol; if (!symbol) { auto componentIter{std::find_if(components.begin(), components.end(), [=](const Symbol &symbol) { return symbol.name() == source; })}; if (componentIter != components.end()) { symbol = &*componentIter; } } if (!symbol) { // C7101 Say(source, "Keyword '%s=' does not name a component of derived type '%s'"_err_en_US, source, typeName); } } else { if (anyKeyword) { // C7100 Say(source, "Value in structure constructor lacks a component name"_err_en_US); checkConflicts = false; // stem cascade } // Here's a regrettably common extension of the standard: anonymous // initialization of parent components, e.g., T(PT(1)) rather than // T(1) or T(PT=PT(1)). if (nextAnonymous == components.begin() && parentComponent && valueType == DynamicType::From(*parentComponent) && context().IsEnabled(LanguageFeature::AnonymousParents)) { auto iter{ std::find(components.begin(), components.end(), *parentComponent)}; if (iter != components.end()) { symbol = parentComponent; nextAnonymous = ++iter; if (context().ShouldWarn(LanguageFeature::AnonymousParents)) { Say(source, "Whole parent component '%s' in structure " "constructor should not be anonymous"_en_US, symbol->name()); } } } while (!symbol && nextAnonymous != components.end()) { const Symbol &next{*nextAnonymous}; ++nextAnonymous; if (!next.test(Symbol::Flag::ParentComp)) { symbol = &next; } } if (!symbol) { Say(source, "Unexpected value in structure constructor"_err_en_US); } } if (symbol) { if (const auto *currScope{context_.globalScope().FindScope(source)}) { if (auto msg{CheckAccessibleComponent(*currScope, *symbol)}) { Say(source, *msg); } } if (checkConflicts) { auto componentIter{ std::find(components.begin(), components.end(), *symbol)}; if (unavailable.find(symbol->name()) != unavailable.cend()) { // C797, C798 Say(source, "Component '%s' conflicts with another component earlier in " "this structure constructor"_err_en_US, symbol->name()); } else if (symbol->test(Symbol::Flag::ParentComp)) { // Make earlier components unavailable once a whole parent appears. for (auto it{components.begin()}; it != componentIter; ++it) { unavailable.insert(it->name()); } } else { // Make whole parent components unavailable after any of their // constituents appear. for (auto it{componentIter}; it != components.end(); ++it) { if (it->test(Symbol::Flag::ParentComp)) { unavailable.insert(it->name()); } } } } unavailable.insert(symbol->name()); if (value) { if (symbol->has()) { CHECK(IsPointer(*symbol)); } else if (symbol->has()) { // C1594(4) const auto &innermost{context_.FindScope(expr.source)}; if (const auto *pureProc{FindPureProcedureContaining(innermost)}) { if (const Symbol * pointer{FindPointerComponent(*symbol)}) { if (const Symbol * object{FindExternallyVisibleObject(*value, *pureProc)}) { if (auto *msg{Say(expr.source, "Externally visible object '%s' may not be " "associated with pointer component '%s' in a " "pure procedure"_err_en_US, object->name(), pointer->name())}) { msg->Attach(object->name(), "Object declaration"_en_US) .Attach(pointer->name(), "Pointer declaration"_en_US); } } } } } else if (symbol->has()) { Say(expr.source, "Type parameter '%s' may not appear as a component " "of a structure constructor"_err_en_US, symbol->name()); continue; } else { Say(expr.source, "Component '%s' is neither a procedure pointer " "nor a data object"_err_en_US, symbol->name()); continue; } if (IsPointer(*symbol)) { semantics::CheckPointerAssignment( GetFoldingContext(), *symbol, *value); // C7104, C7105 result.Add(*symbol, Fold(std::move(*value))); } else if (MaybeExpr converted{ ConvertToType(*symbol, std::move(*value))}) { if (auto componentShape{GetShape(GetFoldingContext(), *symbol)}) { if (auto valueShape{GetShape(GetFoldingContext(), *converted)}) { if (GetRank(*componentShape) == 0 && GetRank(*valueShape) > 0) { AttachDeclaration( Say(expr.source, "Rank-%d array value is not compatible with scalar component '%s'"_err_en_US, GetRank(*valueShape), symbol->name()), *symbol); } else if (CheckConformance(messages, *componentShape, *valueShape, "component", "value")) { if (GetRank(*componentShape) > 0 && GetRank(*valueShape) == 0 && !IsExpandableScalar(*converted)) { AttachDeclaration( Say(expr.source, "Scalar value cannot be expanded to shape of array component '%s'"_err_en_US, symbol->name()), *symbol); } else { result.Add(*symbol, std::move(*converted)); } } } else { Say(expr.source, "Shape of value cannot be determined"_err_en_US); } } else { AttachDeclaration( Say(expr.source, "Shape of component '%s' cannot be determined"_err_en_US, symbol->name()), *symbol); } } else if (IsAllocatable(*symbol) && std::holds_alternative(value->u)) { // NULL() with no arguments allowed by 7.5.10 para 6 for ALLOCATABLE } else if (auto symType{DynamicType::From(symbol)}) { if (valueType) { AttachDeclaration( Say(expr.source, "Value in structure constructor of type %s is " "incompatible with component '%s' of type %s"_err_en_US, valueType->AsFortran(), symbol->name(), symType->AsFortran()), *symbol); } else { AttachDeclaration( Say(expr.source, "Value in structure constructor is incompatible with " " component '%s' of type %s"_err_en_US, symbol->name(), symType->AsFortran()), *symbol); } } } } } // Ensure that unmentioned component objects have default initializers. for (const Symbol &symbol : components) { if (!symbol.test(Symbol::Flag::ParentComp) && unavailable.find(symbol.name()) == unavailable.cend() && !IsAllocatable(symbol)) { if (const auto *details{ symbol.detailsIf()}) { if (details->init()) { result.Add(symbol, common::Clone(*details->init())); } else { // C799 AttachDeclaration(Say(typeName, "Structure constructor lacks a value for " "component '%s'"_err_en_US, symbol.name()), symbol); } } } } return AsMaybeExpr(Expr{std::move(result)}); } static std::optional GetPassName( const semantics::Symbol &proc) { return std::visit( [](const auto &details) { if constexpr (std::is_base_of_v>) { return details.passName(); } else { return std::optional{}; } }, proc.details()); } static int GetPassIndex(const Symbol &proc) { CHECK(!proc.attrs().test(semantics::Attr::NOPASS)); std::optional passName{GetPassName(proc)}; const auto *interface{semantics::FindInterface(proc)}; if (!passName || !interface) { return 0; // first argument is passed-object } const auto &subp{interface->get()}; int index{0}; for (const auto *arg : subp.dummyArgs()) { if (arg && arg->name() == passName) { return index; } ++index; } DIE("PASS argument name not in dummy argument list"); } // Injects an expression into an actual argument list as the "passed object" // for a type-bound procedure reference that is not NOPASS. Adds an // argument keyword if possible, but not when the passed object goes // before a positional argument. // e.g., obj%tbp(x) -> tbp(obj,x). static void AddPassArg(ActualArguments &actuals, const Expr &expr, const Symbol &component, bool isPassedObject = true) { if (component.attrs().test(semantics::Attr::NOPASS)) { return; } int passIndex{GetPassIndex(component)}; auto iter{actuals.begin()}; int at{0}; while (iter < actuals.end() && at < passIndex) { if (*iter && (*iter)->keyword()) { iter = actuals.end(); break; } ++iter; ++at; } ActualArgument passed{AsGenericExpr(common::Clone(expr))}; passed.set_isPassedObject(isPassedObject); if (iter == actuals.end()) { if (auto passName{GetPassName(component)}) { passed.set_keyword(*passName); } } actuals.emplace(iter, std::move(passed)); } // Return the compile-time resolution of a procedure binding, if possible. static const Symbol *GetBindingResolution( const std::optional &baseType, const Symbol &component) { const auto *binding{component.detailsIf()}; if (!binding) { return nullptr; } if (!component.attrs().test(semantics::Attr::NON_OVERRIDABLE) && (!baseType || baseType->IsPolymorphic())) { return nullptr; } return &binding->symbol(); } auto ExpressionAnalyzer::AnalyzeProcedureComponentRef( const parser::ProcComponentRef &pcr, ActualArguments &&arguments) -> std::optional { const parser::StructureComponent &sc{pcr.v.thing}; if (MaybeExpr base{Analyze(sc.base)}) { if (const Symbol * sym{sc.component.symbol}) { if (auto *dtExpr{UnwrapExpr>(*base)}) { if (sym->has()) { AdjustActuals adjustment{ [&](const Symbol &proc, ActualArguments &actuals) { if (!proc.attrs().test(semantics::Attr::NOPASS)) { AddPassArg(actuals, std::move(*dtExpr), proc); } return true; }}; sym = ResolveGeneric(*sym, arguments, adjustment); if (!sym) { EmitGenericResolutionError(*sc.component.symbol); return std::nullopt; } } if (const Symbol * resolution{GetBindingResolution(dtExpr->GetType(), *sym)}) { AddPassArg(arguments, std::move(*dtExpr), *sym, false); return CalleeAndArguments{ ProcedureDesignator{*resolution}, std::move(arguments)}; } else if (std::optional dataRef{ ExtractDataRef(std::move(*dtExpr))}) { if (sym->attrs().test(semantics::Attr::NOPASS)) { return CalleeAndArguments{ ProcedureDesignator{Component{std::move(*dataRef), *sym}}, std::move(arguments)}; } else { AddPassArg(arguments, Expr{Designator{std::move(*dataRef)}}, *sym); return CalleeAndArguments{ ProcedureDesignator{*sym}, std::move(arguments)}; } } } Say(sc.component.source, "Base of procedure component reference is not a derived-type object"_err_en_US); } } CHECK(!GetContextualMessages().empty()); return std::nullopt; } // Can actual be argument associated with dummy? static bool CheckCompatibleArgument(bool isElemental, const ActualArgument &actual, const characteristics::DummyArgument &dummy) { return std::visit( common::visitors{ [&](const characteristics::DummyDataObject &x) { characteristics::TypeAndShape dummyTypeAndShape{x.type}; if (!isElemental && actual.Rank() != dummyTypeAndShape.Rank()) { return false; } else if (auto actualType{actual.GetType()}) { return dummyTypeAndShape.type().IsTkCompatibleWith(*actualType); } else { return false; } }, [&](const characteristics::DummyProcedure &) { const auto *expr{actual.UnwrapExpr()}; return expr && IsProcedurePointer(*expr); }, [&](const characteristics::AlternateReturn &) { return actual.isAlternateReturn(); }, }, dummy.u); } // Are the actual arguments compatible with the dummy arguments of procedure? static bool CheckCompatibleArguments( const characteristics::Procedure &procedure, const ActualArguments &actuals) { bool isElemental{procedure.IsElemental()}; const auto &dummies{procedure.dummyArguments}; CHECK(dummies.size() == actuals.size()); for (std::size_t i{0}; i < dummies.size(); ++i) { const characteristics::DummyArgument &dummy{dummies[i]}; const std::optional &actual{actuals[i]}; if (actual && !CheckCompatibleArgument(isElemental, *actual, dummy)) { return false; } } return true; } // Handles a forward reference to a module function from what must // be a specification expression. Return false if the symbol is // an invalid forward reference. bool ExpressionAnalyzer::ResolveForward(const Symbol &symbol) { if (context_.HasError(symbol)) { return false; } if (const auto *details{ symbol.detailsIf()}) { if (details->kind() == semantics::SubprogramKind::Module) { // If this symbol is still a SubprogramNameDetails, we must be // checking a specification expression in a sibling module // procedure. Resolve its names now so that its interface // is known. semantics::ResolveSpecificationParts(context_, symbol); if (symbol.has()) { // When the symbol hasn't had its details updated, we must have // already been in the process of resolving the function's // specification part; but recursive function calls are not // allowed in specification parts (10.1.11 para 5). Say("The module function '%s' may not be referenced recursively in a specification expression"_err_en_US, symbol.name()); context_.SetError(symbol); return false; } } else { // 10.1.11 para 4 Say("The internal function '%s' may not be referenced in a specification expression"_err_en_US, symbol.name()); context_.SetError(symbol); return false; } } return true; } // Resolve a call to a generic procedure with given actual arguments. // adjustActuals is called on procedure bindings to handle pass arg. const Symbol *ExpressionAnalyzer::ResolveGeneric(const Symbol &symbol, const ActualArguments &actuals, const AdjustActuals &adjustActuals, bool mightBeStructureConstructor) { const Symbol *elemental{nullptr}; // matching elemental specific proc const auto &details{symbol.GetUltimate().get()}; for (const Symbol &specific : details.specificProcs()) { if (!ResolveForward(specific)) { continue; } if (std::optional procedure{ characteristics::Procedure::Characterize( ProcedureDesignator{specific}, context_.intrinsics())}) { ActualArguments localActuals{actuals}; if (specific.has()) { if (!adjustActuals.value()(specific, localActuals)) { continue; } } if (semantics::CheckInterfaceForGeneric( *procedure, localActuals, GetFoldingContext())) { if (CheckCompatibleArguments(*procedure, localActuals)) { if (!procedure->IsElemental()) { return &specific; // takes priority over elemental match } elemental = &specific; } } } } if (elemental) { return elemental; } // Check parent derived type if (const auto *parentScope{symbol.owner().GetDerivedTypeParent()}) { if (const Symbol * extended{parentScope->FindComponent(symbol.name())}) { if (extended->GetUltimate().has()) { if (const Symbol * result{ResolveGeneric(*extended, actuals, adjustActuals, false)}) { return result; } } } } if (mightBeStructureConstructor && details.derivedType()) { return details.derivedType(); } return nullptr; } void ExpressionAnalyzer::EmitGenericResolutionError(const Symbol &symbol) { if (semantics::IsGenericDefinedOp(symbol)) { Say("No specific procedure of generic operator '%s' matches the actual arguments"_err_en_US, symbol.name()); } else { Say("No specific procedure of generic '%s' matches the actual arguments"_err_en_US, symbol.name()); } } auto ExpressionAnalyzer::GetCalleeAndArguments( const parser::ProcedureDesignator &pd, ActualArguments &&arguments, bool isSubroutine, bool mightBeStructureConstructor) -> std::optional { return std::visit( common::visitors{ [&](const parser::Name &name) { return GetCalleeAndArguments(name, std::move(arguments), isSubroutine, mightBeStructureConstructor); }, [&](const parser::ProcComponentRef &pcr) { return AnalyzeProcedureComponentRef(pcr, std::move(arguments)); }, }, pd.u); } auto ExpressionAnalyzer::GetCalleeAndArguments(const parser::Name &name, ActualArguments &&arguments, bool isSubroutine, bool mightBeStructureConstructor) -> std::optional { const Symbol *symbol{name.symbol}; if (context_.HasError(symbol)) { return std::nullopt; // also handles null symbol } const Symbol &ultimate{DEREF(symbol).GetUltimate()}; if (ultimate.attrs().test(semantics::Attr::INTRINSIC)) { if (std::optional specificCall{context_.intrinsics().Probe( CallCharacteristics{ultimate.name().ToString(), isSubroutine}, arguments, GetFoldingContext())}) { return CalleeAndArguments{ ProcedureDesignator{std::move(specificCall->specificIntrinsic)}, std::move(specificCall->arguments)}; } } else { CheckForBadRecursion(name.source, ultimate); if (ultimate.has()) { ExpressionAnalyzer::AdjustActuals noAdjustment; symbol = ResolveGeneric( *symbol, arguments, noAdjustment, mightBeStructureConstructor); } if (symbol) { if (symbol->GetUltimate().has()) { if (mightBeStructureConstructor) { return CalleeAndArguments{ semantics::SymbolRef{*symbol}, std::move(arguments)}; } } else { return CalleeAndArguments{ ProcedureDesignator{*symbol}, std::move(arguments)}; } } else if (std::optional specificCall{ context_.intrinsics().Probe( CallCharacteristics{ ultimate.name().ToString(), isSubroutine}, arguments, GetFoldingContext())}) { // Generics can extend intrinsics return CalleeAndArguments{ ProcedureDesignator{std::move(specificCall->specificIntrinsic)}, std::move(specificCall->arguments)}; } else { EmitGenericResolutionError(*name.symbol); } } return std::nullopt; } void ExpressionAnalyzer::CheckForBadRecursion( parser::CharBlock callSite, const semantics::Symbol &proc) { if (const auto *scope{proc.scope()}) { if (scope->sourceRange().Contains(callSite)) { parser::Message *msg{nullptr}; if (proc.attrs().test(semantics::Attr::NON_RECURSIVE)) { // 15.6.2.1(3) msg = Say("NON_RECURSIVE procedure '%s' cannot call itself"_err_en_US, callSite); } else if (IsAssumedLengthCharacter(proc) && IsExternal(proc)) { msg = Say( // 15.6.2.1(3) "Assumed-length CHARACTER(*) function '%s' cannot call itself"_err_en_US, callSite); } AttachDeclaration(msg, proc); } } } template static const Symbol *AssumedTypeDummy(const A &x) { if (const auto *designator{ std::get_if>(&x.u)}) { if (const auto *dataRef{ std::get_if(&designator->value().u)}) { if (const auto *name{std::get_if(&dataRef->u)}) { if (const Symbol * symbol{name->symbol}) { if (const auto *type{symbol->GetType()}) { if (type->category() == semantics::DeclTypeSpec::TypeStar) { return symbol; } } } } } } return nullptr; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::FunctionReference &funcRef, std::optional *structureConstructor) { const parser::Call &call{funcRef.v}; auto restorer{GetContextualMessages().SetLocation(call.source)}; ArgumentAnalyzer analyzer{*this, call.source, true /* isProcedureCall */}; for (const auto &arg : std::get>(call.t)) { analyzer.Analyze(arg, false /* not subroutine call */); } if (analyzer.fatalErrors()) { return std::nullopt; } if (std::optional callee{ GetCalleeAndArguments(std::get(call.t), analyzer.GetActuals(), false /* not subroutine */, true /* might be structure constructor */)}) { if (auto *proc{std::get_if(&callee->u)}) { return MakeFunctionRef( call.source, std::move(*proc), std::move(callee->arguments)); } else if (structureConstructor) { // Structure constructor misparsed as function reference? CHECK(std::holds_alternative(callee->u)); const Symbol &derivedType{*std::get(callee->u)}; const auto &designator{std::get(call.t)}; if (const auto *name{std::get_if(&designator.u)}) { semantics::Scope &scope{context_.FindScope(name->source)}; semantics::DerivedTypeSpec dtSpec{ name->source, derivedType.GetUltimate()}; if (dtSpec.IsForwardReferenced()) { Say(call.source, "Cannot construct value for derived type '%s' " "before it is defined"_err_en_US, name->source); return std::nullopt; } const semantics::DeclTypeSpec &type{ semantics::FindOrInstantiateDerivedType( scope, std::move(dtSpec), context_)}; auto &mutableRef{const_cast(funcRef)}; *structureConstructor = mutableRef.ConvertToStructureConstructor(type.derivedTypeSpec()); return Analyze(structureConstructor->value()); } } } return std::nullopt; } void ExpressionAnalyzer::Analyze(const parser::CallStmt &callStmt) { const parser::Call &call{callStmt.v}; auto restorer{GetContextualMessages().SetLocation(call.source)}; ArgumentAnalyzer analyzer{*this, call.source, true /* isProcedureCall */}; const auto &actualArgList{std::get>(call.t)}; for (const auto &arg : actualArgList) { analyzer.Analyze(arg, true /* is subroutine call */); } if (!analyzer.fatalErrors()) { if (std::optional callee{ GetCalleeAndArguments(std::get(call.t), analyzer.GetActuals(), true /* subroutine */)}) { ProcedureDesignator *proc{std::get_if(&callee->u)}; CHECK(proc); if (CheckCall(call.source, *proc, callee->arguments)) { bool hasAlternateReturns{ callee->arguments.size() < actualArgList.size()}; callStmt.typedCall.Reset( new ProcedureRef{std::move(*proc), std::move(callee->arguments), hasAlternateReturns}, ProcedureRef::Deleter); } } } } const Assignment *ExpressionAnalyzer::Analyze(const parser::AssignmentStmt &x) { if (!x.typedAssignment) { ArgumentAnalyzer analyzer{*this}; analyzer.Analyze(std::get(x.t)); analyzer.Analyze(std::get(x.t)); if (analyzer.fatalErrors()) { x.typedAssignment.Reset( new GenericAssignmentWrapper{}, GenericAssignmentWrapper::Deleter); } else { std::optional procRef{analyzer.TryDefinedAssignment()}; Assignment assignment{ Fold(analyzer.MoveExpr(0)), Fold(analyzer.MoveExpr(1))}; if (procRef) { assignment.u = std::move(*procRef); } x.typedAssignment.Reset( new GenericAssignmentWrapper{std::move(assignment)}, GenericAssignmentWrapper::Deleter); } } return common::GetPtrFromOptional(x.typedAssignment->v); } const Assignment *ExpressionAnalyzer::Analyze( const parser::PointerAssignmentStmt &x) { if (!x.typedAssignment) { MaybeExpr lhs{Analyze(std::get(x.t))}; MaybeExpr rhs{Analyze(std::get(x.t))}; if (!lhs || !rhs) { x.typedAssignment.Reset( new GenericAssignmentWrapper{}, GenericAssignmentWrapper::Deleter); } else { Assignment assignment{std::move(*lhs), std::move(*rhs)}; std::visit(common::visitors{ [&](const std::list &list) { Assignment::BoundsRemapping bounds; for (const auto &elem : list) { auto lower{AsSubscript(Analyze(std::get<0>(elem.t)))}; auto upper{AsSubscript(Analyze(std::get<1>(elem.t)))}; if (lower && upper) { bounds.emplace_back(Fold(std::move(*lower)), Fold(std::move(*upper))); } } assignment.u = std::move(bounds); }, [&](const std::list &list) { Assignment::BoundsSpec bounds; for (const auto &bound : list) { if (auto lower{AsSubscript(Analyze(bound.v))}) { bounds.emplace_back(Fold(std::move(*lower))); } } assignment.u = std::move(bounds); }, }, std::get(x.t).u); x.typedAssignment.Reset( new GenericAssignmentWrapper{std::move(assignment)}, GenericAssignmentWrapper::Deleter); } } return common::GetPtrFromOptional(x.typedAssignment->v); } static bool IsExternalCalledImplicitly( parser::CharBlock callSite, const ProcedureDesignator &proc) { if (const auto *symbol{proc.GetSymbol()}) { return symbol->has() && symbol->owner().IsGlobal() && (!symbol->scope() /*ENTRY*/ || !symbol->scope()->sourceRange().Contains(callSite)); } else { return false; } } std::optional ExpressionAnalyzer::CheckCall( parser::CharBlock callSite, const ProcedureDesignator &proc, ActualArguments &arguments) { auto chars{ characteristics::Procedure::Characterize(proc, context_.intrinsics())}; if (chars) { bool treatExternalAsImplicit{IsExternalCalledImplicitly(callSite, proc)}; if (treatExternalAsImplicit && !chars->CanBeCalledViaImplicitInterface()) { Say(callSite, "References to the procedure '%s' require an explicit interface"_en_US, DEREF(proc.GetSymbol()).name()); } semantics::CheckArguments(*chars, arguments, GetFoldingContext(), context_.FindScope(callSite), treatExternalAsImplicit); const Symbol *procSymbol{proc.GetSymbol()}; if (procSymbol && !IsPureProcedure(*procSymbol)) { if (const semantics::Scope * pure{semantics::FindPureProcedureContaining( context_.FindScope(callSite))}) { Say(callSite, "Procedure '%s' referenced in pure subprogram '%s' must be pure too"_err_en_US, procSymbol->name(), DEREF(pure->symbol()).name()); } } } return chars; } // Unary operations MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::Parentheses &x) { if (MaybeExpr operand{Analyze(x.v.value())}) { if (const semantics::Symbol * symbol{GetLastSymbol(*operand)}) { if (const semantics::Symbol * result{FindFunctionResult(*symbol)}) { if (semantics::IsProcedurePointer(*result)) { Say("A function reference that returns a procedure " "pointer may not be parenthesized"_err_en_US); // C1003 } } } return Parenthesize(std::move(*operand)); } return std::nullopt; } static MaybeExpr NumericUnaryHelper(ExpressionAnalyzer &context, NumericOperator opr, const parser::Expr::IntrinsicUnary &x) { ArgumentAnalyzer analyzer{context}; analyzer.Analyze(x.v); if (analyzer.fatalErrors()) { return std::nullopt; } else if (analyzer.IsIntrinsicNumeric(opr)) { if (opr == NumericOperator::Add) { return analyzer.MoveExpr(0); } else { return Negation(context.GetContextualMessages(), analyzer.MoveExpr(0)); } } else { return analyzer.TryDefinedOp(AsFortran(opr), "Operand of unary %s must be numeric; have %s"_err_en_US); } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::UnaryPlus &x) { return NumericUnaryHelper(*this, NumericOperator::Add, x); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::Negate &x) { return NumericUnaryHelper(*this, NumericOperator::Subtract, x); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::NOT &x) { ArgumentAnalyzer analyzer{*this}; analyzer.Analyze(x.v); if (analyzer.fatalErrors()) { return std::nullopt; } else if (analyzer.IsIntrinsicLogical()) { return AsGenericExpr( LogicalNegation(std::get>(analyzer.MoveExpr(0).u))); } else { return analyzer.TryDefinedOp(LogicalOperator::Not, "Operand of %s must be LOGICAL; have %s"_err_en_US); } } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::PercentLoc &x) { // Represent %LOC() exactly as if it had been a call to the LOC() extension // intrinsic function. // Use the actual source for the name of the call for error reporting. std::optional arg; if (const Symbol * assumedTypeDummy{AssumedTypeDummy(x.v.value())}) { arg = ActualArgument{ActualArgument::AssumedType{*assumedTypeDummy}}; } else if (MaybeExpr argExpr{Analyze(x.v.value())}) { arg = ActualArgument{std::move(*argExpr)}; } else { return std::nullopt; } parser::CharBlock at{GetContextualMessages().at()}; CHECK(at.size() >= 4); parser::CharBlock loc{at.begin() + 1, 3}; CHECK(loc == "loc"); return MakeFunctionRef(loc, ActualArguments{std::move(*arg)}); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::DefinedUnary &x) { const auto &name{std::get(x.t).v}; ArgumentAnalyzer analyzer{*this, name.source}; analyzer.Analyze(std::get<1>(x.t)); return analyzer.TryDefinedOp(name.source.ToString().c_str(), "No operator %s defined for %s"_err_en_US, true); } // Binary (dyadic) operations template