//===-- 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/Fortran.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; } else { return GetCharLength(); } } 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}, source_{context.GetContextualMessages().at()}, 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::optional &thisType, std::size_t i, std::optional otherType); bool IsIntrinsicRelational( RelationalOperator, const DynamicType &, const DynamicType &) const; bool IsIntrinsicLogical() const; bool IsIntrinsicNumeric(NumericOperator) const; bool IsIntrinsicConcat() const; bool CheckConformance(); bool CheckForNullPointer(const char *where = "as an operand here"); // 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, const Symbol **definedOpSymbolPtr = nullptr, bool isUserOp = false); template MaybeExpr TryDefinedOp(E opr, parser::MessageFixedText msg) { return TryDefinedOp( context_.context().languageFeatures().GetNames(opr), 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, const Symbol *&definedOp); 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 evaluate::IsBOZLiteral(GetExpr(i)); } void SayNoMatch(const std::string &, bool isAssignment = false); std::string TypeAsFortran(std::size_t); bool AnyUntypedOrMissingOperand(); ExpressionAnalyzer &context_; ActualArguments actuals_; parser::CharBlock source_; bool fatalErrors_{false}; const bool isProcedureCall_; // false for user-defined op or assignment }; // 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 &last{ref.GetLastSymbol()}; const Symbol &symbol{BypassGeneric(last).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)) { if (symbol.has()) { Say("'%s' is not a specific procedure"_err_en_US, symbol.name()); } else { return Expr{ProcedureDesignator{symbol}}; } } else if (auto interface{context_.intrinsics().IsSpecificIntrinsicFunction( symbol.name().ToString())}; interface && !interface->isRestrictedSpecific) { SpecificIntrinsic intrinsic{ symbol.name().ToString(), std::move(*interface)}; intrinsic.isRestrictedSpecific = interface->isRestrictedSpecific; return Expr{ProcedureDesignator{std::move(intrinsic)}}; } else { Say("'%s' is not an unrestricted specific intrinsic procedure"_err_en_US, symbol.name()); } return std::nullopt; } else if (MaybeExpr result{AsGenericExpr(std::move(ref))}) { return result; } else { if (!context_.HasError(last) && !context_.HasError(symbol)) { AttachDeclaration( Say("'%s' is not an object that can appear in an expression"_err_en_US, last.name()), symbol); context_.SetError(last); } 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()}; int symbolRank{symbol.Rank()}; int subscripts{static_cast(ref.size())}; if (subscripts == 0) { return std::nullopt; // error recovery } 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) { // C919a 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 (const auto *object{ symbol.detailsIf()}) { // 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; } } } else { // Shouldn't get here from Analyze(ArrayElement) without a valid base, // which, if not an object, must be a construct entity from // SELECT TYPE/RANK or ASSOCIATE. CHECK(symbol.has()); } return Designate(DataRef{std::move(ref)}); } // Applies subscripts to a data reference. MaybeExpr ExpressionAnalyzer::ApplySubscripts( DataRef &&dataRef, std::vector &&subscripts) { if (subscripts.empty()) { return std::nullopt; // error recovery } 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) { // C919a 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) { auto restorer{GetContextualMessages().SetLocation(n.source)}; 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) { auto restorer{GetContextualMessages().SetLocation(n.v.source)}; 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 &n) { if (MaybeExpr value{Analyze(n.v)}) { // Subtle: when the NullInit is a DataStmtConstant, it might // be a misparse of a structure constructor without parameters // or components (e.g., T()). Checking the result to ensure // that a "=>" data entity initializer actually resolved to // a null pointer has to be done by the caller. return Fold(std::move(*value)); } return std::nullopt; } 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) { 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 } else if (baseExpr->Rank() == 0) { if (const Symbol * symbol{GetLastSymbol(*baseExpr)}) { if (!context_.HasError(symbol)) { if (inDataStmtConstant_) { // Better error for NULL(X) with a MOLD= argument Say("'%s' must be an array or structure constructor if used with non-empty parentheses as a DATA statement constant"_err_en_US, symbol->name()); } else { 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. std::optional ExpressionAnalyzer::CreateComponent( DataRef &&base, const Symbol &component, const semantics::Scope &scope) { if (IsAllocatableOrPointer(component) && base.Rank() > 0) { // C919b Say("An allocatable or pointer component reference must be applied to a scalar base"_err_en_US); } 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)}; Symbol *sym{sc.component.symbol}; if (!base || !sym || 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) { auto restorer{GetContextualMessages().SetLocation(name)}; 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 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: using ImpliedDoIntType = ResultType; void Push(MaybeExpr &&); void Add(const parser::AcValue::Triplet &); void Add(const parser::Expr &); void Add(const parser::AcImpliedDo &); void UnrollConstantImpliedDo(const parser::AcImpliedDo &, parser::CharBlock name, std::int64_t lower, std::int64_t upper, std::int64_t stride); template std::optional>> GetSpecificIntExpr( const A &x) { if (MaybeExpr y{exprAnalyzer_.Analyze(x)}) { Expr *intExpr{UnwrapExpr>(*y)}; return Fold(exprAnalyzer_.GetFoldingContext(), 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_; std::uint64_t messageDisplayedSet_{0}; }; void ArrayConstructorContext::Push(MaybeExpr &&x) { if (!x) { return; } if (!type_) { if (auto *boz{std::get_if(&x->u)}) { // Treat an array constructor of BOZ as if default integer. if (exprAnalyzer_.context().ShouldWarn( common::LanguageFeature::BOZAsDefaultInteger)) { exprAnalyzer_.Say( "BOZ literal in array constructor without explicit type is assumed to be default INTEGER"_en_US); } x = AsGenericExpr(ConvertToKind( exprAnalyzer_.GetDefaultKind(TypeCategory::Integer), std::move(*boz))); } } std::optional dyType{x->GetType()}; if (!dyType) { if (auto *boz{std::get_if(&x->u)}) { if (!type_) { // Treat an array constructor of BOZ as if default integer. if (exprAnalyzer_.context().ShouldWarn( common::LanguageFeature::BOZAsDefaultInteger)) { exprAnalyzer_.Say( "BOZ literal in array constructor without explicit type is assumed to be default INTEGER"_en_US); } x = AsGenericExpr(ConvertToKind( exprAnalyzer_.GetDefaultKind(TypeCategory::Integer), std::move(*boz))); dyType = x.value().GetType(); } else if (auto cast{ConvertToType(*type_, std::move(*x))}) { x = std::move(cast); dyType = *type_; } else { if (!(messageDisplayedSet_ & 0x80)) { exprAnalyzer_.Say( "BOZ literal is not suitable for use in this array constructor"_err_en_US); messageDisplayedSet_ |= 0x80; } return; } } else { // procedure name, &c. if (!(messageDisplayedSet_ & 0x40)) { exprAnalyzer_.Say( "Item is not suitable for use in an array constructor"_err_en_US); messageDisplayedSet_ |= 0x40; } return; } } else if (dyType->IsUnlimitedPolymorphic()) { if (!(messageDisplayedSet_ & 8)) { exprAnalyzer_.Say("Cannot have an unlimited polymorphic value in an " "array constructor"_err_en_US); // C7113 messageDisplayedSet_ |= 8; } return; } DynamicTypeWithLength xType{dyType.value()}; 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 (type_->IsTkCompatibleWith(xType) && xType.IsTkCompatibleWith(*type_)) { values_.Push(std::move(*x)); if (auto thisLen{ToInt64(xType.LEN())}) { if (constantLength_) { if (exprAnalyzer_.context().warnOnNonstandardUsage() && *thisLen != *constantLength_) { if (!(messageDisplayedSet_ & 1)) { exprAnalyzer_.Say( "Character literal in array constructor without explicit " "type has different length than earlier elements"_en_US); messageDisplayedSet_ |= 1; } } 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 (!(messageDisplayedSet_ & 2)) { exprAnalyzer_.Say( "Values in array constructor must have the same declared type " "when no explicit type appears"_err_en_US); // C7110 messageDisplayedSet_ |= 2; } } } else { if (auto cast{ConvertToType(*type_, std::move(*x))}) { values_.Push(std::move(*cast)); } else if (!(messageDisplayedSet_ & 4)) { 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 messageDisplayedSet_ |= 4; } } } void ArrayConstructorContext::Add(const parser::AcValue &x) { std::visit( common::visitors{ [&](const parser::AcValue::Triplet &triplet) { Add(triplet); }, [&](const common::Indirection &expr) { Add(expr.value()); }, [&](const common::Indirection &impliedDo) { Add(impliedDo.value()); }, }, x.u); } // Transforms l:u(:s) into (_,_=l,u(,s)) with an anonymous index '_' void ArrayConstructorContext::Add(const parser::AcValue::Triplet &triplet) { 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{ImpliedDoIntType::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)}); } } void ArrayConstructorContext::Add(const parser::Expr &expr) { auto restorer{exprAnalyzer_.GetContextualMessages().SetLocation(expr.source)}; Push(exprAnalyzer_.Analyze(expr)); } void ArrayConstructorContext::Add(const parser::AcImpliedDo &impliedDo) { const auto &control{std::get(impliedDo.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{ImpliedDoIntType::kind}; if (const auto dynamicType{DynamicType::From(symbol)}) { kind = dynamicType->kind(); } std::optional> lower{ GetSpecificIntExpr(bounds.lower)}; std::optional> upper{ GetSpecificIntExpr(bounds.upper)}; if (lower && upper) { std::optional> stride{ GetSpecificIntExpr(bounds.step)}; if (!stride) { stride = Expr{1}; } if (exprAnalyzer_.AddImpliedDo(name, kind)) { // Check for constant bounds; the loop may require complete unrolling // of the parse tree if all bounds are constant in order to allow the // implied DO loop index to qualify as a constant expression. auto cLower{ToInt64(lower)}; auto cUpper{ToInt64(upper)}; auto cStride{ToInt64(stride)}; if (!(messageDisplayedSet_ & 0x10) && cStride && *cStride == 0) { exprAnalyzer_.SayAt(bounds.step.value().thing.thing.value().source, "The stride of an implied DO loop must not be zero"_err_en_US); messageDisplayedSet_ |= 0x10; } bool isConstant{cLower && cUpper && cStride && *cStride != 0}; bool isNonemptyConstant{isConstant && ((*cStride > 0 && *cLower <= *cUpper) || (*cStride < 0 && *cLower >= *cUpper))}; bool unrollConstantLoop{false}; parser::Messages buffer; auto saveMessagesDisplayed{messageDisplayedSet_}; { auto messageRestorer{ exprAnalyzer_.GetContextualMessages().SetMessages(buffer)}; auto v{std::move(values_)}; for (const auto &value : std::get>(impliedDo.t)) { Add(value); } std::swap(v, values_); if (isNonemptyConstant && buffer.AnyFatalError()) { unrollConstantLoop = true; } else { values_.Push(ImpliedDo{name, std::move(*lower), std::move(*upper), std::move(*stride), std::move(v)}); } } if (unrollConstantLoop) { messageDisplayedSet_ = saveMessagesDisplayed; UnrollConstantImpliedDo(impliedDo, name, *cLower, *cUpper, *cStride); } else if (auto *messages{ exprAnalyzer_.GetContextualMessages().messages()}) { messages->Annex(std::move(buffer)); } exprAnalyzer_.RemoveImpliedDo(name); } else if (!(messageDisplayedSet_ & 0x20)) { exprAnalyzer_.SayAt(name, "Implied DO index '%s' is active in a surrounding implied DO loop " "and may not have the same name"_err_en_US, name); // C7115 messageDisplayedSet_ |= 0x20; } } } // Fortran considers an implied DO index of an array constructor to be // a constant expression if the bounds of the implied DO loop are constant. // Usually this doesn't matter, but if we emitted spurious messages as a // result of not using constant values for the index while analyzing the // items, we need to do it again the "hard" way with multiple iterations over // the parse tree. void ArrayConstructorContext::UnrollConstantImpliedDo( const parser::AcImpliedDo &impliedDo, parser::CharBlock name, std::int64_t lower, std::int64_t upper, std::int64_t stride) { auto &foldingContext{exprAnalyzer_.GetFoldingContext()}; auto restorer{exprAnalyzer_.DoNotUseSavedTypedExprs()}; for (auto &at{foldingContext.StartImpliedDo(name, lower)}; (stride > 0 && at <= upper) || (stride < 0 && at >= upper); at += stride) { for (const auto &value : std::get>(impliedDo.t)) { Add(value); } } foldingContext.EndImpliedDo(name); } 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::Name structureType{std::get(parsedType.t)}; parser::CharBlock &typeName{structureType.source}; if (semantics::Symbol * typeSymbol{structureType.symbol}) { if (typeSymbol->has()) { semantics::DerivedTypeSpec dtSpec{typeName, typeSymbol->GetUltimate()}; if (!CheckIsValidForwardReference(dtSpec)) { return std::nullopt; } } } 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 { auto checked{ CheckConformance(messages, *componentShape, *valueShape, CheckConformanceFlags::RightIsExpandableDeferred, "component", "value")}; if (checked && *checked && 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); } if (checked.value_or(true)) { 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) && IsBareNullPointer(&*value)) { // NULL() with no arguments allowed by 7.5.10 para 6 for ALLOCATABLE } else if (auto symType{DynamicType::From(symbol)}) { if (IsAllocatable(*symbol) && symType->IsUnlimitedPolymorphic() && valueType) { // ok } else 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 (context_.HasError(sym)) { return std::nullopt; } if (!IsProcedure(*sym)) { AttachDeclaration( Say(sc.component.source, "'%s' is not a procedure"_err_en_US, sc.component.source), *sym); return std::nullopt; } 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; }}; auto pair{ResolveGeneric(*sym, arguments, adjustment)}; sym = pair.first; if (!sym) { EmitGenericResolutionError(*sc.component.symbol, pair.second); 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(context_.AnyFatalError()); return std::nullopt; } // Can actual be argument associated with dummy? static bool CheckCompatibleArgument(bool isElemental, const ActualArgument &actual, const characteristics::DummyArgument &dummy) { const auto *expr{actual.UnwrapExpr()}; return std::visit( common::visitors{ [&](const characteristics::DummyDataObject &x) { if (x.attrs.test(characteristics::DummyDataObject::Attr::Pointer) && IsBareNullPointer(expr)) { // NULL() without MOLD= is compatible with any dummy data pointer // but cannot be allowed to lead to ambiguity. return true; } else if (!isElemental && actual.Rank() != x.type.Rank() && !x.type.attrs().test( characteristics::TypeAndShape::Attr::AssumedRank)) { return false; } else if (auto actualType{actual.GetType()}) { return x.type.type().IsTkCompatibleWith(*actualType); } return false; }, [&](const characteristics::DummyProcedure &) { return expr && IsProcedurePointerTarget(*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. std::pair ExpressionAnalyzer::ResolveGeneric( const Symbol &symbol, const ActualArguments &actuals, const AdjustActuals &adjustActuals, bool mightBeStructureConstructor) { const Symbol *elemental{nullptr}; // matching elemental specific proc const Symbol *nonElemental{nullptr}; // matching non-elemental specific const auto &details{symbol.GetUltimate().get()}; bool anyBareNullActual{ std::find_if(actuals.begin(), actuals.end(), [](auto iter) { return IsBareNullPointer(iter->UnwrapExpr()); }) != actuals.end()}; for (const Symbol &specific : details.specificProcs()) { if (!ResolveForward(specific)) { continue; } if (std::optional procedure{ characteristics::Procedure::Characterize( ProcedureDesignator{specific}, context_.foldingContext())}) { ActualArguments localActuals{actuals}; if (specific.has()) { if (!adjustActuals.value()(specific, localActuals)) { continue; } } if (semantics::CheckInterfaceForGeneric(*procedure, localActuals, GetFoldingContext(), false /* no integer conversions */) && CheckCompatibleArguments(*procedure, localActuals)) { if ((procedure->IsElemental() && elemental) || (!procedure->IsElemental() && nonElemental)) { // 16.9.144(6): a bare NULL() is not allowed as an actual // argument to a generic procedure if the specific procedure // cannot be unambiguously distinguished return {nullptr, true /* due to NULL actuals */}; } if (!procedure->IsElemental()) { // takes priority over elemental match nonElemental = &specific; if (!anyBareNullActual) { break; // unambiguous case } } else { elemental = &specific; } } } } if (nonElemental) { return {&AccessSpecific(symbol, *nonElemental), false}; } else if (elemental) { return {&AccessSpecific(symbol, *elemental), false}; } // Check parent derived type if (const auto *parentScope{symbol.owner().GetDerivedTypeParent()}) { if (const Symbol * extended{parentScope->FindComponent(symbol.name())}) { if (extended->GetUltimate().has()) { auto pair{ResolveGeneric(*extended, actuals, adjustActuals, false)}; if (pair.first) { return pair; } } } } if (mightBeStructureConstructor && details.derivedType()) { return {details.derivedType(), false}; } return {nullptr, false}; } const Symbol &ExpressionAnalyzer::AccessSpecific( const Symbol &originalGeneric, const Symbol &specific) { if (const auto *hosted{ originalGeneric.detailsIf()}) { return AccessSpecific(hosted->symbol(), specific); } else if (const auto *used{ originalGeneric.detailsIf()}) { const auto &scope{originalGeneric.owner()}; if (auto iter{scope.find(specific.name())}; iter != scope.end()) { if (const auto *useDetails{ iter->second->detailsIf()}) { const Symbol &usedSymbol{useDetails->symbol()}; const auto *usedGeneric{ usedSymbol.detailsIf()}; if (&usedSymbol == &specific || (usedGeneric && usedGeneric->specific() == &specific)) { return specific; } } } // Create a renaming USE of the specific procedure. auto rename{context_.SaveTempName( used->symbol().owner().GetName().value().ToString() + "$" + specific.name().ToString())}; return *const_cast(scope) .try_emplace(rename, specific.attrs(), semantics::UseDetails{rename, specific}) .first->second; } else { return specific; } } void ExpressionAnalyzer::EmitGenericResolutionError( const Symbol &symbol, bool dueToNullActuals) { Say(dueToNullActuals ? "One or more NULL() actual arguments to the generic procedure '%s' requires a MOLD= for disambiguation"_err_en_US : semantics::IsGenericDefinedOp(symbol) ? "No specific procedure of generic operator '%s' matches the actual arguments"_err_en_US : "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())}) { CheckBadExplicitType(*specificCall, *symbol); return CalleeAndArguments{ ProcedureDesignator{std::move(specificCall->specificIntrinsic)}, std::move(specificCall->arguments)}; } } else { CheckForBadRecursion(name.source, ultimate); bool dueToNullActual{false}; if (ultimate.has()) { ExpressionAnalyzer::AdjustActuals noAdjustment; auto pair{ResolveGeneric( *symbol, arguments, noAdjustment, mightBeStructureConstructor)}; symbol = pair.first; dueToNullActual = pair.second; } if (symbol) { if (symbol->GetUltimate().has()) { if (mightBeStructureConstructor) { return CalleeAndArguments{ semantics::SymbolRef{*symbol}, std::move(arguments)}; } } else if (IsProcedure(*symbol)) { return CalleeAndArguments{ ProcedureDesignator{*symbol}, std::move(arguments)}; } if (!context_.HasError(*symbol)) { AttachDeclaration( Say(name.source, "'%s' is not a callable procedure"_err_en_US, name.source), *symbol); } } 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, dueToNullActual); } } return std::nullopt; } // Fortran 2018 expressly states (8.2 p3) that any declared type for a // generic intrinsic function "has no effect" on the result type of a // call to that intrinsic. So one can declare "character*8 cos" and // still get a real result from "cos(1.)". This is a dangerous feature, // especially since implementations are free to extend their sets of // intrinsics, and in doing so might clash with a name in a program. // So we emit a warning in this situation, and perhaps it should be an // error -- any correctly working program can silence the message by // simply deleting the pointless type declaration. void ExpressionAnalyzer::CheckBadExplicitType( const SpecificCall &call, const Symbol &intrinsic) { if (intrinsic.GetUltimate().GetType()) { const auto &procedure{call.specificIntrinsic.characteristics.value()}; if (const auto &result{procedure.functionResult}) { if (const auto *typeAndShape{result->GetTypeAndShape()}) { if (auto declared{ typeAndShape->Characterize(intrinsic, GetFoldingContext())}) { if (!declared->type().IsTkCompatibleWith(typeAndShape->type())) { if (auto *msg{Say( "The result type '%s' of the intrinsic function '%s' is not the explicit declared type '%s'"_en_US, typeAndShape->AsFortran(), intrinsic.name(), declared->AsFortran())}) { msg->Attach(intrinsic.name(), "Ignored declaration of intrinsic function '%s'"_en_US, intrinsic.name()); } } } } } } } 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)}) { return AssumedTypeDummy(*name); } } } return nullptr; } template <> const Symbol *AssumedTypeDummy(const parser::Name &name) { if (const Symbol * symbol{name.symbol}) { if (const auto *type{symbol->GetType()}) { if (type->category() == semantics::DeclTypeSpec::TypeStar) { return symbol; } } } return nullptr; } template static const Symbol *AssumedTypePointerOrAllocatableDummy(const A &object) { // It is illegal for allocatable of pointer objects to be TYPE(*), but at that // point it is is not guaranteed that it has been checked the object has // POINTER or ALLOCATABLE attribute, so do not assume nullptr can be directly // returned. return std::visit( common::visitors{ [&](const parser::StructureComponent &x) { return AssumedTypeDummy(x.component); }, [&](const parser::Name &x) { return AssumedTypeDummy(x); }, }, object.u); } template <> const Symbol *AssumedTypeDummy( const parser::AllocateObject &x) { return AssumedTypePointerOrAllocatableDummy(x); } template <> const Symbol *AssumedTypeDummy( const parser::PointerObject &x) { return AssumedTypePointerOrAllocatableDummy(x); } bool ExpressionAnalyzer::CheckIsValidForwardReference( const semantics::DerivedTypeSpec &dtSpec) { if (dtSpec.IsForwardReferenced()) { Say("Cannot construct value for derived type '%s' " "before it is defined"_err_en_US, dtSpec.name()); return false; } return true; } 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)); } CHECK(std::holds_alternative(callee->u)); const Symbol &symbol{*std::get(callee->u)}; if (structureConstructor) { // Structure constructor misparsed as function reference? 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, symbol.GetUltimate()}; if (!CheckIsValidForwardReference(dtSpec)) { return std::nullopt; } const semantics::DeclTypeSpec &type{ semantics::FindOrInstantiateDerivedType(scope, std::move(dtSpec))}; auto &mutableRef{const_cast(funcRef)}; *structureConstructor = mutableRef.ConvertToStructureConstructor(type.derivedTypeSpec()); return Analyze(structureConstructor->value()); } } if (!context_.HasError(symbol)) { AttachDeclaration( Say("'%s' is called like a function but is not a procedure"_err_en_US, symbol.name()), symbol); context_.SetError(symbol); } } return std::nullopt; } static bool HasAlternateReturns(const evaluate::ActualArguments &args) { for (const auto &arg : args) { if (arg && arg->isAlternateReturn()) { return true; } } return false; } 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{HasAlternateReturns(callee->arguments)}; 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)); std::optional assignment; if (!analyzer.fatalErrors()) { std::optional procRef{analyzer.TryDefinedAssignment()}; if (!procRef) { analyzer.CheckForNullPointer( "in a non-pointer intrinsic assignment statement"); } assignment.emplace(analyzer.MoveExpr(0), 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_.foldingContext())}; 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()); } // Checks for ASSOCIATED() are done in intrinsic table processing bool procIsAssociated{false}; if (const SpecificIntrinsic * specificIntrinsic{proc.GetSpecificIntrinsic()}) { if (specificIntrinsic->name == "associated") { procIsAssociated = true; } } if (!procIsAssociated) { semantics::CheckArguments(*chars, arguments, GetFoldingContext(), context_.FindScope(callSite), treatExternalAsImplicit, proc.GetSpecificIntrinsic()); 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()) { if (analyzer.IsIntrinsicNumeric(opr)) { analyzer.CheckForNullPointer(); 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); } } return std::nullopt; } 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()) { if (analyzer.IsIntrinsicLogical()) { analyzer.CheckForNullPointer(); 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); } } return std::nullopt; } 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, nullptr, true); } // Binary (dyadic) operations template