// Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "expression.h" #include "scope.h" #include "semantics.h" #include "symbol.h" #include "../common/idioms.h" #include "../evaluate/common.h" #include "../evaluate/fold.h" #include "../evaluate/tools.h" #include "../parser/characters.h" #include "../parser/parse-tree-visitor.h" #include "../parser/parse-tree.h" #include #include #include // TODO pmk remove when scaffolding is obsolete #undef PMKDEBUG // #define PMKDEBUG 1 #if PMKDEBUG #include "dump-parse-tree.h" #include #endif // Typedef for optional generic expressions (ubiquitous in this file) using MaybeExpr = std::optional>; namespace Fortran::parser { bool SourceLocationFindingVisitor::Pre(const Expr &x) { source = x.source; return false; } void SourceLocationFindingVisitor::Post(const CharBlock &at) { source = at; } } // 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::TypeCategory; // If a generic expression simply wraps a DataRef, extract it. // TODO: put in tools.h? template std::optional ExtractDataRef(A &&) { return std::nullopt; } template std::optional ExtractDataRef(Designator &&d) { return std::visit( [](auto &&x) -> std::optional { using Ty = std::decay_t; if constexpr (common::HasMember) { return {DataRef{std::move(x)}}; } return std::nullopt; }, std::move(d.u)); } template std::optional ExtractDataRef(Expr> &&expr) { using Ty = ResultType; if (auto *designator{std::get_if>(&expr.u)}) { return ExtractDataRef(std::move(*designator)); } else { return std::nullopt; } } template std::optional ExtractDataRef(Expr> &&expr) { return std::visit( [](auto &&specificExpr) { return ExtractDataRef(std::move(specificExpr)); }, std::move(expr.u)); } static std::optional ExtractDataRef(Expr &&expr) { return std::visit( common::visitors{ [](BOZLiteralConstant &&) -> std::optional { return std::nullopt; }, [](auto &&catExpr) { return ExtractDataRef(std::move(catExpr)); }, }, std::move(expr.u)); } template std::optional ExtractDataRef(std::optional &&x) { if (x.has_value()) { return ExtractDataRef(std::move(*x)); } return std::nullopt; } struct CallAndArguments { ProcedureDesignator procedureDesignator; ActualArguments arguments; }; struct DynamicTypeWithLength : public DynamicType { std::optional> length; }; std::optional AnalyzeTypeSpec( ExpressionAnalysisContext &context, const std::optional &spec) { if (spec.has_value()) { 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 kind{ToInt64(intrinsic->kind())}) { DynamicTypeWithLength result{{category, static_cast(*kind)}}; 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. if (len.GetExplicit().has_value()) { Expr copy{*len.GetExplicit()}; result.length = ConvertToType(std::move(copy)); } } return result; } } else if (const semantics::DerivedTypeSpec * derived{typeSpec->AsDerived()}) { return DynamicTypeWithLength{{TypeCategory::Derived, 0, derived}}; } } } return std::nullopt; } // Forward declarations of additional AnalyzeExpr specializations and overloads template MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &, const std::variant &); template MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &, const std::optional &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Designator &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::IntLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::SignedIntLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::RealLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::SignedRealLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::ComplexPart &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::ComplexLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::LogicalLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::HollerithLiteralConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::BOZLiteralConstant &); static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &, const parser::Name &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::NamedConstant &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Substring &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::ArrayElement &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::StructureComponent &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::CoindexedNamedObject &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::CharLiteralConstantSubstring &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::ArrayConstructor &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::StructureConstructor &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::FunctionReference &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Parentheses &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::UnaryPlus &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Negate &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::NOT &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::PercentLoc &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::DefinedUnary &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Power &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Multiply &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Divide &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Add &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Subtract &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::ComplexConstructor &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::Concat &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::LT &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::LE &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::EQ &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::NE &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::GE &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::GT &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::AND &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::OR &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::EQV &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::NEQV &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::XOR &); static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &, const parser::Expr::DefinedBinary &); // Catch-all unwrapper for AnalyzeExpr's most general case. template MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const A &x) { // Some compiler/version/option set combinations used to mysteriously // overlook the template specialization in expression.h that // redirected parser::Expr arguments, and they would arrive here // in the catch-all template. We've worked around that problem. static_assert( !std::is_same_v, "template specialization failed"); return AnalyzeExpr(context, x.u); } // Definitions of AnalyzeExpr() specializations follow. // Helper subroutines are intermixed. // Variants and optionals are silently traversed by AnalyzeExpr(). template MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const std::variant &u) { return std::visit([&](const auto &x) { return AnalyzeExpr(context, x); }, u); } template MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const std::optional &x) { if (x.has_value()) { return AnalyzeExpr(context, *x); } else { return std::nullopt; } } // Wraps a object in an explicitly typed representation (e.g., Designator<> // or FunctionRef<>) that has been instantiated on a dynamically chosen type. // TODO: move to tools.h? template typename WRAPPER, typename WRAPPED> MaybeExpr WrapperHelper(int kind, WRAPPED &&x) { return common::SearchTypes( TypeKindVisitor{kind, std::move(x)}); } template typename WRAPPER, typename WRAPPED> MaybeExpr TypedWrapper(const DynamicType &dyType, WRAPPED &&x) { switch (dyType.category) { case TypeCategory::Integer: return WrapperHelper( dyType.kind, std::move(x)); case TypeCategory::Real: return WrapperHelper( dyType.kind, std::move(x)); case TypeCategory::Complex: return WrapperHelper( dyType.kind, std::move(x)); case TypeCategory::Character: return WrapperHelper( dyType.kind, std::move(x)); case TypeCategory::Logical: return WrapperHelper( dyType.kind, std::move(x)); case TypeCategory::Derived: return AsGenericExpr(Expr{WRAPPER{std::move(x)}}); default: CRASH_NO_CASE; } } // Wraps a data reference in a typed Designator<>. static MaybeExpr Designate(DataRef &&dataRef) { const Symbol &symbol{dataRef.GetLastSymbol()}; if (std::optional dyType{GetSymbolType(&symbol)}) { return TypedWrapper( std::move(*dyType), std::move(dataRef)); } // TODO: graceful errors on CLASS(*) and TYPE(*) misusage return std::nullopt; } // Catch and resolve the ambiguous parse of a substring reference // that looks like a 1-D array element or section. static MaybeExpr ResolveAmbiguousSubstring( ExpressionAnalysisContext &context, ArrayRef &&ref) { const Symbol &symbol{ref.GetLastSymbol()}; if (std::optional dyType{GetSymbolType(&symbol)}) { if (dyType->category == TypeCategory::Character && ref.size() == 1) { DataRef base{std::visit([](auto &&y) { return DataRef{std::move(y)}; }, std::move(ref.base()))}; std::optional> lower, upper; if (std::visit( common::visitors{ [&](IndirectSubscriptIntegerExpr &&x) { lower = std::move(*x); return true; }, [&](Triplet &&triplet) { lower = triplet.lower(); upper = triplet.upper(); return triplet.IsStrideOne(); }, }, std::move(ref.at(0).u))) { return WrapperHelper( dyType->kind, Substring{std::move(base), std::move(lower), std::move(upper)}); } } } return std::nullopt; } // Some subscript semantic checks must be deferred until all of the // subscripts are in hand. This is also where we can catch the // ambiguous parse of a substring reference that looks like a 1-D array // element or section. static MaybeExpr CompleteSubscripts( ExpressionAnalysisContext &context, ArrayRef &&ref) { const Symbol &symbol{ref.GetLastSymbol()}; int symbolRank{symbol.Rank()}; int subscripts = ref.size(); if (subscripts == 0) { // A -> A(:,:) for (; subscripts < symbolRank; ++subscripts) { ref.emplace_back(Triplet{}); } } if (subscripts != symbolRank) { if (MaybeExpr substring{ ResolveAmbiguousSubstring(context, std::move(ref))}) { return substring; } context.Say("Reference to rank-%d object '%s' has %d subscripts"_err_en_US, symbolRank, symbol.name().ToString().data(), subscripts); } else if (subscripts == 0) { // nothing to check } else if (Component * component{std::get_if(&ref.base())}) { int baseRank{component->base().Rank()}; if (baseRank > 0) { int subscriptRank{0}; for (const auto &expr : ref.subscript()) { subscriptRank += expr.Rank(); } if (subscriptRank > 0) { context.Say("Subscripts of component '%s' of rank-%d derived type " "array have rank %d but must all be scalar"_err_en_US, symbol.name().ToString().data(), baseRank, subscriptRank); } } } else if (const auto *details{ symbol.detailsIf()}) { // C928 & C1002 if (Triplet * last{std::get_if(&ref.subscript().back().u)}) { if (!last->upper().has_value() && details->IsAssumedSize()) { context.Say("Assumed-size array '%s' must have explicit final " "subscript upper bound value"_err_en_US, symbol.name().ToString().data()); } } } return Designate(DataRef{std::move(ref)}); } // Applies subscripts to a data reference. static MaybeExpr ApplySubscripts(ExpressionAnalysisContext &context, DataRef &&dataRef, std::vector &&subscripts) { return std::visit( common::visitors{ [&](const Symbol *symbol) { return CompleteSubscripts( context, ArrayRef{*symbol, std::move(subscripts)}); }, [&](Component &&c) { return CompleteSubscripts( context, ArrayRef{std::move(c), std::move(subscripts)}); }, [&](auto &&) -> MaybeExpr { CHECK(!"bad base for ArrayRef"); return std::nullopt; }, }, std::move(dataRef.u)); } // Top-level checks for data references. Unsubscripted whole array references // get expanded -- e.g., MATRIX becomes MATRIX(:,:). static MaybeExpr TopLevelChecks( ExpressionAnalysisContext &context, DataRef &&dataRef) { bool addSubscripts{false}; if (Component * component{std::get_if(&dataRef.u)}) { const Symbol &symbol{component->GetLastSymbol()}; int componentRank{symbol.Rank()}; if (componentRank > 0) { int baseRank{component->base().Rank()}; if (baseRank > 0) { context.Say("reference to whole rank-%d component '%%%s' of " "rank-%d array of derived type is not allowed"_err_en_US, componentRank, symbol.name().ToString().data(), baseRank); } else { addSubscripts = true; } } } else if (const Symbol **symbol{std::get_if(&dataRef.u)}) { addSubscripts = (*symbol)->Rank() > 0; } if (addSubscripts) { if (MaybeExpr subscripted{ApplySubscripts( context, std::move(dataRef), std::vector{})}) { return subscripted; } } return Designate(std::move(dataRef)); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Designator &d) { // These checks have to be deferred to these "top level" data-refs where // we can be sure that there are no following subscripts (yet). if (MaybeExpr result{AnalyzeExpr(context, d.u)}) { if (std::optional dataRef{ evaluate::ExtractDataRef(std::move(result))}) { return TopLevelChecks(context, std::move(*dataRef)); } return result; } return std::nullopt; } // A utility subroutine to repackage optional expressions of various levels // of type specificity as fully general MaybeExpr values. template MaybeExpr AsMaybeExpr(A &&x) { return std::make_optional(AsGenericExpr(std::move(x))); } template MaybeExpr AsMaybeExpr(std::optional &&x) { if (x.has_value()) { return AsMaybeExpr(std::move(*x)); } return std::nullopt; } // Type kind parameter values for literal constants. static int AnalyzeKindParam(ExpressionAnalysisContext &context, const std::optional &kindParam, int defaultKind, int kanjiKind = -1) { if (!kindParam.has_value()) { return defaultKind; } return std::visit( common::visitors{ [](std::uint64_t k) { return static_cast(k); }, [&](const parser::Scalar< parser::Integer>> &n) { if (MaybeExpr ie{AnalyzeExpr(context, n)}) { if (std::optional i64{ToInt64(*ie)}) { int iv = *i64; if (iv == *i64) { return iv; } } } return defaultKind; }, [&](parser::KindParam::Kanji) { if (kanjiKind >= 0) { return kanjiKind; } context.Say("Kanji not allowed here"_err_en_US); return defaultKind; }, }, kindParam->u); } // Common handling of parser::IntLiteralConstant and SignedIntLiteralConstant template MaybeExpr IntLiteralConstant( ExpressionAnalysisContext &context, const PARSED &x) { int kind{ AnalyzeKindParam(context, std::get>(x.t), context.GetDefaultKind(TypeCategory::Integer))}; auto value{std::get<0>(x.t)}; // std::(u)int64_t if (!context.CheckIntrinsicKind(TypeCategory::Integer, kind)) { return std::nullopt; } return common::SearchTypes( TypeKindVisitor{ kind, static_cast(value)}); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::IntLiteralConstant &x) { return IntLiteralConstant(context, x); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::SignedIntLiteralConstant &x) { return IntLiteralConstant(context, x); } template Constant ReadRealLiteral( parser::CharBlock source, FoldingContext &context) { const char *p{source.begin()}; auto valWithFlags{Scalar::Read(p, context.rounding())}; CHECK(p == source.end()); RealFlagWarnings(context, valWithFlags.flags, "conversion of REAL literal"); auto value{valWithFlags.value}; if (context.flushSubnormalsToZero()) { value = value.FlushSubnormalToZero(); } return {value}; } struct RealTypeVisitor { using Result = std::optional>; using Types = RealTypes; RealTypeVisitor(int k, parser::CharBlock lit, FoldingContext &ctx) : kind{k}, literal{lit}, context{ctx} {} template Result Test() { if (kind == T::kind) { return {AsCategoryExpr(ReadRealLiteral(literal, context))}; } return std::nullopt; } int kind; parser::CharBlock literal; FoldingContext &context; }; // Reads a real literal constant and encodes it with the right kind. static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::RealLiteralConstant &x) { // Use a local message context around the real literal for better // provenance on any messages. auto save{context.GetContextualMessages().SetLocation(x.real.source)}; // If a kind parameter appears, it defines the kind of the literal and any // letter used in an exponent part (e.g., the 'E' in "6.02214E+23") // should agree. In the absence of an explicit kind parameter, any exponent // letter determines the kind. Otherwise, defaults apply. // TODO: warn on inexact conversions? auto &defaults{context.context().defaultKinds()}; int defaultKind{defaults.GetDefaultKind(TypeCategory::Real)}; const char *end{x.real.source.end()}; std::optional letterKind; for (const char *p{x.real.source.begin()}; p < end; ++p) { if (parser::IsLetter(*p)) { switch (*p) { case 'e': letterKind = defaults.GetDefaultKind(TypeCategory::Real); break; case 'd': letterKind = defaults.doublePrecisionKind(); break; case 'q': letterKind = defaults.quadPrecisionKind(); break; default: context.Say("unknown exponent letter '%c'"_err_en_US, *p); } break; } } if (letterKind.has_value()) { defaultKind = *letterKind; } auto kind{AnalyzeKindParam(context, x.kind, defaultKind)}; if (letterKind.has_value() && kind != *letterKind) { context.Say( "explicit kind parameter on real constant disagrees with " "exponent letter"_en_US); } auto result{common::SearchTypes( RealTypeVisitor{kind, x.real.source, context.GetFoldingContext()})}; if (!result.has_value()) { context.Say("unsupported REAL(KIND=%d)"_err_en_US, kind); } return AsMaybeExpr(std::move(result)); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::SignedRealLiteralConstant &x) { if (MaybeExpr result{ AnalyzeExpr(context, std::get(x.t))}) { auto *realExpr{std::get_if>(&result->u)}; CHECK(realExpr != nullptr); if (auto sign{std::get>(x.t)}) { if (sign == parser::Sign::Negative) { return {AsGenericExpr(-std::move(*realExpr))}; } } return result; } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::ComplexPart &x) { return AnalyzeExpr(context, x.u); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::ComplexLiteralConstant &z) { return AsMaybeExpr(ConstructComplex(context.GetContextualMessages(), AnalyzeExpr(context, std::get<0>(z.t)), AnalyzeExpr(context, std::get<1>(z.t)), context.GetDefaultKind(TypeCategory::Real))); } // CHARACTER literal processing. static MaybeExpr AnalyzeString( ExpressionAnalysisContext &context, std::string &&string, int kind) { if (!context.CheckIntrinsicKind(TypeCategory::Character, kind)) { return std::nullopt; } if (kind == 1) { return {AsGenericExpr( Constant>{std::move(string)})}; } else if (std::optional unicode{ parser::DecodeUTF8(string)}) { if (kind == 4) { return {AsGenericExpr( Constant>{std::move(*unicode)})}; } CHECK(kind == 2); // TODO: better Kanji support std::u16string result; for (const char32_t &ch : *unicode) { result += static_cast(ch); } return {AsGenericExpr( Constant>{std::move(result)})}; } else { context.Say( "bad UTF-8 encoding of CHARACTER(KIND=%d) literal"_err_en_US, kind); return std::nullopt; } } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::CharLiteralConstant &x) { int kind{AnalyzeKindParam( context, std::get>(x.t), 1)}; auto value{std::get(x.t)}; return AnalyzeString(context, std::move(value), kind); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::HollerithLiteralConstant &x) { int kind{context.GetDefaultKind(TypeCategory::Character)}; auto value{x.v}; return AnalyzeString(context, std::move(value), kind); } // .TRUE. and .FALSE. of various kinds static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::LogicalLiteralConstant &x) { auto kind{ AnalyzeKindParam(context, std::get>(x.t), context.GetDefaultKind(TypeCategory::Logical))}; bool value{std::get(x.t)}; auto result{common::SearchTypes( TypeKindVisitor{ kind, std::move(value)})}; if (!result.has_value()) { context.Say("unsupported LOGICAL(KIND=%d)"_err_en_US, kind); } return result; } // BOZ typeless literals static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::BOZLiteralConstant &x) { const char *p{x.v.data()}; 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 == '"'); auto value{BOZLiteralConstant::ReadUnsigned(++p, base)}; if (*p != '"') { context.Say( "invalid digit ('%c') in BOZ literal %s"_err_en_US, *p, x.v.data()); return std::nullopt; } if (value.overflow) { context.Say("BOZ literal %s too large"_err_en_US, x.v.data()); return std::nullopt; } return {AsGenericExpr(std::move(value.value))}; } // For use with SearchTypes to create a TypeParamInquiry with the // right integer kind. struct TypeParamInquiryVisitor { using Result = std::optional>; using Types = IntegerTypes; TypeParamInquiryVisitor(int k, SymbolOrComponent &&b, const Symbol ¶m) : kind{k}, base{std::move(b)}, parameter{param} {} template Result Test() { if (kind == T::kind) { return Expr{ Expr{TypeParamInquiry{std::move(base), parameter}}}; } return std::nullopt; } int kind; SymbolOrComponent base; const Symbol ¶meter; }; static std::optional> MakeTypeParamInquiry( const Symbol *symbol) { if (std::optional dyType{GetSymbolType(symbol)}) { if (dyType->category == TypeCategory::Integer) { return common::SearchTypes(TypeParamInquiryVisitor{ dyType->kind, SymbolOrComponent{nullptr}, *symbol}); } } return std::nullopt; } // Names and named constants static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Name &n) { if (std::optional kind{context.IsAcImpliedDo(n.source)}) { return AsMaybeExpr(ConvertToKind( *kind, AsExpr(ImpliedDoIndex{n.source}))); } else if (n.symbol == nullptr) { // error should have been reported in name resolution } else if (n.symbol->attrs().test(semantics::Attr::PARAMETER)) { if (auto *details{n.symbol->detailsIf()}) { if (auto &init{details->init()}) { return init; } } // TODO: enumerators, do they have the PARAMETER attribute? } else if (n.symbol->detailsIf()) { // A bare reference to a derived type parameter (within a parameterized // derived type definition) return AsMaybeExpr(MakeTypeParamInquiry(n.symbol)); } else if (MaybeExpr result{Designate(DataRef{*n.symbol})}) { return result; } else { context.Say(n.source, "not of a supported type and kind"_err_en_US); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::NamedConstant &n) { if (MaybeExpr value{AnalyzeExpr(context, n.v)}) { Expr folded{Fold(context.GetFoldingContext(), std::move(*value))}; if (IsConstantExpr(folded)) { return {folded}; } context.Say(n.v.source, "must be a constant"_err_en_US); } return std::nullopt; } // Substring references static std::optional> GetSubstringBound( ExpressionAnalysisContext &context, const std::optional &bound) { if (bound.has_value()) { if (MaybeExpr expr{AnalyzeExpr(context, *bound)}) { if (expr->Rank() > 1) { context.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 { context.Say("substring bound expression is not INTEGER"_err_en_US); } } } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Substring &ss) { if (MaybeExpr baseExpr{ AnalyzeExpr(context, std::get(ss.t))}) { if (std::optional dataRef{ExtractDataRef(std::move(*baseExpr))}) { if (MaybeExpr newBaseExpr{TopLevelChecks(context, std::move(*dataRef))}) { if (std::optional checked{ ExtractDataRef(std::move(*newBaseExpr))}) { const parser::SubstringRange &range{ std::get(ss.t)}; std::optional> first{ GetSubstringBound(context, std::get<0>(range.t))}; std::optional> last{ GetSubstringBound(context, std::get<1>(range.t))}; const Symbol &symbol{checked->GetLastSymbol()}; if (std::optional dynamicType{GetSymbolType(&symbol)}) { if (dynamicType->category == TypeCategory::Character) { return WrapperHelper(dynamicType->kind, Substring{std::move(checked.value()), std::move(first), std::move(last)}); } } context.Say("substring may apply only to CHARACTER"_err_en_US); } } } } return std::nullopt; } // CHARACTER literal substrings static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::CharLiteralConstantSubstring &x) { const parser::SubstringRange &range{std::get(x.t)}; std::optional> lower{ GetSubstringBound(context, std::get<0>(range.t))}; std::optional> upper{ GetSubstringBound(context, std::get<1>(range.t))}; if (MaybeExpr string{ AnalyzeExpr(context, std::get(x.t))}) { if (auto *charExpr{std::get_if>(&string->u)}) { Expr length{std::visit( [](const auto &ckExpr) { return ckExpr.LEN(); }, charExpr->u)}; if (!lower.has_value()) { lower = Expr{1}; } if (!upper.has_value()) { upper = Expr{ static_cast(ToInt64(length).value())}; } return std::visit( [&](auto &&ckExpr) -> MaybeExpr { using Result = ResultType; auto *cp{std::get_if>(&ckExpr.u)}; CHECK(cp != nullptr); // the parent was parsed as a constant string CHECK(cp->size() == 1); StaticDataObject::Pointer staticData{StaticDataObject::Create()}; staticData->set_alignment(Result::kind) .set_itemBytes(Result::kind) .Push(**cp); Substring substring{std::move(staticData), std::move(lower.value()), std::move(upper.value())}; return AsGenericExpr(Expr{ Expr{Designator{std::move(substring)}}}); }, std::move(charExpr->u)); } } return std::nullopt; } // Subscripted array references static std::optional> AsSubscript( ExpressionAnalysisContext &context, MaybeExpr &&expr) { if (expr.has_value()) { if (expr->Rank() > 1) { context.Say("subscript 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 { context.Say("subscript expression is not INTEGER"_err_en_US); } } return std::nullopt; } static std::optional> TripletPart( ExpressionAnalysisContext &context, const std::optional &s) { if (s.has_value()) { return AsSubscript(context, AnalyzeExpr(context, *s)); } return std::nullopt; } static std::optional AnalyzeSectionSubscript( ExpressionAnalysisContext &context, const parser::SectionSubscript &ss) { return std::visit( common::visitors{ [&](const parser::SubscriptTriplet &t) { return std::make_optional( Subscript{Triplet{TripletPart(context, std::get<0>(t.t)), TripletPart(context, std::get<1>(t.t)), TripletPart(context, std::get<2>(t.t))}}); }, [&](const auto &s) -> std::optional { if (auto subscriptExpr{ AsSubscript(context, AnalyzeExpr(context, s))}) { return {Subscript{std::move(*subscriptExpr)}}; } else { return std::nullopt; } }, }, ss.u); } static std::vector AnalyzeSectionSubscripts( ExpressionAnalysisContext &context, const std::list &sss) { std::vector subscripts; for (const auto &s : sss) { if (auto subscript{AnalyzeSectionSubscript(context, s)}) { subscripts.emplace_back(std::move(*subscript)); } } return subscripts; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::ArrayElement &ae) { std::vector subscripts{ AnalyzeSectionSubscripts(context, ae.subscripts)}; if (MaybeExpr baseExpr{AnalyzeExpr(context, ae.base)}) { if (std::optional dataRef{ExtractDataRef(std::move(*baseExpr))}) { if (MaybeExpr result{ApplySubscripts( context, std::move(*dataRef), std::move(subscripts))}) { return result; } } } context.Say( "subscripts may be applied only to an object or component"_err_en_US); return std::nullopt; } // Type parameter inquiries apply to data references, but don't depend // on any trailing (co)subscripts. static SymbolOrComponent IgnoreAnySubscripts( Designator &&designator) { return std::visit( common::visitors{ [](const Symbol *symbol) { return SymbolOrComponent{symbol}; }, [](Component &&component) { return SymbolOrComponent{component}; }, [](ArrayRef &&arrayRef) { return std::move(arrayRef.base()); }, [](CoarrayRef &&coarrayRef) { return SymbolOrComponent{&coarrayRef.GetLastSymbol()}; }, }, std::move(designator.u)); } // Components of parent derived types are explicitly represented as such. static std::optional CreateComponent( DataRef &&base, const Symbol &component, const semantics::Scope &scope) { if (&component.owner() == &scope) { return {Component{std::move(base), component}}; } if (const semantics::Scope * parentScope{scope.GetDerivedTypeParent()}) { if (const Symbol * parentComponent{parentScope->GetSymbol()}) { return CreateComponent( DataRef{Component{std::move(base), *parentComponent}}, component, *parentScope); } } return std::nullopt; } // Derived type component references and type parameter inquiries static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::StructureComponent &sc) { const auto &name{sc.component.source}; if (MaybeExpr base{AnalyzeExpr(context, sc.base)}) { Symbol *sym{sc.component.symbol}; if (sym == nullptr) { context.Say(sc.component.source, "component name was not resolved to a symbol"_err_en_US); } else if (auto *dtExpr{UnwrapExpr>(*base)}) { const semantics::DerivedTypeSpec *dtSpec{nullptr}; if (std::optional dtDyTy{dtExpr->GetType()}) { dtSpec = dtDyTy->derived; } if (sym->detailsIf()) { if (auto *designator{UnwrapExpr>(*dtExpr)}) { if (std::optional dyType{GetSymbolType(sym)}) { if (dyType->category == TypeCategory::Integer) { return AsMaybeExpr( common::SearchTypes(TypeParamInquiryVisitor{dyType->kind, IgnoreAnySubscripts(std::move(*designator)), *sym})); } } context.Say(name, "type parameter is not INTEGER"_err_en_US); } else { context.Say(name, "type parameter inquiry must be applied to " "a designator"_err_en_US); } } else if (dtSpec == nullptr || dtSpec->scope() == nullptr) { context.Say(name, "TODO: base of component reference lacks a derived type"_err_en_US); } else if (std::optional dataRef{ ExtractDataRef(std::move(*dtExpr))}) { if (auto component{ CreateComponent(std::move(*dataRef), *sym, *dtSpec->scope())}) { return Designate(DataRef{std::move(*component)}); } else { context.Say(name, "component is not in scope of derived TYPE(%s)"_err_en_US, dtSpec->typeSymbol().name().ToString().data()); } } else { context.Say(name, "base of component reference must be a data reference"_err_en_US); } } else if (auto *details{sym->detailsIf()}) { // special part-ref: %re, %im, %kind, %len // Type errors are detected and reported in semantics. using MiscKind = semantics::MiscDetails::Kind; MiscKind kind{details->kind()}; if (kind == MiscKind::ComplexPartRe || kind == MiscKind::ComplexPartIm) { if (auto *zExpr{std::get_if>(&base->u)}) { if (std::optional dataRef{ ExtractDataRef(std::move(*zExpr))}) { Expr realExpr{std::visit( [&](const auto &z) { using PartType = typename ResultType::Part; auto part{kind == MiscKind::ComplexPartRe ? ComplexPart::Part::RE : ComplexPart::Part::IM}; return AsCategoryExpr(Designator{ ComplexPart{std::move(*dataRef), part}}); }, zExpr->u)}; return {AsGenericExpr(std::move(realExpr))}; } } } else if (kind == MiscKind::KindParamInquiry || kind == MiscKind::LenParamInquiry) { // Convert x%KIND -> intrinsic KIND(x), x%LEN -> intrinsic LEN(x) SpecificIntrinsic func{name.ToString()}; func.type = context.GetDefaultKindOfType(TypeCategory::Integer); return TypedWrapper(*func.type, ProcedureRef{ProcedureDesignator{std::move(func)}, ActualArguments{ActualArgument{std::move(*base)}}}); } else { common::die("unexpected kind"); } } else { context.Say( name, "derived type required before component reference"_err_en_US); } } return std::nullopt; } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::CoindexedNamedObject &co) { context.Say("TODO: CoindexedNamedObject unimplemented"_err_en_US); return std::nullopt; } static int IntegerTypeSpecKind( ExpressionAnalysisContext &context, const parser::IntegerTypeSpec &spec) { Expr value{context.Analyze(TypeCategory::Integer, spec.v)}; if (auto kind{ToInt64(value)}) { return static_cast(*kind); } context.SayAt(spec, "Constant INTEGER kind value required here"_err_en_US); return context.GetDefaultKind(TypeCategory::Integer); } template std::optional>> GetSpecificIntExpr( ExpressionAnalysisContext &context, const A &x) { if (MaybeExpr y{AnalyzeExpr(context, x)}) { Expr *intExpr{UnwrapExpr>(*y)}; CHECK(intExpr != nullptr); return ConvertToType>( std::move(*intExpr)); } return std::nullopt; } // Array constructors class ArrayConstructorContext { public: ArrayConstructorContext( ExpressionAnalysisContext &c, std::optional &t) : exprContext_{c}, type_{t} {} ArrayConstructorContext(const ArrayConstructorContext &) = default; void Push(MaybeExpr &&); void Add(const parser::AcValue &); std::optional &type() const { return type_; } const ArrayConstructorValues &values() { return values_; } private: ExpressionAnalysisContext &exprContext_; std::optional &type_; bool explicitType_{type_.has_value()}; std::optional constantLength_; ArrayConstructorValues values_; }; void ArrayConstructorContext::Push(MaybeExpr &&x) { if (x.has_value()) { DynamicTypeWithLength xType; if (auto dyType{x->GetType()}) { *static_cast(&xType) = *dyType; } if (Expr * charExpr{UnwrapExpr>(*x)}) { CHECK(xType.category == TypeCategory::Character); xType.length = std::visit([](const auto &kc) { return kc.LEN(); }, charExpr->u); } if (!type_.has_value()) { // 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. // TODO pmk: better type compatibility checks for derived types CHECK(!explicitType_); type_ = std::move(xType); constantLength_ = ToInt64(type_->length); values_.Push(std::move(*x)); } else if (!explicitType_) { if (static_cast(*type_) == static_cast(xType)) { values_.Push(std::move(*x)); if (auto thisLen{ToInt64(xType.length)}) { if (constantLength_.has_value()) { if (exprContext_.context().warnOnNonstandardUsage() && *thisLen != *constantLength_) { exprContext_.Say( "Character literal in array constructor without explicit " "type has different length than earlier element"_en_US); } if (*thisLen > *constantLength_) { // Language extension: use the longest literal to determine the // length of the array constructor's character elements, not the // first, when there is no explicit type. *constantLength_ = *thisLen; type_->length = std::move(xType.length); } } else { constantLength_ = *thisLen; type_->length = std::move(xType.length); } } } else { exprContext_.Say( "Values in array constructor must have the same declared type " "when no explicit type appears"_err_en_US); } } else { if (auto cast{ConvertToType(*type_, std::move(*x))}) { values_.Push(std::move(*cast)); } else { exprContext_.Say( "Value in array constructor could not be converted to the type " "of the array"_err_en_US); } } } } void ArrayConstructorContext::Add(const parser::AcValue &x) { using IntType = ResultType; std::visit( common::visitors{ [&](const parser::AcValue::Triplet &triplet) { // Transform l:u(:s) into (_,_=l,u(,s)) with an anonymous index '_' std::optional> lower{ GetSpecificIntExpr( exprContext_, std::get<0>(triplet.t))}; std::optional> upper{ GetSpecificIntExpr( exprContext_, std::get<1>(triplet.t))}; std::optional> stride{ GetSpecificIntExpr( exprContext_, std::get<2>(triplet.t))}; if (lower.has_value() && upper.has_value()) { if (!stride.has_value()) { stride = Expr{1}; } if (!type_.has_value()) { type_ = DynamicTypeWithLength{IntType::GetType()}; } ArrayConstructorContext nested{*this}; parser::CharBlock name; nested.Push(Expr{ Expr{Expr{ImpliedDoIndex{name}}}}); values_.Push(ImpliedDo{name, std::move(*lower), std::move(*upper), std::move(*stride), std::move(nested.values_)}); } }, [&](const common::Indirection &expr) { auto restorer{ exprContext_.GetContextualMessages().SetLocation(expr->source)}; if (MaybeExpr v{exprContext_.Analyze(*expr)}) { Push(std::move(*v)); } }, [&](const common::Indirection &impliedDo) { const auto &control{ std::get(impliedDo->t)}; const auto &bounds{ std::get>(control.t)}; parser::CharBlock name{bounds.name.thing.thing.source}; int kind{IntType::kind}; if (auto &its{std::get>( control.t)}) { kind = IntegerTypeSpecKind(exprContext_, *its); } bool inserted{exprContext_.AddAcImpliedDo(name, kind)}; if (!inserted) { exprContext_.SayAt(name, "Implied DO index is active in surrounding implied DO loop " "and cannot have the same name"_err_en_US); } std::optional> lower{ GetSpecificIntExpr(exprContext_, bounds.lower)}; std::optional> upper{ GetSpecificIntExpr(exprContext_, bounds.upper)}; std::optional> stride{ GetSpecificIntExpr(exprContext_, bounds.step)}; ArrayConstructorContext nested{*this}; for (const auto &value : std::get>(impliedDo->t)) { nested.Add(value); } if (lower.has_value() && upper.has_value()) { if (!stride.has_value()) { stride = Expr{1}; } values_.Push(ImpliedDo{name, std::move(*lower), std::move(*upper), std::move(*stride), std::move(nested.values_)}); } if (inserted) { exprContext_.RemoveAcImpliedDo(name); } }, }, x.u); } // 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.values()) { std::visit( common::visitors{ [&](CopyableIndirection> &&expr) { auto *typed{UnwrapExpr>(*expr)}; CHECK(typed != nullptr); to.Push(std::move(*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; } struct ArrayConstructorTypeVisitor { using Result = MaybeExpr; using Types = AllTypes; template Result Test() { if (type.category == T::category) { if constexpr (T::category == TypeCategory::Derived) { CHECK(type.derived != nullptr); return AsMaybeExpr(ArrayConstructor{ *type.derived, MakeSpecific(std::move(values))}); } else if (type.kind == T::kind) { if constexpr (T::category == TypeCategory::Character) { CHECK(type.length.has_value()); return AsMaybeExpr(ArrayConstructor{ std::move(*type.length), MakeSpecific(std::move(values))}); } else { return AsMaybeExpr( ArrayConstructor{MakeSpecific(std::move(values))}); } } } return std::nullopt; } DynamicTypeWithLength type; ArrayConstructorValues values; }; static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &exprContext, const parser::ArrayConstructor &array) { const parser::AcSpec &acSpec{array.v}; std::optional type{ AnalyzeTypeSpec(exprContext, acSpec.type)}; ArrayConstructorContext context{exprContext, type}; for (const parser::AcValue &value : acSpec.values) { context.Add(value); } if (type.has_value()) { ArrayConstructorTypeVisitor visitor{ std::move(*type), std::move(context.values())}; return common::SearchTypes(std::move(visitor)); } return std::nullopt; } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::StructureConstructor &structure) { auto &parsedType{std::get(structure.t)}; parser::CharBlock typeName{std::get(parsedType.t).source}; if (parsedType.derivedTypeSpec == nullptr) { context.Say("INTERNAL: StructureConstructor lacks type"_err_en_US); return std::nullopt; } const auto &spec{*parsedType.derivedTypeSpec}; CHECK(spec.scope() != nullptr); const Symbol &typeSymbol{spec.typeSymbol()}; if (typeSymbol.attrs().test(semantics::Attr::ABSTRACT)) { // C796 if (auto *msg{context.Say(typeName, "ABSTRACT derived type '%s' cannot be used in a structure constructor"_err_en_US, typeName.ToString().data())}) { msg->Attach( typeSymbol.name(), "Declaration of ABSTRACT derived type"_en_US); } } // This list holds 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. const auto &details{typeSymbol.get()}; std::list components{details.OrderComponents(*spec.scope())}; auto nextAnonymous{components.begin()}; std::set unavailable; bool anyKeyword{false}; StructureConstructor result{spec}; bool checkConflicts{true}; for (const auto &component : std::get>(structure.t)) { const parser::Expr &expr{ *std::get(component.t).v}; parser::CharBlock source{expr.source}; const Symbol *symbol{nullptr}; if (const auto &kw{std::get>(component.t)}) { source = kw->v.source; symbol = kw->v.symbol; anyKeyword = true; } else { if (anyKeyword) { // C7100 context.Say(source, "Value in structure constructor lacks a component name"_err_en_US); checkConflicts = false; // stem cascade } while (nextAnonymous != components.end()) { symbol = *nextAnonymous++; if (!symbol->test(Symbol::Flag::ParentComp)) { break; } } if (symbol == nullptr) { context.Say( source, "Unexpected value in structure constructor"_err_en_US); } } if (symbol != nullptr) { if (symbol->has()) { context.Say(source, "Type parameter '%s' cannot be a component of this structure constructor"_err_en_US, symbol->name().ToString().data()); } else if (checkConflicts) { auto componentIter{ std::find(components.begin(), components.end(), symbol)}; if (unavailable.find(symbol->name()) != unavailable.cend()) { // C797, C798 context.Say(source, "Component '%s' conflicts with another component earlier in this structure constructor"_err_en_US, symbol->name().ToString().data()); } 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 (MaybeExpr value{AnalyzeExpr(context, expr)}) { // TODO pmk: C7104, C7105 check that pointer components are // being initialized with data/procedure designators appropriately result.Add(*symbol, std::move(*value)); } } } // 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() && !symbol->attrs().test(semantics::Attr::ALLOCATABLE)) { if (const auto *details{ symbol->detailsIf()}) { if (details->init().has_value()) { result.Add(*symbol, common::Clone(*details->init())); } else { // C799 if (auto *msg{context.Say(typeName, "Structure constructor lacks a value for component '%s'"_err_en_US, symbol->name().ToString().data())}) { msg->Attach(symbol->name(), "Absent component"_en_US); } } } } } // TODO pmk check type compatibility on component expressions return AsMaybeExpr(Expr{std::move(result)}); } static std::optional Procedure( ExpressionAnalysisContext &context, const parser::ProcedureDesignator &pd, ActualArguments &arguments) { return std::visit( common::visitors{ [&](const parser::Name &n) -> std::optional { if (n.symbol == nullptr) { context.Say( "TODO INTERNAL no symbol for procedure designator name '%s'"_err_en_US, n.ToString().data()); return std::nullopt; } return std::visit( common::visitors{ [&](const semantics::ProcEntityDetails &p) -> std::optional { if (p.HasExplicitInterface()) { // TODO: check actual arguments vs. interface } else { CallCharacteristics cc{n.source}; if (std::optional specificCall{ context.context().intrinsics().Probe(cc, arguments, &context.GetContextualMessages())}) { return {CallAndArguments{ ProcedureDesignator{ std::move(specificCall->specificIntrinsic)}, std::move(specificCall->arguments)}}; } else { // TODO: if name is not INTRINSIC, call with implicit // interface } } return {CallAndArguments{ProcedureDesignator{*n.symbol}, std::move(arguments)}}; }, [&](const auto &) -> std::optional { context.Say( "TODO: unimplemented/invalid kind of symbol as procedure designator '%s'"_err_en_US, n.ToString().data()); return std::nullopt; }, }, n.symbol->details()); }, [&](const parser::ProcComponentRef &pcr) -> std::optional { if (MaybeExpr component{AnalyzeExpr(context, pcr.v)}) { // TODO distinguish PCR from TBP // TODO optional PASS argument for TBP context.Say("TODO: proc component ref"_err_en_US); return std::nullopt; } else { return std::nullopt; } }, }, pd.u); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::FunctionReference &funcRef) { // TODO: C1002: Allow a whole assumed-size array to appear if the dummy // argument would accept it. Handle by special-casing the context // ActualArg -> Variable -> Designator. ActualArguments arguments; for (const auto &arg : std::get>(funcRef.v.t)) { MaybeExpr actualArgExpr; std::visit( common::visitors{ [&](const common::Indirection &v) { actualArgExpr = AnalyzeExpr(context, *v); }, [&](const common::Indirection &x) { actualArgExpr = AnalyzeExpr(context, *x); }, [&](const parser::Name &n) { context.Say("TODO: procedure name actual arg"_err_en_US); }, [&](const parser::ProcComponentRef &) { context.Say("TODO: proc component ref actual arg"_err_en_US); }, [&](const parser::AltReturnSpec &) { context.Say( "alternate return specification cannot appear on function reference"_err_en_US); }, [&](const parser::ActualArg::PercentRef &) { context.Say("TODO: %REF() argument"_err_en_US); }, [&](const parser::ActualArg::PercentVal &) { context.Say("TODO: %VAL() argument"_err_en_US); }, }, std::get(arg.t).u); if (actualArgExpr.has_value()) { arguments.emplace_back(std::make_optional( Fold(context.GetFoldingContext(), std::move(*actualArgExpr)))); if (const auto &argKW{std::get>(arg.t)}) { arguments.back()->keyword = argKW->v.source; } } else { return std::nullopt; } } // TODO: map user generic to specific procedure if (std::optional proc{Procedure(context, std::get(funcRef.v.t), arguments)}) { if (std::optional dyType{ proc->procedureDesignator.GetType()}) { return TypedWrapper(*dyType, ProcedureRef{std::move(proc->procedureDesignator), std::move(proc->arguments)}); } } return std::nullopt; } // Unary operations static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Parentheses &x) { // TODO: C1003: A parenthesized function reference may not return a // procedure pointer. if (MaybeExpr operand{AnalyzeExpr(context, *x.v)}) { return std::visit( common::visitors{ [&](BOZLiteralConstant &&boz) { return operand; // ignore parentheses around typeless constants }, [&](Expr &&) { // TODO: parenthesized derived type variable return operand; }, [](auto &&catExpr) { return std::visit( [](auto &&expr) -> MaybeExpr { using Ty = ResultType; return {AsGenericExpr(Parentheses{std::move(expr)})}; }, std::move(catExpr.u)); }, }, std::move(operand->u)); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::UnaryPlus &x) { MaybeExpr value{AnalyzeExpr(context, *x.v)}; if (value.has_value()) { std::visit( common::visitors{ [](const BOZLiteralConstant &) {}, // allow +Z'1', it's harmless [&](const auto &catExpr) { TypeCategory cat{ResultType::category}; if (cat != TypeCategory::Integer && cat != TypeCategory::Real && cat != TypeCategory::Complex) { context.Say( "operand of unary + must be of a numeric type"_err_en_US); } }, }, value->u); } return value; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Negate &x) { if (MaybeExpr operand{AnalyzeExpr(context, *x.v)}) { return Negation(context.GetContextualMessages(), std::move(*operand)); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::NOT &x) { if (MaybeExpr operand{AnalyzeExpr(context, *x.v)}) { return std::visit( common::visitors{ [](Expr &&lx) -> MaybeExpr { return {AsGenericExpr(LogicalNegation(std::move(lx)))}; }, [&](auto &&) -> MaybeExpr { // TODO: accept INTEGER operand and maybe typeless // if not overridden context.Say("Operand of .NOT. must be LOGICAL"_err_en_US); return std::nullopt; }, }, std::move(operand->u)); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::PercentLoc &) { context.Say("TODO: %LOC unimplemented"_err_en_US); return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::DefinedUnary &) { context.Say("TODO: DefinedUnary unimplemented"_err_en_US); return std::nullopt; } // Binary (dyadic) operations // TODO: check defined operators for illegal intrinsic operator cases template class OPR, typename PARSED> MaybeExpr BinaryOperationHelper( ExpressionAnalysisContext &context, const PARSED &x) { if (auto both{common::AllPresent(AnalyzeExpr(context, *std::get<0>(x.t)), AnalyzeExpr(context, *std::get<1>(x.t)))}) { ConformabilityCheck(context.GetContextualMessages(), std::get<0>(*both), std::get<1>(*both)); return NumericOperation(context.GetContextualMessages(), std::move(std::get<0>(*both)), std::move(std::get<1>(*both)), context.GetDefaultKind(TypeCategory::Real)); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Power &x) { return BinaryOperationHelper(context, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Multiply &x) { return BinaryOperationHelper(context, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Divide &x) { return BinaryOperationHelper(context, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Add &x) { return BinaryOperationHelper(context, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Subtract &x) { return BinaryOperationHelper(context, x); } static MaybeExpr AnalyzeExpr(ExpressionAnalysisContext &context, const parser::Expr::ComplexConstructor &x) { auto re{AnalyzeExpr(context, *std::get<0>(x.t))}; auto im{AnalyzeExpr(context, *std::get<1>(x.t))}; if (re.has_value() && im.has_value()) { ConformabilityCheck(context.GetContextualMessages(), *re, *im); } return AsMaybeExpr( ConstructComplex(context.GetContextualMessages(), std::move(re), std::move(im), context.GetDefaultKind(TypeCategory::Real))); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::Concat &x) { if (auto both{common::AllPresent(AnalyzeExpr(context, *std::get<0>(x.t)), AnalyzeExpr(context, *std::get<1>(x.t)))}) { ConformabilityCheck(context.GetContextualMessages(), std::get<0>(*both), std::get<1>(*both)); return std::visit( common::visitors{ [&](Expr &&cx, Expr &&cy) { return std::visit( [&](auto &&cxk, auto &&cyk) -> MaybeExpr { using Ty = ResultType; if constexpr (std::is_same_v>) { return {AsGenericExpr( Concat{std::move(cxk), std::move(cyk)})}; } else { context.Say( "Operands of // must be the same kind of CHARACTER"_err_en_US); return std::nullopt; } }, std::move(cx.u), std::move(cy.u)); }, [&](auto &&, auto &&) -> MaybeExpr { context.Say("Operands of // must be CHARACTER"_err_en_US); return std::nullopt; }, }, std::move(std::get<0>(*both).u), std::move(std::get<1>(*both).u)); } return std::nullopt; } // TODO: check defined operators for illegal intrinsic operator cases template MaybeExpr RelationHelper(ExpressionAnalysisContext &context, RelationalOperator opr, const PARSED &x) { if (auto both{common::AllPresent(AnalyzeExpr(context, *std::get<0>(x.t)), AnalyzeExpr(context, *std::get<1>(x.t)))}) { ConformabilityCheck(context.GetContextualMessages(), std::get<0>(*both), std::get<1>(*both)); return AsMaybeExpr(Relate(context.GetContextualMessages(), opr, std::move(std::get<0>(*both)), std::move(std::get<1>(*both)))); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::LT &x) { return RelationHelper(context, RelationalOperator::LT, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::LE &x) { return RelationHelper(context, RelationalOperator::LE, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::EQ &x) { return RelationHelper(context, RelationalOperator::EQ, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::NE &x) { return RelationHelper(context, RelationalOperator::NE, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::GE &x) { return RelationHelper(context, RelationalOperator::GE, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::GT &x) { return RelationHelper(context, RelationalOperator::GT, x); } // TODO: check defined operators for illegal intrinsic operator cases template MaybeExpr LogicalHelper( ExpressionAnalysisContext &context, LogicalOperator opr, const PARSED &x) { if (auto both{common::AllPresent(AnalyzeExpr(context, *std::get<0>(x.t)), AnalyzeExpr(context, *std::get<1>(x.t)))}) { return std::visit( common::visitors{ [&](Expr &&lx, Expr &&ly) -> MaybeExpr { ConformabilityCheck(context.GetContextualMessages(), lx, ly); return {AsGenericExpr( BinaryLogicalOperation(opr, std::move(lx), std::move(ly)))}; }, [&](auto &&, auto &&) -> MaybeExpr { // TODO: extension: INTEGER and typeless operands // ifort and PGI accept them if not overridden // need to define IAND, IOR, IEOR intrinsic representation context.Say( "operands to LOGICAL operation must be LOGICAL"_err_en_US); return {}; }, }, std::move(std::get<0>(*both).u), std::move(std::get<1>(*both).u)); } return std::nullopt; } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::AND &x) { return LogicalHelper(context, LogicalOperator::And, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::OR &x) { return LogicalHelper(context, LogicalOperator::Or, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::EQV &x) { return LogicalHelper(context, LogicalOperator::Eqv, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::NEQV &x) { return LogicalHelper(context, LogicalOperator::Neqv, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::XOR &x) { return LogicalHelper(context, LogicalOperator::Neqv, x); } static MaybeExpr AnalyzeExpr( ExpressionAnalysisContext &context, const parser::Expr::DefinedBinary &) { context.Say("TODO: DefinedBinary unimplemented"_err_en_US); return std::nullopt; } MaybeExpr ExpressionAnalysisContext::Analyze(const parser::Expr &expr) { if (const auto *typed{expr.typedExpr.get()}) { // Expression was already checked by AnalyzeExpressions() below. return std::make_optional>(typed->v); } else if (!expr.source.empty()) { // Analyze the expression in a specified source position context for better // error reporting. auto save{GetFoldingContext().messages().SetLocation(expr.source)}; return AnalyzeExpr(*this, expr.u); } else { return AnalyzeExpr(*this, expr.u); } } MaybeExpr ExpressionAnalysisContext::Analyze(const parser::Variable &variable) { return AnalyzeExpr(*this, variable.u); } Expr ExpressionAnalysisContext::Analyze(TypeCategory category, const std::optional &selector) { int defaultKind{GetDefaultKind(category)}; if (!selector.has_value()) { return Expr{defaultKind}; } return std::visit( common::visitors{ [&](const parser::ScalarIntConstantExpr &x) -> Expr { if (MaybeExpr kind{AnalyzeExpr(*this, x)}) { Expr folded{ Fold(GetFoldingContext(), std::move(*kind))}; if (std::optional code{ToInt64(folded)}) { if (CheckIntrinsicKind(category, *code)) { return Expr{*code}; } } else if (auto *intExpr{UnwrapExpr>(folded)}) { return ConvertToType(std::move(*intExpr)); } } return Expr{defaultKind}; }, [&](const parser::KindSelector::StarSize &x) -> Expr { std::intmax_t size = x.v; if (!CheckIntrinsicSize(category, size)) { size = defaultKind; } else if (category == TypeCategory::Complex) { size /= 2; } return Expr{size}; }, }, selector->u); } int ExpressionAnalysisContext::GetDefaultKind(common::TypeCategory category) { return context_.defaultKinds().GetDefaultKind(category); } DynamicType ExpressionAnalysisContext::GetDefaultKindOfType( common::TypeCategory category) { return {category, GetDefaultKind(category)}; } bool ExpressionAnalysisContext::CheckIntrinsicKind( TypeCategory category, std::int64_t kind) { if (IsValidKindOfIntrinsicType(category, kind)) { return true; } else { Say("%s(KIND=%jd) is not a supported type"_err_en_US, parser::ToUpperCaseLetters(EnumToString(category)).data(), kind); return false; } } bool ExpressionAnalysisContext::CheckIntrinsicSize( TypeCategory category, std::int64_t size) { if (category == TypeCategory::Complex) { // COMPLEX*16 == COMPLEX(KIND=8) if (size % 2 == 0 && IsValidKindOfIntrinsicType(category, size / 2)) { return true; } } else if (IsValidKindOfIntrinsicType(category, size)) { return true; } Say("%s*%jd is not a supported type"_err_en_US, parser::ToUpperCaseLetters(EnumToString(category)).data(), size); return false; } bool ExpressionAnalysisContext::AddAcImpliedDo( parser::CharBlock name, int kind) { return acImpliedDos_.insert(std::make_pair(name, kind)).second; } void ExpressionAnalysisContext::RemoveAcImpliedDo(parser::CharBlock name) { auto iter{acImpliedDos_.find(name)}; if (iter != acImpliedDos_.end()) { acImpliedDos_.erase(iter); } } std::optional ExpressionAnalysisContext::IsAcImpliedDo( parser::CharBlock name) const { auto iter{acImpliedDos_.find(name)}; if (iter != acImpliedDos_.cend()) { return {iter->second}; } else { return std::nullopt; } } } namespace Fortran::semantics { namespace { class Visitor { public: Visitor(SemanticsContext &context) : context_{context} {} template bool Pre(const A &) { return true /* visit children */; } template void Post(const A &) {} bool Pre(const parser::Expr &expr) { if (expr.typedExpr.get() == nullptr) { if (MaybeExpr checked{AnalyzeExpr(context_, expr)}) { #if PMKDEBUG // checked->AsFortran(std::cout << "checked expression: ") << '\n'; #endif expr.typedExpr.reset( new evaluate::GenericExprWrapper{std::move(*checked)}); } else { #if PMKDEBUG std::cout << "TODO: expression analysis failed for this expression: "; DumpTree(std::cout, expr); #endif } } return false; } private: SemanticsContext &context_; }; } void AnalyzeExpressions(parser::Program &program, SemanticsContext &context) { Visitor visitor{context}; parser::Walk(program, visitor); } evaluate::Expr AnalyzeKindSelector( SemanticsContext &context, parser::CharBlock source, common::TypeCategory category, const std::optional &selector) { evaluate::ExpressionAnalysisContext exprContext{context}; auto save{exprContext.GetContextualMessages().SetLocation(source)}; return exprContext.Analyze(category, selector); } }