//===-- 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-utils.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 #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(std::string_view 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, FoldingContext &context) { 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. DynamicTypeWithLength type{DynamicType{kind, len}}; if (auto lenExpr{type.LEN()}) { type.length = Fold(context, AsExpr(Extremum{Ordering::Greater, Expr{0}, std::move(*lenExpr)})); } return type; } else { return DynamicTypeWithLength{DynamicType{category, kind}}; } } } else if (const semantics::DerivedTypeSpec *derived{ typeSpec->AsDerived()}) { return DynamicTypeWithLength{DynamicType{*derived}}; } } } return std::nullopt; } // Utilities to set a source location, if we have one, on an actual argument, // when it is statically present. static void SetArgSourceLocation(ActualArgument &x, parser::CharBlock at) { x.set_sourceLocation(at); } static void SetArgSourceLocation( std::optional &x, parser::CharBlock at) { if (x) { x->set_sourceLocation(at); } } static void SetArgSourceLocation( std::optional &x, std::optional at) { if (x && at) { x->set_sourceLocation(*at); } } 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)); SetArgSourceLocation(actuals_.back(), x.source); fatalErrors_ |= !actuals_.back(); } void Analyze(const parser::Variable &); void Analyze(const parser::ActualArgSpec &, bool isSubroutine); void ConvertBOZ(std::optional *thisType, std::size_t, std::optional otherType); bool IsIntrinsicRelational( RelationalOperator, const DynamicType &, const DynamicType &) const; bool IsIntrinsicLogical() const; bool IsIntrinsicNumeric(NumericOperator) const; bool IsIntrinsicConcat() const; bool CheckConformance(); bool CheckAssignmentConformance(); bool CheckForNullPointer(const char *where = "as an operand here"); bool CheckForAssumedRank(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. // If a definedOpSymbolPtr is provided, the caller must check // for its accessibility. MaybeExpr TryDefinedOp( const char *, parser::MessageFixedText, 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( const std::vector &, parser::MessageFixedText); MaybeExpr TryBoundOp(const Symbol &, int passIndex); std::optional AnalyzeExpr(const parser::Expr &); std::optional AnalyzeVariable(const parser::Variable &); MaybeExpr AnalyzeExprOrWholeAssumedSizeArray(const parser::Expr &); bool AreConformable() const; const Symbol *FindBoundOp(parser::CharBlock, int passIndex, const Symbol *&generic, bool isSubroutine); 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 &specific{BypassGeneric(last)}; const Symbol &symbol{specific.GetUltimate()}; if (semantics::IsProcedure(symbol)) { if (symbol.attrs().test(semantics::Attr::ABSTRACT)) { Say("Abstract procedure interface '%s' may not be used as a designator"_err_en_US, last.name()); } if (auto *component{std::get_if(&ref.u)}) { if (!CheckDataRef(ref)) { return std::nullopt; } 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, last.name()); } else if (IsProcedurePointer(specific)) { // For procedure pointers, retain associations so that data accesses // from client modules will work. return Expr{ProcedureDesignator{specific}}; } 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, last.name()); } return std::nullopt; } else if (MaybeExpr result{AsGenericExpr(std::move(ref))}) { return result; } else if (semantics::HadUseError( context_, GetContextualMessages().at(), &symbol)) { return std::nullopt; } 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 (symbol.has() || symbol.has()) { // C928 & C1002 if (Triplet *last{std::get_if(&ref.subscript().back().u)}) { if (!last->upper() && IsAssumedSizeArray(symbol)) { 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()); } if (!semantics::IsNamedConstant(symbol) && !inDataStmtObject_) { // Subscripts of named constants are checked in folding. // Subscripts of DATA statement objects are checked in data statement // conversion to initializers. CheckSubscripts(ref); } 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 common::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)); } void ExpressionAnalyzer::CheckSubscripts(ArrayRef &ref) { // Fold subscript expressions and check for an empty triplet. const Symbol &arraySymbol{ref.base().GetLastSymbol()}; Shape lb{GetLBOUNDs(foldingContext_, NamedEntity{arraySymbol})}; CHECK(lb.size() >= ref.subscript().size()); Shape ub{GetUBOUNDs(foldingContext_, NamedEntity{arraySymbol})}; CHECK(ub.size() >= ref.subscript().size()); bool anyPossiblyEmptyDim{false}; int dim{0}; for (Subscript &ss : ref.subscript()) { if (Triplet * triplet{std::get_if(&ss.u)}) { auto expr{Fold(triplet->stride())}; auto stride{ToInt64(expr)}; triplet->set_stride(std::move(expr)); std::optional lower, upper; if (auto expr{triplet->lower()}) { *expr = Fold(std::move(*expr)); lower = ToInt64(*expr); triplet->set_lower(std::move(*expr)); } else { lower = ToInt64(lb[dim]); } if (auto expr{triplet->upper()}) { *expr = Fold(std::move(*expr)); upper = ToInt64(*expr); triplet->set_upper(std::move(*expr)); } else { upper = ToInt64(ub[dim]); } if (stride) { if (*stride == 0) { Say("Stride of triplet must not be zero"_err_en_US); return; } if (lower && upper) { if (*stride > 0) { anyPossiblyEmptyDim |= *lower > *upper; } else { anyPossiblyEmptyDim |= *lower < *upper; } } else { anyPossiblyEmptyDim = true; } } else { // non-constant stride if (lower && upper && *lower == *upper) { // stride is not relevant } else { anyPossiblyEmptyDim = true; } } } else { // not triplet auto &expr{std::get(ss.u).value()}; expr = Fold(std::move(expr)); anyPossiblyEmptyDim |= expr.Rank() > 0; // vector subscript } ++dim; } if (anyPossiblyEmptyDim) { return; } dim = 0; for (Subscript &ss : ref.subscript()) { auto dimLB{ToInt64(lb[dim])}; auto dimUB{ToInt64(ub[dim])}; if (dimUB && dimLB && *dimUB < *dimLB) { AttachDeclaration( Warn(common::UsageWarning::SubscriptedEmptyArray, "Empty array dimension %d should not be subscripted as an element or non-empty array section"_err_en_US, dim + 1), arraySymbol); break; } std::optional val[2]; int vals{0}; if (auto *triplet{std::get_if(&ss.u)}) { auto stride{ToInt64(triplet->stride())}; std::optional lower, upper; if (const auto *lowerExpr{triplet->GetLower()}) { lower = ToInt64(*lowerExpr); } else if (lb[dim]) { lower = ToInt64(*lb[dim]); } if (const auto *upperExpr{triplet->GetUpper()}) { upper = ToInt64(*upperExpr); } else if (ub[dim]) { upper = ToInt64(*ub[dim]); } if (lower) { val[vals++] = *lower; if (upper && *upper != lower && (stride && *stride != 0)) { // Normalize upper bound for non-unit stride // 1:10:2 -> 1:9:2, 10:1:-2 -> 10:2:-2 val[vals++] = *lower + *stride * ((*upper - *lower) / *stride); } } } else { val[vals++] = ToInt64(std::get(ss.u).value()); } for (int j{0}; j < vals; ++j) { if (val[j]) { std::optional msg; std::optional bound; if (dimLB && *val[j] < *dimLB) { msg = "Subscript %jd is less than lower bound %jd for dimension %d of array"_err_en_US; bound = *dimLB; } else if (dimUB && *val[j] > *dimUB) { msg = "Subscript %jd is greater than upper bound %jd for dimension %d of array"_err_en_US; bound = *dimUB; if (dim + 1 == arraySymbol.Rank() && IsDummy(arraySymbol) && *bound == 1) { // Old-school overindexing of a dummy array isn't fatal when // it's on the last dimension and the extent is 1. msg->set_severity(parser::Severity::Warning); } } if (msg) { AttachDeclaration( Say(std::move(*msg), static_cast(*val[j]), static_cast(bound.value()), dim + 1), arraySymbol); } } } ++dim; } } // C919a - only one part-ref of a data-ref may have rank > 0 bool ExpressionAnalyzer::CheckRanks(const DataRef &dataRef) { return common::visit( common::visitors{ [this](const Component &component) { const Symbol &symbol{component.GetLastSymbol()}; if (int componentRank{symbol.Rank()}; componentRank > 0) { if (int baseRank{component.base().Rank()}; 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 false; } } else { return CheckRanks(component.base()); } return true; }, [this](const ArrayRef &arrayRef) { if (const auto *component{arrayRef.base().UnwrapComponent()}) { int subscriptRank{0}; for (const Subscript &subscript : arrayRef.subscript()) { subscriptRank += subscript.Rank(); } if (subscriptRank > 0) { if (int componentBaseRank{component->base().Rank()}; componentBaseRank > 0) { Say("Subscripts of component '%s' of rank-%d derived type array have rank %d but must all be scalar"_err_en_US, component->GetLastSymbol().name(), componentBaseRank, subscriptRank); return false; } } else { return CheckRanks(component->base()); } } return true; }, [](const SymbolRef &) { return true; }, [](const CoarrayRef &) { return true; }, }, dataRef.u); } // C911 - if the last name in a data-ref has an abstract derived type, // it must also be polymorphic. bool ExpressionAnalyzer::CheckPolymorphic(const DataRef &dataRef) { if (auto type{DynamicType::From(dataRef.GetLastSymbol())}) { if (type->category() == TypeCategory::Derived && !type->IsPolymorphic()) { const Symbol &typeSymbol{ type->GetDerivedTypeSpec().typeSymbol().GetUltimate()}; if (typeSymbol.attrs().test(semantics::Attr::ABSTRACT)) { AttachDeclaration( Say("Reference to object with abstract derived type '%s' must be polymorphic"_err_en_US, typeSymbol.name()), typeSymbol); return false; } } } return true; } bool ExpressionAnalyzer::CheckDataRef(const DataRef &dataRef) { // Always check both, don't short-circuit bool ranksOk{CheckRanks(dataRef)}; bool polyOk{CheckPolymorphic(dataRef)}; return ranksOk && polyOk; } // Parse tree correction after a substring S(j:k) was misparsed as an // array section. Fortran substrings must have a range, not a // single index. static std::optional FixMisparsedSubstringDataRef( parser::DataRef &dataRef) { if (auto *ae{ std::get_if>(&dataRef.u)}) { // ...%a(j:k) and "a" is a character scalar parser::ArrayElement &arrElement{ae->value()}; if (arrElement.subscripts.size() == 1) { if (auto *triplet{std::get_if( &arrElement.subscripts.front().u)}) { if (!std::get<2 /*stride*/>(triplet->t).has_value()) { if (const Symbol *symbol{ parser::GetLastName(arrElement.base).symbol}) { const Symbol &ultimate{symbol->GetUltimate()}; if (const semantics::DeclTypeSpec *type{ultimate.GetType()}) { if (ultimate.Rank() == 0 && 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. return arrElement.ConvertToSubstring(); } } } } } } } return std::nullopt; } // When a designator is a misparsed type-param-inquiry of a misparsed // substring -- it looks like a structure component reference of an array // slice -- fix the substring and then convert to an intrinsic function // call to KIND() or LEN(). And when the designator is a misparsed // substring, convert it into a substring reference in place. MaybeExpr ExpressionAnalyzer::FixMisparsedSubstring( const parser::Designator &d) { auto &mutate{const_cast(d)}; if (auto *dataRef{std::get_if(&mutate.u)}) { if (auto *sc{std::get_if>( &dataRef->u)}) { parser::StructureComponent &structComponent{sc->value()}; parser::CharBlock which{structComponent.component.source}; if (which == "kind" || which == "len") { if (auto substring{ FixMisparsedSubstringDataRef(structComponent.base)}) { // ...%a(j:k)%kind or %len and "a" is a character scalar mutate.u = std::move(*substring); if (MaybeExpr substringExpr{Analyze(d)}) { return MakeFunctionRef(which, ActualArguments{ActualArgument{std::move(*substringExpr)}}); } } } } else if (auto substring{FixMisparsedSubstringDataRef(*dataRef)}) { mutate.u = std::move(*substring); } } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze(const parser::Designator &d) { auto restorer{GetContextualMessages().SetLocation(d.source)}; if (auto substringInquiry{FixMisparsedSubstring(d)}) { return substringInquiry; } // These checks have to be deferred to these "top level" data-refs where // we can be sure that there are no following subscripts (yet). MaybeExpr result{Analyze(d.u)}; if (result) { std::optional dataRef{ExtractDataRef(std::move(result))}; if (!dataRef) { dataRef = ExtractDataRef(std::move(result), /*intoSubstring=*/true); } if (!dataRef) { dataRef = ExtractDataRef(std::move(result), /*intoSubstring=*/false, /*intoComplexPart=*/true); } if (dataRef && !CheckDataRef(*dataRef)) { result.reset(); } } return result; } // 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; } std::int64_t kind{common::visit( common::visitors{ [](std::uint64_t k) { return static_cast(k); }, [&](const parser::Scalar< parser::Integer>> &n) { if (MaybeExpr ie{Analyze(n)}) { return ToInt64(*ie).value_or(defaultKind); } return static_cast(defaultKind); }, }, kindParam->u)}; if (kind != static_cast(kind)) { Say("Unsupported type kind value (%jd)"_err_en_US, static_cast(kind)); kind = defaultKind; } return static_cast(kind); } // Common handling of parser::IntLiteralConstant, SignedIntLiteralConstant, // and UnsignedLiteralConstant template struct IntTypeVisitor { using Result = MaybeExpr; using Types = TYPES; template Result Test() { if (T::kind >= kind) { const char *p{digits.begin()}; using Int = typename T::Scalar; typename Int::ValueWithOverflow num{0, false}; const char *typeName{ CAT == TypeCategory::Integer ? "INTEGER" : "UNSIGNED"}; if (isNegated) { auto unsignedNum{Int::Read(p, 10, false /*unsigned*/)}; num.value = unsignedNum.value.Negate().value; num.overflow = unsignedNum.overflow || (CAT == TypeCategory::Integer && num.value > Int{0}); if (!num.overflow && num.value.Negate().overflow) { analyzer.Warn(LanguageFeature::BigIntLiterals, digits, "negated maximum INTEGER(KIND=%d) literal"_port_en_US, T::kind); } } else { num = Int::Read(p, 10, /*isSigned=*/CAT == TypeCategory::Integer); } if (num.overflow) { if constexpr (CAT == TypeCategory::Unsigned) { analyzer.Warn(common::UsageWarning::UnsignedLiteralTruncation, "Unsigned literal too large for UNSIGNED(KIND=%d); truncated"_warn_en_US, kind); return Expr{ Expr>{Expr{Constant{std::move(num.value)}}}}; } } else { if (T::kind > kind) { if (!isDefaultKind || !analyzer.context().IsEnabled(LanguageFeature::BigIntLiterals)) { return std::nullopt; } else { analyzer.Warn(LanguageFeature::BigIntLiterals, digits, "Integer literal is too large for default %s(KIND=%d); " "assuming %s(KIND=%d)"_port_en_US, typeName, kind, typeName, T::kind); } } return Expr{ Expr>{Expr{Constant{std::move(num.value)}}}}; } } return std::nullopt; } ExpressionAnalyzer &analyzer; parser::CharBlock digits; std::int64_t kind; bool isDefaultKind; bool isNegated; }; template MaybeExpr ExpressionAnalyzer::IntLiteralConstant( const PARSED &x, bool isNegated) { const auto &kindParam{std::get>(x.t)}; bool isDefaultKind{!kindParam}; int kind{AnalyzeKindParam(kindParam, GetDefaultKind(CAT))}; const char *typeName{CAT == TypeCategory::Integer ? "INTEGER" : "UNSIGNED"}; if (CheckIntrinsicKind(CAT, kind)) { auto digits{std::get(x.t)}; if (MaybeExpr result{common::SearchTypes(IntTypeVisitor{ *this, digits, kind, isDefaultKind, isNegated})}) { return result; } else if (isDefaultKind) { Say(digits, "Integer literal is too large for any allowable kind of %s"_err_en_US, typeName); } else { Say(digits, "Integer literal is too large for %s(KIND=%d)"_err_en_US, typeName, kind); } } return std::nullopt; } MaybeExpr ExpressionAnalyzer::Analyze( const parser::IntLiteralConstant &x, bool isNegated) { auto restorer{ GetContextualMessages().SetLocation(std::get(x.t))}; return IntLiteralConstant(x, isNegated); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::SignedIntLiteralConstant &x) { auto restorer{GetContextualMessages().SetLocation(x.source)}; return IntLiteralConstant(x); } MaybeExpr ExpressionAnalyzer::Analyze( const parser::UnsignedLiteralConstant &x) { parser::CharBlock at{std::get(x.t)}; auto restorer{GetContextualMessages().SetLocation(at)}; if (!context().IsEnabled(common::LanguageFeature::Unsigned) && !context().AnyFatalError()) { context().Say( at, "-funsigned is required to enable UNSIGNED constants"_err_en_US); } return IntLiteralConstant(x); } template Constant ReadRealLiteral( parser::CharBlock source, FoldingContext &context) { const char *p{source.begin()}; auto valWithFlags{ Scalar::Read(p, context.targetCharacteristics().roundingMode())}; CHECK(p == source.end()); RealFlagWarnings(context, valWithFlags.flags, "conversion of REAL literal"); auto value{valWithFlags.value}; if (context.targetCharacteristics().areSubnormalsFlushedToZero()) { 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. // Extension: allow exponent-letter matching the kind-param. auto kind{AnalyzeKindParam(x.kind, defaultKind)}; if (letterKind && expoLetter != 'e') { if (kind != *letterKind) { Warn(common::LanguageFeature::ExponentMatchingKindParam, "Explicit kind parameter on real constant disagrees with exponent letter '%c'"_warn_en_US, expoLetter); } else if (x.kind) { Warn(common::LanguageFeature::ExponentMatchingKindParam, "Explicit kind parameter together with non-'E' exponent letter is not standard"_port_en_US); } } 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 AnalyzeComplex(Analyze(std::get<0>(z.t)), Analyze(std::get<1>(z.t)), "complex literal constant"); } // 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 result{AnalyzeString(std::string{x.v}, kind)}; if (auto *constant{UnwrapConstantValue(result)}) { constant->set_wasHollerith(true); } return result; } // .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}))); } if (context_.HasError(n.symbol)) { // includes case of no symbol 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. auto dyType{DynamicType::From(ultimate)}; if (!dyType) { // When the integer kind of this type parameter is not known now, // it's either an error or because it depends on earlier-declared kind // type parameters. So assume that it's a subscript integer for now // while processing other specification expressions in the PDT // definition; the right kind value will be used later in each of its // instantiations. int kind{SubscriptInteger::kind}; if (const auto *typeSpec{ultimate.GetType()}) { if (const semantics::IntrinsicTypeSpec * intrinType{typeSpec->AsIntrinsic()}) { if (auto k{ToInt64(Fold(semantics::KindExpr{intrinType->kind()}))}; k && IsValidKindOfIntrinsicType(TypeCategory::Integer, *k)) { kind = *k; } } } dyType = DynamicType{TypeCategory::Integer, kind}; } return Fold(ConvertToType( *dyType, 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( ResolveAssociations(*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) { auto restorer{AllowNullPointer()}; if (MaybeExpr value{Analyze(n.v.value())}) { // 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::StmtFunctionStmt &stmtFunc) { inStmtFunctionDefinition_ = true; return Analyze(std::get>(stmtFunc.t)); } 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{Designate(std::move(*dataRef))}) { if (std::optional checked{ ExtractDataRef(std::move(*newBaseExpr))}) { const parser::SubstringRange &range{ std::get(ss.t)}; std::optional> first{ Fold(GetSubstringBound(std::get<0>(range.t)))}; std::optional> last{ Fold(GetSubstringBound(std::get<1>(range.t)))}; const Symbol &symbol{checked->GetLastSymbol()}; if (std::optional dynamicType{ DynamicType::From(symbol)}) { if (dynamicType->category() == TypeCategory::Character) { auto lbValue{ToInt64(first)}; if (!lbValue) { lbValue = 1; } auto ubValue{ToInt64(last)}; auto len{dynamicType->knownLength()}; if (!ubValue) { ubValue = len; } if (lbValue && ubValue && *lbValue > *ubValue) { // valid, substring is empty } else if (lbValue && *lbValue < 1 && (ubValue || !last)) { Say("Substring must begin at 1 or later, not %jd"_err_en_US, static_cast(*lbValue)); return std::nullopt; } else if (ubValue && len && *ubValue > *len && (lbValue || !first)) { Say("Substring must end at %zd or earlier, not %jd"_err_en_US, static_cast(*len), static_cast(*ubValue)); return std::nullopt; } 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{ common::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 common::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(), foldingContext_.targetCharacteristics().isBigEndian()); 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; } // substring%KIND/LEN MaybeExpr ExpressionAnalyzer::Analyze(const parser::SubstringInquiry &x) { if (MaybeExpr substring{Analyze(x.v)}) { CHECK(x.source.size() >= 8); int nameLen{x.source.end()[-1] == 'n' ? 3 /*LEN*/ : 4 /*KIND*/}; parser::CharBlock name{ x.source.end() - nameLen, static_cast(nameLen)}; CHECK(name == "len" || name == "kind"); return MakeFunctionRef( name, ActualArguments{ActualArgument{std::move(*substring)}}); } else { 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 common::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 common::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, but not bindings, of parent derived types are explicitly // represented as such. std::optional ExpressionAnalyzer::CreateComponent(DataRef &&base, const Symbol &component, const semantics::Scope &scope, bool C919bAlreadyEnforced) { if (!C919bAlreadyEnforced && 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 || component.has()) { return Component{std::move(base), component}; } if (const Symbol *typeSymbol{scope.GetSymbol()}) { if (const Symbol *parentComponent{typeSymbol->GetParentComponent(&scope)}) { if (const auto *object{ parentComponent->detailsIf()}) { if (const auto *parentType{object->type()}) { if (const semantics::Scope *parentScope{ parentType->derivedTypeSpec().scope()}) { return CreateComponent( DataRef{Component{std::move(base), *parentComponent}}, component, *parentScope, C919bAlreadyEnforced); } } } } } return std::nullopt; } // Derived type component references and type parameter inquiries MaybeExpr ExpressionAnalyzer::Analyze(const parser::StructureComponent &sc) { Symbol *sym{sc.component.symbol}; if (context_.HasError(sym)) { return std::nullopt; } const auto *misc{sym->detailsIf()}; bool isTypeParamInquiry{sym->has() || (misc && (misc->kind() == semantics::MiscDetails::Kind::KindParamInquiry || misc->kind() == semantics::MiscDetails::Kind::LenParamInquiry))}; MaybeExpr base; if (isTypeParamInquiry) { auto restorer{AllowWholeAssumedSizeArray()}; base = Analyze(sc.base); } else { base = Analyze(sc.base); } if (!base) { return std::nullopt; } const auto &name{sc.component.source}; if (auto *dtExpr{UnwrapExpr>(*base)}) { const auto *dtSpec{GetDerivedTypeSpec(dtExpr->GetType())}; if (isTypeParamInquiry) { 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))}) { auto restorer{GetContextualMessages().SetLocation(name)}; 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 on the base of %re/%im/%len are detected and // reported in name resolution. 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(*zExpr)}) { // Represent %RE/%IM as a designator Expr realExpr{common::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 (isTypeParamInquiry) { // %kind or %len ActualArgument arg{std::move(*base)}; SetArgSourceLocation(arg, name); return MakeFunctionRef(name, ActualArguments{std::move(arg)}); } 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 != GetCorank(symbol)) { Say("'%s' has corank %d, but coindexed reference has %d cosubscripts"_err_en_US, symbol.name(), GetCorank(symbol), numCosubscripts); } } for (const auto &imageSelSpec : std::get>(x.imageSelector.t)) { common::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) { common::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) { ArrayConstructor result{MakeSpecific(std::move(values_))}; if constexpr (T::category == TypeCategory::Character) { if (auto len{LengthIfGood()}) { // The ac-do-variables may be treated as constant expressions, // if some conditions on ac-implied-do-control hold (10.1.12 (12)). // At the same time, they may be treated as constant expressions // only in the context of the ac-implied-do, but setting // the character length here may result in complete elimination // of the ac-implied-do. For example: // character(10) :: c // ... len([(c(i:i), integer(8)::i = 1,4)]) // would be evaulated into: // ... int(max(0_8,i-i+1_8),kind=4) // with a dangling reference to the ac-do-variable. // Prevent this by checking for the ac-do-variable references // in the 'len' expression. result.set_LEN(std::move(*len)); } } return AsMaybeExpr(std::move(result)); } } return std::nullopt; } private: using ImpliedDoIntType = ResultType; std::optional> LengthIfGood() const { if (type_) { auto len{type_->LEN()}; if (explicitType_ || (len && IsConstantExpr(*len) && !ContainsAnyImpliedDoIndex(*len))) { return len; } } return std::nullopt; } bool NeedLength() const { return type_ && type_->category() == TypeCategory::Character && !LengthIfGood(); } 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>> ToSpecificInt( MaybeExpr &&y) { if (y) { Expr *intExpr{UnwrapExpr>(*y)}; return Fold(exprAnalyzer_.GetFoldingContext(), ConvertToType>( std::move(DEREF(intExpr)))); } else { return std::nullopt; } } template std::optional>> GetSpecificIntExpr( const A &x) { return ToSpecificInt(exprAnalyzer_.Analyze(x)); } // 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. exprAnalyzer_.Warn(common::LanguageFeature::BOZAsDefaultInteger, "BOZ literal in array constructor without explicit type is assumed to be default INTEGER"_port_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. exprAnalyzer_.Warn(common::LanguageFeature::BOZAsDefaultInteger, "BOZ literal in array constructor without explicit type is assumed to be default INTEGER"_port_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; } else if (dyType->category() == TypeCategory::Derived && dyType->GetDerivedTypeSpec().typeSymbol().attrs().test( semantics::Attr::ABSTRACT)) { // F'2023 C7125 if (!(messageDisplayedSet_ & 0x200)) { exprAnalyzer_.Say( "An item whose declared type is ABSTRACT may not appear in an array constructor"_err_en_US); messageDisplayedSet_ |= 0x200; } } DynamicTypeWithLength xType{dyType.value()}; if (Expr * charExpr{UnwrapExpr>(*x)}) { CHECK(xType.category() == TypeCategory::Character); xType.length = common::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)); auto xLen{xType.LEN()}; if (auto thisLen{ToInt64(xLen)}) { if (constantLength_) { if (*thisLen != *constantLength_ && !(messageDisplayedSet_ & 1)) { exprAnalyzer_.Warn( common::LanguageFeature::DistinctArrayConstructorLengths, "Character literal in array constructor without explicit " "type has different length than earlier elements"_port_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 = std::move(xLen); } } else { constantLength_ = *thisLen; type_->length = std::move(xLen); } } else if (xLen && NeedLength()) { type_->length = std::move(xLen); } } 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) { common::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) { MaybeExpr lowerExpr{exprAnalyzer_.Analyze(std::get<0>(triplet.t))}; MaybeExpr upperExpr{exprAnalyzer_.Analyze(std::get<1>(triplet.t))}; MaybeExpr strideExpr{exprAnalyzer_.Analyze(std::get<2>(triplet.t))}; if (lowerExpr && upperExpr) { auto lowerType{lowerExpr->GetType()}; auto upperType{upperExpr->GetType()}; auto strideType{strideExpr ? strideExpr->GetType() : lowerType}; if (lowerType && upperType && strideType) { int kind{lowerType->kind()}; if (upperType->kind() > kind) { kind = upperType->kind(); } if (strideType->kind() > kind) { kind = strideType->kind(); } auto lower{ToSpecificInt(std::move(lowerExpr))}; auto upper{ToSpecificInt(std::move(upperExpr))}; if (lower && upper) { auto stride{ ToSpecificInt(std::move(strideExpr))}; if (!stride) { stride = Expr{1}; } DynamicType type{TypeCategory::Integer, kind}; if (!type_) { type_ = DynamicTypeWithLength{type}; } parser::CharBlock anonymous; if (auto converted{ConvertToType(type, AsGenericExpr( Expr{ImpliedDoIndex{anonymous}}))}) { auto v{std::move(values_)}; Push(std::move(converted)); 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}; int kind{ImpliedDoIntType::kind}; if (const Symbol * symbol{bounds.name.thing.thing.symbol}) { if (auto dynamicType{DynamicType::From(symbol)}) { if (dynamicType->category() == TypeCategory::Integer) { 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 isEmpty{isConstant && !isNonemptyConstant}; 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)}); } } // F'2023 7.8 p5 if (!(messageDisplayedSet_ & 0x100) && isEmpty && NeedLength()) { exprAnalyzer_.SayAt(name, "Array constructor implied DO loop has no iterations and indeterminate character length"_err_en_US); messageDisplayedSet_ |= 0x100; } 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, GetFoldingContext())}; for (const parser::AcValue &value : acSpec.values) { acContext.Add(value); } return acContext.ToExpr(); } // Check if implicit conversion of expr to the symbol type is legal (if needed), // and make it explicit if requested. static MaybeExpr ImplicitConvertTo(const semantics::Symbol &sym, Expr &&expr, bool keepConvertImplicit) { if (!keepConvertImplicit) { return ConvertToType(sym, std::move(expr)); } else { // Test if a convert could be inserted, but do not make it explicit to // preserve the information that expr is a variable. if (ConvertToType(sym, common::Clone(expr))) { return MaybeExpr{std::move(expr)}; } } // Illegal implicit convert. return std::nullopt; } 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 semantics::Scope &scope{context_.FindScope(typeName)}; const semantics::Scope *pureContext{FindPureProcedureContaining(scope)}; 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()}; auto afterLastParentComponentIter{components.end()}; if (parentComponent) { for (auto iter{components.begin()}; iter != components.end(); ++iter) { if (iter->test(Symbol::Flag::ParentComp)) { afterLastParentComponentIter = iter; ++afterLastParentComponentIter; } } } std::set unavailable; bool anyKeyword{false}; StructureConstructor result{spec}; bool checkConflicts{true}; // until we hit one auto &messages{GetContextualMessages()}; // NULL() can be a valid component auto restorer{AllowNullPointer()}; 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) { // Skip overridden inaccessible parent components in favor of // their later overrides. for (const Symbol &sym : components) { if (sym.name() == source) { symbol = &sym; } } } 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)). There may be multiple parent components. if (nextAnonymous == components.begin() && parentComponent && valueType && context().IsEnabled(LanguageFeature::AnonymousParents)) { for (auto parent{components.begin()}; parent != afterLastParentComponentIter; ++parent) { if (auto parentType{DynamicType::From(*parent)}; parentType && parent->test(Symbol::Flag::ParentComp) && valueType->IsEquivalentTo(*parentType)) { symbol = &*parent; nextAnonymous = ++parent; Warn(LanguageFeature::AnonymousParents, source, "Whole parent component '%s' in structure constructor should not be anonymous"_port_en_US, symbol->name()); break; } } } 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) { const semantics::Scope &innermost{context_.FindScope(expr.source)}; if (auto msg{CheckAccessibleSymbol(innermost, *symbol)}) { Say(expr.source, std::move(*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()) { Say(expr.source, "Type parameter '%s' may not appear as a component of a structure constructor"_err_en_US, symbol->name()); } if (!(symbol->has() || symbol->has())) { continue; // recovery } if (IsPointer(*symbol)) { // C7104, C7105, C1594(4) semantics::CheckStructConstructorPointerComponent( context_, *symbol, *value, innermost); result.Add(*symbol, Fold(std::move(*value))); continue; } if (IsNullPointer(*value)) { if (IsAllocatable(*symbol)) { if (IsBareNullPointer(&*value)) { // NULL() with no arguments allowed by 7.5.10 para 6 for // ALLOCATABLE. result.Add(*symbol, Expr{NullPointer{}}); continue; } if (IsNullObjectPointer(*value)) { AttachDeclaration( Warn(common::LanguageFeature:: NullMoldAllocatableComponentValue, expr.source, "NULL() with arguments is not standard conforming as the value for allocatable component '%s'"_port_en_US, symbol->name()), *symbol); // proceed to check type & shape } else { AttachDeclaration( Say(expr.source, "A NULL procedure pointer may not be used as the value for component '%s'"_err_en_US, symbol->name()), *symbol); continue; } } else { AttachDeclaration( Say(expr.source, "A NULL pointer may not be used as the value for component '%s'"_err_en_US, symbol->name()), *symbol); continue; } } else if (const Symbol * pointer{FindPointerComponent(*symbol)}; pointer && pureContext) { // C1594(4) if (const Symbol * visible{semantics::FindExternallyVisibleObject( *value, *pureContext)}) { Say(expr.source, "The externally visible object '%s' may not be used in a pure procedure as the value for component '%s' which has the pointer component '%s'"_err_en_US, visible->name(), symbol->name(), pointer->name()); } } // Make implicit conversion explicit to allow folding of the structure // constructors and help semantic checking, unless the component is // allocatable, in which case the value could be an unallocated // allocatable (see Fortran 2018 7.5.10 point 7). The explicit // convert would cause a segfault. Lowering will deal with // conditionally converting and preserving the lower bounds in this // case. if (MaybeExpr converted{ImplicitConvertTo( *symbol, std::move(*value), IsAllocatable(*symbol))}) { 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 && (IsDeferredShape(*symbol) || !IsExpandableScalar(*converted, GetFoldingContext(), *componentShape, true /*admit PURE call*/))) { 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 (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()) { if (IsAllocatable(symbol)) { // Set all remaining allocatables to explicit NULL(). result.Add(symbol, Expr{NullPointer{}}); } else { const auto *object{symbol.detailsIf()}; if (object && object->init()) { result.Add(symbol, common::Clone(*object->init())); } else if (IsPointer(symbol)) { result.Add(symbol, Expr{NullPointer{}}); } else if (object) { // 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 common::visit( [](const auto &details) { if constexpr (std::is_base_of_v>) { return details.passName(); } else { return std::optional{}; } }, proc.details()); } static std::optional 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; } return std::nullopt; } // 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; } std::optional passIndex{GetPassIndex(component)}; if (!passIndex) { return; // error recovery } 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, bool isSubroutine) -> 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()) { const Symbol &generic{*sym}; auto dyType{dtExpr->GetType()}; 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(generic, arguments, adjustment, isSubroutine)}; sym = pair.first; if (!sym) { EmitGenericResolutionError(generic, pair.second, isSubroutine); return std::nullopt; } // re-resolve the name to the specific binding CHECK(sym->has()); // Use the most recent override of a binding, respecting // the rule that inaccessible bindings may not be overridden // outside their module. Fortran doesn't allow a PUBLIC // binding to be overridden by a PRIVATE one. CHECK(dyType && dyType->category() == TypeCategory::Derived && !dyType->IsUnlimitedPolymorphic()); if (const Symbol * latest{DEREF(dyType->GetDerivedTypeSpec().typeSymbol().scope()) .FindComponent(sym->name())}) { if (sym->attrs().test(semantics::Attr::PRIVATE)) { const auto *bindingModule{FindModuleContaining(generic.owner())}; const Symbol *s{latest}; while (s && FindModuleContaining(s->owner()) != bindingModule) { if (const auto *parent{s->owner().GetDerivedTypeParent()}) { s = parent->FindComponent(sym->name()); } else { s = nullptr; } } if (s && !s->attrs().test(semantics::Attr::PRIVATE)) { // The latest override in the same module as the binding // is public, so it can be overridden. } else { latest = s; } } if (latest) { sym = latest; } } sc.component.symbol = const_cast(sym); } std::optional dataRef{ExtractDataRef(std::move(*dtExpr))}; if (dataRef && !CheckDataRef(*dataRef)) { return std::nullopt; } if (dataRef && dataRef->Rank() > 0) { if (sym->has() && sym->attrs().test(semantics::Attr::NOPASS)) { // F'2023 C1529 seems unnecessary and most compilers don't // enforce it. AttachDeclaration( Warn(common::LanguageFeature::NopassScalarBase, sc.component.source, "Base of NOPASS type-bound procedure reference should be scalar"_port_en_US), *sym); } else if (IsProcedurePointer(*sym)) { // C919 Say(sc.component.source, "Base of procedure component reference must be scalar"_err_en_US); } } if (const Symbol *resolution{ GetBindingResolution(dtExpr->GetType(), *sym)}) { AddPassArg(arguments, std::move(*dtExpr), *sym, false); return CalleeAndArguments{ ProcedureDesignator{*resolution}, std::move(arguments)}; } else if (dataRef.has_value()) { if (sym->attrs().test(semantics::Attr::NOPASS)) { const auto *dtSpec{GetDerivedTypeSpec(dtExpr->GetType())}; if (dtSpec && dtSpec->scope()) { if (auto component{CreateComponent(std::move(*dataRef), *sym, *dtSpec->scope(), /*C919bAlreadyEnforced=*/true)}) { return CalleeAndArguments{ ProcedureDesignator{std::move(*component)}, std::move(arguments)}; } } Say(sc.component.source, "Component is not in scope of base derived type"_err_en_US); return std::nullopt; } 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, FoldingContext &foldingContext) { const auto *expr{actual.UnwrapExpr()}; return common::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) && !x.ignoreTKR.test(common::IgnoreTKR::Rank)) { return false; } else if (auto actualType{actual.GetType()}) { return x.type.type().IsTkCompatibleWith(*actualType, x.ignoreTKR); } return false; }, [&](const characteristics::DummyProcedure &dummy) { if ((dummy.attrs.test( characteristics::DummyProcedure::Attr::Optional) || dummy.attrs.test( characteristics::DummyProcedure::Attr::Pointer)) && IsBareNullPointer(expr)) { // NULL() is compatible with any dummy pointer // or optional dummy procedure. return true; } if (!expr || !IsProcedurePointerTarget(*expr)) { return false; } if (auto actualProc{characteristics::Procedure::Characterize( *expr, foldingContext)}) { const auto &dummyResult{dummy.procedure.value().functionResult}; const auto *dummyTypeAndShape{ dummyResult ? dummyResult->GetTypeAndShape() : nullptr}; const auto &actualResult{actualProc->functionResult}; const auto *actualTypeAndShape{ actualResult ? actualResult->GetTypeAndShape() : nullptr}; if (dummyTypeAndShape && actualTypeAndShape) { // Return false when the function results' types are both // known and not compatible. return actualTypeAndShape->type().IsTkCompatibleWith( dummyTypeAndShape->type()); } } return true; }, [&](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, FoldingContext &foldingContext) { 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, foldingContext)) { return false; } } return true; } static constexpr int cudaInfMatchingValue{std::numeric_limits::max()}; // Compute the matching distance as described in section 3.2.3 of the CUDA // Fortran references. static int GetMatchingDistance(const common::LanguageFeatureControl &features, const characteristics::DummyArgument &dummy, const std::optional &actual) { bool isCudaManaged{features.IsEnabled(common::LanguageFeature::CudaManaged)}; bool isCudaUnified{features.IsEnabled(common::LanguageFeature::CudaUnified)}; CHECK(!(isCudaUnified && isCudaManaged) && "expect only one enabled."); std::optional actualDataAttr, dummyDataAttr; if (actual) { if (auto *expr{actual->UnwrapExpr()}) { const auto *actualLastSymbol{evaluate::GetLastSymbol(*expr)}; if (actualLastSymbol) { actualLastSymbol = &semantics::ResolveAssociations(*actualLastSymbol); if (const auto *actualObject{actualLastSymbol ? actualLastSymbol ->detailsIf() : nullptr}) { actualDataAttr = actualObject->cudaDataAttr(); } } } } common::visit(common::visitors{ [&](const characteristics::DummyDataObject &object) { dummyDataAttr = object.cudaDataAttr; }, [&](const auto &) {}, }, dummy.u); if (!dummyDataAttr) { if (!actualDataAttr) { if (isCudaUnified || isCudaManaged) { return 3; } return 0; } else if (*actualDataAttr == common::CUDADataAttr::Device) { return cudaInfMatchingValue; } else if (*actualDataAttr == common::CUDADataAttr::Managed || *actualDataAttr == common::CUDADataAttr::Unified) { return 3; } } else if (*dummyDataAttr == common::CUDADataAttr::Device) { if (!actualDataAttr) { if (isCudaUnified || isCudaManaged) { return 2; } return cudaInfMatchingValue; } else if (*actualDataAttr == common::CUDADataAttr::Device) { return 0; } else if (*actualDataAttr == common::CUDADataAttr::Managed || *actualDataAttr == common::CUDADataAttr::Unified) { return 2; } } else if (*dummyDataAttr == common::CUDADataAttr::Managed) { if (!actualDataAttr) { return isCudaUnified ? 1 : isCudaManaged ? 0 : cudaInfMatchingValue; } if (*actualDataAttr == common::CUDADataAttr::Device) { return cudaInfMatchingValue; } else if (*actualDataAttr == common::CUDADataAttr::Managed) { return 0; } else if (*actualDataAttr == common::CUDADataAttr::Unified) { return 1; } } else if (*dummyDataAttr == common::CUDADataAttr::Unified) { if (!actualDataAttr) { return isCudaUnified ? 0 : isCudaManaged ? 1 : cudaInfMatchingValue; } if (*actualDataAttr == common::CUDADataAttr::Device) { return cudaInfMatchingValue; } else if (*actualDataAttr == common::CUDADataAttr::Managed) { return 1; } else if (*actualDataAttr == common::CUDADataAttr::Unified) { return 0; } } return cudaInfMatchingValue; } static int ComputeCudaMatchingDistance( const common::LanguageFeatureControl &features, const characteristics::Procedure &procedure, const ActualArguments &actuals) { const auto &dummies{procedure.dummyArguments}; CHECK(dummies.size() == actuals.size()); int distance{0}; for (std::size_t i{0}; i < dummies.size(); ++i) { const characteristics::DummyArgument &dummy{dummies[i]}; const std::optional &actual{actuals[i]}; int d{GetMatchingDistance(features, dummy, actual)}; if (d == cudaInfMatchingValue) return d; distance += d; } return distance; } // 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. const Symbol *ExpressionAnalyzer::ResolveForward(const Symbol &symbol) { if (context_.HasError(symbol)) { return nullptr; } 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. const semantics::Scope &scope{symbol.owner()}; semantics::ResolveSpecificationParts(context_, symbol); const Symbol *resolved{nullptr}; if (auto iter{scope.find(symbol.name())}; iter != scope.cend()) { resolved = &*iter->second; } if (!resolved || resolved->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 resolved; } else if (inStmtFunctionDefinition_) { semantics::ResolveSpecificationParts(context_, symbol); CHECK(symbol.has()); } 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 nullptr; } } return &symbol; } // 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 isSubroutine, bool mightBeStructureConstructor) { const Symbol *elemental{nullptr}; // matching elemental specific proc const Symbol *nonElemental{nullptr}; // matching non-elemental specific const Symbol &ultimate{symbol.GetUltimate()}; int crtMatchingDistance{cudaInfMatchingValue}; // Check for a match with an explicit INTRINSIC if (ultimate.attrs().test(semantics::Attr::INTRINSIC)) { parser::Messages buffer; auto restorer{foldingContext_.messages().SetMessages(buffer)}; ActualArguments localActuals{actuals}; if (context_.intrinsics().Probe( CallCharacteristics{ultimate.name().ToString(), isSubroutine}, localActuals, foldingContext_) && !buffer.AnyFatalError()) { return {&ultimate, false}; } } if (const auto *details{ultimate.detailsIf()}) { for (const Symbol &specific0 : details->specificProcs()) { const Symbol &specific1{BypassGeneric(specific0)}; if (isSubroutine != !IsFunction(specific1)) { continue; } const Symbol *specific{ResolveForward(specific1)}; if (!specific) { continue; } if (std::optional procedure{ characteristics::Procedure::Characterize( ProcedureDesignator{*specific}, context_.foldingContext(), /*emitError=*/false)}) { ActualArguments localActuals{actuals}; if (specific->has()) { if (!adjustActuals.value()(*specific, localActuals)) { continue; } } if (semantics::CheckInterfaceForGeneric(*procedure, localActuals, context_, false /* no integer conversions */) && CheckCompatibleArguments( *procedure, localActuals, foldingContext_)) { if ((procedure->IsElemental() && elemental) || (!procedure->IsElemental() && nonElemental)) { int d{ComputeCudaMatchingDistance( context_.languageFeatures(), *procedure, localActuals)}; if (d != crtMatchingDistance) { if (d > crtMatchingDistance) { continue; } // Matching distance is smaller than the previously matched // specific. Let it go thourgh so the current procedure is picked. } else { // 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 // Underspecified external procedure actual arguments can // also lead to ambiguity. return {nullptr, true /* due to ambiguity */}; } } if (!procedure->IsElemental()) { // takes priority over elemental match nonElemental = specific; } else { elemental = specific; } crtMatchingDistance = ComputeCudaMatchingDistance( context_.languageFeatures(), *procedure, localActuals); } } } 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())}) { auto pair{ResolveGeneric( *extended, actuals, adjustActuals, isSubroutine, false)}; if (pair.first) { return pair; } } } if (mightBeStructureConstructor && details->derivedType()) { return {details->derivedType(), false}; } } // Check for generic or explicit INTRINSIC of the same name in outer scopes. // See 15.5.5.2 for details. if (!symbol.owner().IsGlobal() && !symbol.owner().IsDerivedType()) { for (const std::string &n : GetAllNames(context_, symbol.name())) { if (const Symbol *outer{symbol.owner().parent().FindSymbol(n)}) { auto pair{ResolveGeneric(*outer, actuals, adjustActuals, isSubroutine, mightBeStructureConstructor)}; if (pair.first) { return pair; } } } } 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.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 dueToAmbiguity, bool isSubroutine) { Say(dueToAmbiguity ? "The actual arguments to the generic procedure '%s' matched multiple specific procedures, perhaps due to use of NULL() without MOLD= or an actual procedure with an implicit interface"_err_en_US : semantics::IsGenericDefinedOp(symbol) ? "No specific procedure of generic operator '%s' matches the actual arguments"_err_en_US : isSubroutine ? "No specific subroutine of generic '%s' matches the actual arguments"_err_en_US : "No specific function 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 common::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), isSubroutine); }, }, 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 } symbol = ResolveForward(*symbol); if (!symbol) { return std::nullopt; } name.symbol = const_cast(symbol); const Symbol &ultimate{symbol->GetUltimate()}; CheckForBadRecursion(name.source, ultimate); bool dueToAmbiguity{false}; bool isGenericInterface{ultimate.has()}; bool isExplicitIntrinsic{ultimate.attrs().test(semantics::Attr::INTRINSIC)}; const Symbol *resolution{nullptr}; if (isGenericInterface || isExplicitIntrinsic) { ExpressionAnalyzer::AdjustActuals noAdjustment; auto pair{ResolveGeneric(*symbol, arguments, noAdjustment, isSubroutine, mightBeStructureConstructor)}; resolution = pair.first; dueToAmbiguity = pair.second; if (resolution) { if (context_.GetPPCBuiltinsScope() && resolution->name().ToString().rfind("__ppc_", 0) == 0) { semantics::CheckPPCIntrinsic( *symbol, *resolution, arguments, GetFoldingContext()); } // re-resolve name to the specific procedure name.symbol = const_cast(resolution); } } else if (IsProcedure(ultimate) && ultimate.attrs().test(semantics::Attr::ABSTRACT)) { Say("Abstract procedure interface '%s' may not be referenced"_err_en_US, name.source); } else { resolution = symbol; } if (resolution && context_.targetCharacteristics().isOSWindows()) { semantics::CheckWindowsIntrinsic(*resolution, GetFoldingContext()); } if (!resolution || resolution->attrs().test(semantics::Attr::INTRINSIC)) { auto name{resolution ? resolution->name() : ultimate.name()}; if (std::optional specificCall{context_.intrinsics().Probe( CallCharacteristics{name.ToString(), isSubroutine}, arguments, GetFoldingContext())}) { CheckBadExplicitType(*specificCall, *symbol); return CalleeAndArguments{ ProcedureDesignator{std::move(specificCall->specificIntrinsic)}, std::move(specificCall->arguments)}; } else { if (isGenericInterface) { EmitGenericResolutionError(*symbol, dueToAmbiguity, isSubroutine); } return std::nullopt; } } if (resolution->GetUltimate().has()) { if (mightBeStructureConstructor) { return CalleeAndArguments{ semantics::SymbolRef{*resolution}, std::move(arguments)}; } } else if (IsProcedure(*resolution)) { return CalleeAndArguments{ ProcedureDesignator{*resolution}, std::move(arguments)}; } if (!context_.HasError(*resolution)) { AttachDeclaration( Say(name.source, "'%s' is not a callable procedure"_err_en_US, name.source), *resolution); } 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{Warn( common::UsageWarning::IgnoredIntrinsicFunctionType, "The result type '%s' of the intrinsic function '%s' is not the explicit declared type '%s'"_warn_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)) { // TODO: Also catch assumed PDT type parameters msg = Say( // 15.6.2.1(3) "Assumed-length CHARACTER(*) function '%s' cannot call itself"_err_en_US, callSite); } else if (FindCUDADeviceContext(scope)) { msg = Say( "Device subprogram '%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 not guaranteed that it has been checked the object has // POINTER or ALLOCATABLE attribute, so do not assume nullptr can be directly // returned. return common::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; } std::optional ExpressionAnalyzer::AnalyzeChevrons( const parser::CallStmt &call) { Chevrons result; auto checkLaunchArg{[&](const Expr &expr, const char *which) { if (auto dyType{expr.GetType()}) { if (dyType->category() == TypeCategory::Integer) { return true; } if (dyType->category() == TypeCategory::Derived && !dyType->IsPolymorphic() && IsBuiltinDerivedType(&dyType->GetDerivedTypeSpec(), "dim3")) { return true; } } Say("Kernel launch %s parameter must be either integer or TYPE(dim3)"_err_en_US, which); return false; }}; if (const auto &chevrons{call.chevrons}) { auto &starOrExpr{std::get<0>(chevrons->t)}; if (starOrExpr.v) { if (auto expr{Analyze(*starOrExpr.v)}; expr && checkLaunchArg(*expr, "grid")) { result.emplace_back(*expr); } else { return std::nullopt; } } else { result.emplace_back( AsGenericExpr(evaluate::Constant{-1})); } if (auto expr{Analyze(std::get<1>(chevrons->t))}; expr && checkLaunchArg(*expr, "block")) { result.emplace_back(*expr); } else { return std::nullopt; } if (const auto &maybeExpr{std::get<2>(chevrons->t)}) { if (auto expr{Analyze(*maybeExpr)}) { result.emplace_back(*expr); } else { return std::nullopt; } } if (const auto &maybeExpr{std::get<3>(chevrons->t)}) { if (auto expr{Analyze(*maybeExpr)}) { result.emplace_back(*expr); } else { return std::nullopt; } } } return std::move(result); } MaybeExpr ExpressionAnalyzer::Analyze(const parser::FunctionReference &funcRef, std::optional *structureConstructor) { const parser::Call &call{funcRef.v}; auto restorer{GetContextualMessages().SetLocation(funcRef.source)}; ArgumentAnalyzer analyzer{*this, funcRef.source, true /* isProcedureCall */}; for (const auto &arg : std::get>(call.t)) { analyzer.Analyze(arg, false /* not subroutine call */); } if (analyzer.fatalErrors()) { return std::nullopt; } bool mightBeStructureConstructor{structureConstructor != nullptr}; if (std::optional callee{GetCalleeAndArguments( std::get(call.t), analyzer.GetActuals(), false /* not subroutine */, mightBeStructureConstructor)}) { if (auto *proc{std::get_if(&callee->u)}) { return MakeFunctionRef( funcRef.source, std::move(*proc), std::move(callee->arguments)); } CHECK(std::holds_alternative(callee->u)); const Symbol &symbol{*std::get(callee->u)}; if (mightBeStructureConstructor) { // 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.call}; auto restorer{GetContextualMessages().SetLocation(callStmt.source)}; ArgumentAnalyzer analyzer{*this, callStmt.source, true /* isProcedureCall */}; const auto &actualArgList{std::get>(call.t)}; for (const auto &arg : actualArgList) { analyzer.Analyze(arg, true /* is subroutine call */); } if (auto chevrons{AnalyzeChevrons(callStmt)}; chevrons && !analyzer.fatalErrors()) { if (std::optional callee{ GetCalleeAndArguments(std::get(call.t), analyzer.GetActuals(), true /* subroutine */)}) { ProcedureDesignator *proc{std::get_if(&callee->u)}; CHECK(proc); bool isKernel{false}; if (const Symbol * procSym{proc->GetSymbol()}) { const Symbol &ultimate{procSym->GetUltimate()}; if (const auto *subpDetails{ ultimate.detailsIf()}) { if (auto attrs{subpDetails->cudaSubprogramAttrs()}) { isKernel = *attrs == common::CUDASubprogramAttrs::Global || *attrs == common::CUDASubprogramAttrs::Grid_Global; } } else if (const auto *procDetails{ ultimate.detailsIf()}) { isKernel = procDetails->isCUDAKernel(); } if (isKernel && chevrons->empty()) { Say("'%s' is a kernel subroutine and must be called with kernel launch parameters in chevrons"_err_en_US, procSym->name()); } } if (!isKernel && !chevrons->empty()) { Say("Kernel launch parameters in chevrons may not be used unless calling a kernel subroutine"_err_en_US); } if (CheckCall(callStmt.source, *proc, callee->arguments)) { callStmt.typedCall.Reset( new ProcedureRef{std::move(*proc), std::move(callee->arguments), HasAlternateReturns(callee->arguments)}, ProcedureRef::Deleter); DEREF(callStmt.typedCall.get()).set_chevrons(std::move(*chevrons)); return; } } if (!context_.AnyFatalError()) { std::string buf; llvm::raw_string_ostream dump{buf}; parser::DumpTree(dump, callStmt); Say("Internal error: Expression analysis failed on CALL statement: %s"_err_en_US, buf); } } } const Assignment *ExpressionAnalyzer::Analyze(const parser::AssignmentStmt &x) { if (!x.typedAssignment) { ArgumentAnalyzer analyzer{*this}; const auto &variable{std::get(x.t)}; analyzer.Analyze(variable); analyzer.Analyze(std::get(x.t)); std::optional assignment; if (!analyzer.fatalErrors()) { auto restorer{GetContextualMessages().SetLocation(variable.GetSource())}; std::optional procRef{analyzer.TryDefinedAssignment()}; if (!procRef) { analyzer.CheckForNullPointer( "in a non-pointer intrinsic assignment statement"); analyzer.CheckForAssumedRank("in an assignment statement"); const Expr &lhs{analyzer.GetExpr(0)}; if (auto dyType{lhs.GetType()}; dyType && dyType->IsPolymorphic()) { // 10.2.1.2p1(1) const Symbol *lastWhole0{UnwrapWholeSymbolOrComponentDataRef(lhs)}; const Symbol *lastWhole{ lastWhole0 ? &lastWhole0->GetUltimate() : nullptr}; if (!lastWhole || !IsAllocatable(*lastWhole)) { Say("Left-hand side of assignment may not be polymorphic unless assignment is to an entire allocatable"_err_en_US); } else if (evaluate::IsCoarray(*lastWhole)) { Say("Left-hand side of assignment may not be polymorphic if it is a coarray"_err_en_US); } } } 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; { auto restorer{AllowNullPointer()}; 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)}; common::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 Symbol *symbol) { return symbol && symbol->owner().IsGlobal() && symbol->has() && (!symbol->scope() /*ENTRY*/ || !symbol->scope()->sourceRange().Contains(callSite)); } std::optional ExpressionAnalyzer::CheckCall( parser::CharBlock callSite, const ProcedureDesignator &proc, ActualArguments &arguments) { bool treatExternalAsImplicit{ IsExternalCalledImplicitly(callSite, proc.GetSymbol())}; const Symbol *procSymbol{proc.GetSymbol()}; std::optional chars; if (procSymbol && procSymbol->has() && procSymbol->owner().IsGlobal()) { // Unknown global external, implicit interface; assume // characteristics from the actual arguments, and check // for consistency with other references. chars = characteristics::Procedure::FromActuals( proc, arguments, context_.foldingContext()); if (chars && procSymbol) { // Ensure calls over implicit interfaces are consistent auto name{procSymbol->name()}; if (auto iter{implicitInterfaces_.find(name)}; iter != implicitInterfaces_.end()) { std::string whyNot; if (!chars->IsCompatibleWith(iter->second.second, /*ignoreImplicitVsExplicit=*/false, &whyNot)) { if (auto *msg{Warn( common::UsageWarning::IncompatibleImplicitInterfaces, callSite, "Reference to the procedure '%s' has an implicit interface that is distinct from another reference: %s"_warn_en_US, name, whyNot)}) { msg->Attach( iter->second.first, "previous reference to '%s'"_en_US, name); } } } else { implicitInterfaces_.insert( std::make_pair(name, std::make_pair(callSite, *chars))); } } } if (!chars) { chars = characteristics::Procedure::Characterize( proc, context_.foldingContext(), /*emitError=*/true); } bool ok{true}; if (chars) { std::string whyNot; if (treatExternalAsImplicit && !chars->CanBeCalledViaImplicitInterface(&whyNot)) { if (auto *msg{Say(callSite, "References to the procedure '%s' require an explicit interface"_err_en_US, DEREF(procSymbol).name())}; msg && !whyNot.empty()) { msg->Attach(callSite, "%s"_because_en_US, whyNot); } } const SpecificIntrinsic *specificIntrinsic{proc.GetSpecificIntrinsic()}; bool procIsDummy{procSymbol && IsDummy(*procSymbol)}; if (chars->functionResult && chars->functionResult->IsAssumedLengthCharacter() && !specificIntrinsic && !procIsDummy) { Say(callSite, "Assumed-length character function must be defined with a length to be called"_err_en_US); } ok &= semantics::CheckArguments(*chars, arguments, context_, context_.FindScope(callSite), treatExternalAsImplicit, /*ignoreImplicitVsExplicit=*/false, specificIntrinsic); } 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()); } } if (ok && !treatExternalAsImplicit && procSymbol && !(chars && chars->HasExplicitInterface())) { if (const Symbol *global{FindGlobal(*procSymbol)}; global && global != procSymbol && IsProcedure(*global)) { // Check a known global definition behind a local interface if (auto globalChars{characteristics::Procedure::Characterize( *global, context_.foldingContext())}) { semantics::CheckArguments(*globalChars, arguments, context_, context_.FindScope(callSite), /*treatExternalAsImplicit=*/true, /*ignoreImplicitVsExplicit=*/false, nullptr /*not specific intrinsic*/); } } } 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(); analyzer.CheckForAssumedRank(); 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) { if (const auto *litConst{ std::get_if(&x.v.value().u)}) { if (const auto *intConst{ std::get_if(&litConst->u)}) { return Analyze(*intConst, true); } } 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(); analyzer.CheckForAssumedRank(); 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, true); } // Binary (dyadic) operations template