…arrays When comparing dummy array extents, cope with references to symbols better (including references to other dummy arguments), and emit warnings in dubious cases that are not equivalent but not provably incompatible.
1889 lines
70 KiB
C++
1889 lines
70 KiB
C++
//===-- lib/Evaluate/tools.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/Evaluate/tools.h"
|
|
#include "flang/Common/idioms.h"
|
|
#include "flang/Evaluate/characteristics.h"
|
|
#include "flang/Evaluate/traverse.h"
|
|
#include "flang/Parser/message.h"
|
|
#include "flang/Semantics/tools.h"
|
|
#include <algorithm>
|
|
#include <variant>
|
|
|
|
using namespace Fortran::parser::literals;
|
|
|
|
namespace Fortran::evaluate {
|
|
|
|
// Can x*(a,b) be represented as (x*a,x*b)? This code duplication
|
|
// of the subexpression "x" cannot (yet?) be reliably undone by
|
|
// common subexpression elimination in lowering, so it's disabled
|
|
// here for now to avoid the risk of potential duplication of
|
|
// expensive subexpressions (e.g., large array expressions, references
|
|
// to expensive functions) in generate code.
|
|
static constexpr bool allowOperandDuplication{false};
|
|
|
|
std::optional<Expr<SomeType>> AsGenericExpr(DataRef &&ref) {
|
|
const Symbol &symbol{ref.GetLastSymbol()};
|
|
if (auto dyType{DynamicType::From(symbol)}) {
|
|
return TypedWrapper<Designator, DataRef>(*dyType, std::move(ref));
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> AsGenericExpr(const Symbol &symbol) {
|
|
return AsGenericExpr(DataRef{symbol});
|
|
}
|
|
|
|
Expr<SomeType> Parenthesize(Expr<SomeType> &&expr) {
|
|
return common::visit(
|
|
[&](auto &&x) {
|
|
using T = std::decay_t<decltype(x)>;
|
|
if constexpr (common::HasMember<T, TypelessExpression>) {
|
|
return expr; // no parentheses around typeless
|
|
} else if constexpr (std::is_same_v<T, Expr<SomeDerived>>) {
|
|
return AsGenericExpr(Parentheses<SomeDerived>{std::move(x)});
|
|
} else {
|
|
return common::visit(
|
|
[](auto &&y) {
|
|
using T = ResultType<decltype(y)>;
|
|
return AsGenericExpr(Parentheses<T>{std::move(y)});
|
|
},
|
|
std::move(x.u));
|
|
}
|
|
},
|
|
std::move(expr.u));
|
|
}
|
|
|
|
std::optional<DataRef> ExtractDataRef(
|
|
const ActualArgument &arg, bool intoSubstring, bool intoComplexPart) {
|
|
return ExtractDataRef(arg.UnwrapExpr(), intoSubstring, intoComplexPart);
|
|
}
|
|
|
|
std::optional<DataRef> ExtractSubstringBase(const Substring &substring) {
|
|
return common::visit(
|
|
common::visitors{
|
|
[&](const DataRef &x) -> std::optional<DataRef> { return x; },
|
|
[&](const StaticDataObject::Pointer &) -> std::optional<DataRef> {
|
|
return std::nullopt;
|
|
},
|
|
},
|
|
substring.parent());
|
|
}
|
|
|
|
// IsVariable()
|
|
|
|
auto IsVariableHelper::operator()(const Symbol &symbol) const -> Result {
|
|
// ASSOCIATE(x => expr) -- x counts as a variable, but undefinable
|
|
const Symbol &ultimate{symbol.GetUltimate()};
|
|
return !IsNamedConstant(ultimate) &&
|
|
(ultimate.has<semantics::ObjectEntityDetails>() ||
|
|
ultimate.has<semantics::AssocEntityDetails>());
|
|
}
|
|
auto IsVariableHelper::operator()(const Component &x) const -> Result {
|
|
const Symbol &comp{x.GetLastSymbol()};
|
|
return (*this)(comp) && (IsPointer(comp) || (*this)(x.base()));
|
|
}
|
|
auto IsVariableHelper::operator()(const ArrayRef &x) const -> Result {
|
|
return (*this)(x.base());
|
|
}
|
|
auto IsVariableHelper::operator()(const Substring &x) const -> Result {
|
|
return (*this)(x.GetBaseObject());
|
|
}
|
|
auto IsVariableHelper::operator()(const ProcedureDesignator &x) const
|
|
-> Result {
|
|
if (const Symbol * symbol{x.GetSymbol()}) {
|
|
const Symbol *result{FindFunctionResult(*symbol)};
|
|
return result && IsPointer(*result) && !IsProcedurePointer(*result);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Conversions of COMPLEX component expressions to REAL.
|
|
ConvertRealOperandsResult ConvertRealOperands(
|
|
parser::ContextualMessages &messages, Expr<SomeType> &&x,
|
|
Expr<SomeType> &&y, int defaultRealKind) {
|
|
return common::visit(
|
|
common::visitors{
|
|
[&](Expr<SomeInteger> &&ix,
|
|
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
|
|
// Can happen in a CMPLX() constructor. Per F'2018,
|
|
// both integer operands are converted to default REAL.
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(ix)),
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(iy)))};
|
|
},
|
|
[&](Expr<SomeInteger> &&ix,
|
|
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
ConvertTo(ry, std::move(ix)), std::move(ry))};
|
|
},
|
|
[&](Expr<SomeReal> &&rx,
|
|
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
std::move(rx), ConvertTo(rx, std::move(iy)))};
|
|
},
|
|
[&](Expr<SomeReal> &&rx,
|
|
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
std::move(rx), std::move(ry))};
|
|
},
|
|
[&](Expr<SomeInteger> &&ix,
|
|
BOZLiteralConstant &&by) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(ix)),
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(by)))};
|
|
},
|
|
[&](BOZLiteralConstant &&bx,
|
|
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(bx)),
|
|
ConvertToKind<TypeCategory::Real>(
|
|
defaultRealKind, std::move(iy)))};
|
|
},
|
|
[&](Expr<SomeReal> &&rx,
|
|
BOZLiteralConstant &&by) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
std::move(rx), ConvertTo(rx, std::move(by)))};
|
|
},
|
|
[&](BOZLiteralConstant &&bx,
|
|
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
|
|
return {AsSameKindExprs<TypeCategory::Real>(
|
|
ConvertTo(ry, std::move(bx)), std::move(ry))};
|
|
},
|
|
[&](auto &&, auto &&) -> ConvertRealOperandsResult { // C718
|
|
messages.Say("operands must be INTEGER or REAL"_err_en_US);
|
|
return std::nullopt;
|
|
},
|
|
},
|
|
std::move(x.u), std::move(y.u));
|
|
}
|
|
|
|
// Helpers for NumericOperation and its subroutines below.
|
|
static std::optional<Expr<SomeType>> NoExpr() { return std::nullopt; }
|
|
|
|
template <TypeCategory CAT>
|
|
std::optional<Expr<SomeType>> Package(Expr<SomeKind<CAT>> &&catExpr) {
|
|
return {AsGenericExpr(std::move(catExpr))};
|
|
}
|
|
template <TypeCategory CAT>
|
|
std::optional<Expr<SomeType>> Package(
|
|
std::optional<Expr<SomeKind<CAT>>> &&catExpr) {
|
|
if (catExpr) {
|
|
return {AsGenericExpr(std::move(*catExpr))};
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// Mixed REAL+INTEGER operations. REAL**INTEGER is a special case that
|
|
// does not require conversion of the exponent expression.
|
|
template <template <typename> class OPR>
|
|
std::optional<Expr<SomeType>> MixedRealLeft(
|
|
Expr<SomeReal> &&rx, Expr<SomeInteger> &&iy) {
|
|
return Package(common::visit(
|
|
[&](auto &&rxk) -> Expr<SomeReal> {
|
|
using resultType = ResultType<decltype(rxk)>;
|
|
if constexpr (std::is_same_v<OPR<resultType>, Power<resultType>>) {
|
|
return AsCategoryExpr(
|
|
RealToIntPower<resultType>{std::move(rxk), std::move(iy)});
|
|
}
|
|
// G++ 8.1.0 emits bogus warnings about missing return statements if
|
|
// this statement is wrapped in an "else", as it should be.
|
|
return AsCategoryExpr(OPR<resultType>{
|
|
std::move(rxk), ConvertToType<resultType>(std::move(iy))});
|
|
},
|
|
std::move(rx.u)));
|
|
}
|
|
|
|
template <int KIND>
|
|
Expr<SomeComplex> MakeComplex(Expr<Type<TypeCategory::Real, KIND>> &&re,
|
|
Expr<Type<TypeCategory::Real, KIND>> &&im) {
|
|
return AsCategoryExpr(ComplexConstructor<KIND>{std::move(re), std::move(im)});
|
|
}
|
|
|
|
std::optional<Expr<SomeComplex>> ConstructComplex(
|
|
parser::ContextualMessages &messages, Expr<SomeType> &&real,
|
|
Expr<SomeType> &&imaginary, int defaultRealKind) {
|
|
if (auto converted{ConvertRealOperands(
|
|
messages, std::move(real), std::move(imaginary), defaultRealKind)}) {
|
|
return {common::visit(
|
|
[](auto &&pair) {
|
|
return MakeComplex(std::move(pair[0]), std::move(pair[1]));
|
|
},
|
|
std::move(*converted))};
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Expr<SomeComplex>> ConstructComplex(
|
|
parser::ContextualMessages &messages, std::optional<Expr<SomeType>> &&real,
|
|
std::optional<Expr<SomeType>> &&imaginary, int defaultRealKind) {
|
|
if (auto parts{common::AllPresent(std::move(real), std::move(imaginary))}) {
|
|
return ConstructComplex(messages, std::get<0>(std::move(*parts)),
|
|
std::get<1>(std::move(*parts)), defaultRealKind);
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Extracts the real or imaginary part of the result of a COMPLEX
|
|
// expression, when that expression is simple enough to be duplicated.
|
|
template <bool GET_IMAGINARY> struct ComplexPartExtractor {
|
|
template <typename A> static std::optional<Expr<SomeReal>> Get(const A &) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Parentheses<Type<TypeCategory::Complex, KIND>> &kz) {
|
|
if (auto x{Get(kz.left())}) {
|
|
return AsGenericExpr(AsSpecificExpr(
|
|
Parentheses<Type<TypeCategory::Real, KIND>>{std::move(*x)}));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Negate<Type<TypeCategory::Complex, KIND>> &kz) {
|
|
if (auto x{Get(kz.left())}) {
|
|
return AsGenericExpr(AsSpecificExpr(
|
|
Negate<Type<TypeCategory::Real, KIND>>{std::move(*x)}));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Convert<Type<TypeCategory::Complex, KIND>, TypeCategory::Complex>
|
|
&kz) {
|
|
if (auto x{Get(kz.left())}) {
|
|
return AsGenericExpr(AsSpecificExpr(
|
|
Convert<Type<TypeCategory::Real, KIND>, TypeCategory::Real>{
|
|
AsGenericExpr(std::move(*x))}));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(const ComplexConstructor<KIND> &kz) {
|
|
return GET_IMAGINARY ? Get(kz.right()) : Get(kz.left());
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Constant<Type<TypeCategory::Complex, KIND>> &kz) {
|
|
if (auto cz{kz.GetScalarValue()}) {
|
|
return AsGenericExpr(
|
|
AsSpecificExpr(GET_IMAGINARY ? cz->AIMAG() : cz->REAL()));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Designator<Type<TypeCategory::Complex, KIND>> &kz) {
|
|
if (const auto *symbolRef{std::get_if<SymbolRef>(&kz.u)}) {
|
|
return AsGenericExpr(AsSpecificExpr(
|
|
Designator<Type<TypeCategory::Complex, KIND>>{ComplexPart{
|
|
DataRef{*symbolRef},
|
|
GET_IMAGINARY ? ComplexPart::Part::IM : ComplexPart::Part::RE}}));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
template <int KIND>
|
|
static std::optional<Expr<SomeReal>> Get(
|
|
const Expr<Type<TypeCategory::Complex, KIND>> &kz) {
|
|
return Get(kz.u);
|
|
}
|
|
|
|
static std::optional<Expr<SomeReal>> Get(const Expr<SomeComplex> &z) {
|
|
return Get(z.u);
|
|
}
|
|
};
|
|
|
|
// Convert REAL to COMPLEX of the same kind. Preserving the real operand kind
|
|
// and then applying complex operand promotion rules allows the result to have
|
|
// the highest precision of REAL and COMPLEX operands as required by Fortran
|
|
// 2018 10.9.1.3.
|
|
Expr<SomeComplex> PromoteRealToComplex(Expr<SomeReal> &&someX) {
|
|
return common::visit(
|
|
[](auto &&x) {
|
|
using RT = ResultType<decltype(x)>;
|
|
return AsCategoryExpr(ComplexConstructor<RT::kind>{
|
|
std::move(x), AsExpr(Constant<RT>{Scalar<RT>{}})});
|
|
},
|
|
std::move(someX.u));
|
|
}
|
|
|
|
// Handle mixed COMPLEX+REAL (or INTEGER) operations in a better way
|
|
// than just converting the second operand to COMPLEX and performing the
|
|
// corresponding COMPLEX+COMPLEX operation.
|
|
template <template <typename> class OPR, TypeCategory RCAT>
|
|
std::optional<Expr<SomeType>> MixedComplexLeft(
|
|
parser::ContextualMessages &messages, const Expr<SomeComplex> &zx,
|
|
const Expr<SomeKind<RCAT>> &iry, [[maybe_unused]] int defaultRealKind) {
|
|
if constexpr (RCAT == TypeCategory::Integer &&
|
|
std::is_same_v<OPR<LargestReal>, Power<LargestReal>>) {
|
|
// COMPLEX**INTEGER is a special case that doesn't convert the exponent.
|
|
return Package(common::visit(
|
|
[&](const auto &zxk) {
|
|
using Ty = ResultType<decltype(zxk)>;
|
|
return AsCategoryExpr(AsExpr(
|
|
RealToIntPower<Ty>{common::Clone(zxk), common::Clone(iry)}));
|
|
},
|
|
zx.u));
|
|
}
|
|
std::optional<Expr<SomeReal>> zr{ComplexPartExtractor<false>{}.Get(zx)};
|
|
std::optional<Expr<SomeReal>> zi{ComplexPartExtractor<true>{}.Get(zx)};
|
|
if (!zr || !zi) {
|
|
} else if constexpr (std::is_same_v<OPR<LargestReal>, Add<LargestReal>> ||
|
|
std::is_same_v<OPR<LargestReal>, Subtract<LargestReal>>) {
|
|
// (a,b) + x -> (a+x, b)
|
|
// (a,b) - x -> (a-x, b)
|
|
if (std::optional<Expr<SomeType>> rr{
|
|
NumericOperation<OPR>(messages, AsGenericExpr(std::move(*zr)),
|
|
AsGenericExpr(common::Clone(iry)), defaultRealKind)}) {
|
|
return Package(ConstructComplex(messages, std::move(*rr),
|
|
AsGenericExpr(std::move(*zi)), defaultRealKind));
|
|
}
|
|
} else if constexpr (allowOperandDuplication &&
|
|
(std::is_same_v<OPR<LargestReal>, Multiply<LargestReal>> ||
|
|
std::is_same_v<OPR<LargestReal>, Divide<LargestReal>>)) {
|
|
// (a,b) * x -> (a*x, b*x)
|
|
// (a,b) / x -> (a/x, b/x)
|
|
auto copy{iry};
|
|
auto rr{NumericOperation<OPR>(messages, AsGenericExpr(std::move(*zr)),
|
|
AsGenericExpr(common::Clone(iry)), defaultRealKind)};
|
|
auto ri{NumericOperation<OPR>(messages, AsGenericExpr(std::move(*zi)),
|
|
AsGenericExpr(std::move(copy)), defaultRealKind)};
|
|
if (auto parts{common::AllPresent(std::move(rr), std::move(ri))}) {
|
|
return Package(ConstructComplex(messages, std::get<0>(std::move(*parts)),
|
|
std::get<1>(std::move(*parts)), defaultRealKind));
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Mixed COMPLEX operations with the COMPLEX operand on the right.
|
|
// x + (a,b) -> (x+a, b)
|
|
// x - (a,b) -> (x-a, -b)
|
|
// x * (a,b) -> (x*a, x*b)
|
|
// x / (a,b) -> (x,0) / (a,b) (and **)
|
|
template <template <typename> class OPR, TypeCategory LCAT>
|
|
std::optional<Expr<SomeType>> MixedComplexRight(
|
|
parser::ContextualMessages &messages, const Expr<SomeKind<LCAT>> &irx,
|
|
const Expr<SomeComplex> &zy, [[maybe_unused]] int defaultRealKind) {
|
|
if constexpr (std::is_same_v<OPR<LargestReal>, Add<LargestReal>>) {
|
|
// x + (a,b) -> (a,b) + x -> (a+x, b)
|
|
return MixedComplexLeft<OPR, LCAT>(messages, zy, irx, defaultRealKind);
|
|
} else if constexpr (allowOperandDuplication &&
|
|
std::is_same_v<OPR<LargestReal>, Multiply<LargestReal>>) {
|
|
// x * (a,b) -> (a,b) * x -> (a*x, b*x)
|
|
return MixedComplexLeft<OPR, LCAT>(messages, zy, irx, defaultRealKind);
|
|
} else if constexpr (std::is_same_v<OPR<LargestReal>,
|
|
Subtract<LargestReal>>) {
|
|
// x - (a,b) -> (x-a, -b)
|
|
std::optional<Expr<SomeReal>> zr{ComplexPartExtractor<false>{}.Get(zy)};
|
|
std::optional<Expr<SomeReal>> zi{ComplexPartExtractor<true>{}.Get(zy)};
|
|
if (zr && zi) {
|
|
if (std::optional<Expr<SomeType>> rr{NumericOperation<Subtract>(messages,
|
|
AsGenericExpr(common::Clone(irx)), AsGenericExpr(std::move(*zr)),
|
|
defaultRealKind)}) {
|
|
return Package(ConstructComplex(messages, std::move(*rr),
|
|
AsGenericExpr(-std::move(*zi)), defaultRealKind));
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Promotes REAL(rk) and COMPLEX(zk) operands COMPLEX(max(rk,zk))
|
|
// then combine them with an operator.
|
|
template <template <typename> class OPR, TypeCategory XCAT, TypeCategory YCAT>
|
|
Expr<SomeComplex> PromoteMixedComplexReal(
|
|
Expr<SomeKind<XCAT>> &&x, Expr<SomeKind<YCAT>> &&y) {
|
|
static_assert(XCAT == TypeCategory::Complex || YCAT == TypeCategory::Complex);
|
|
static_assert(XCAT == TypeCategory::Real || YCAT == TypeCategory::Real);
|
|
return common::visit(
|
|
[&](const auto &kx, const auto &ky) {
|
|
constexpr int maxKind{std::max(
|
|
ResultType<decltype(kx)>::kind, ResultType<decltype(ky)>::kind)};
|
|
using ZTy = Type<TypeCategory::Complex, maxKind>;
|
|
return Expr<SomeComplex>{
|
|
Expr<ZTy>{OPR<ZTy>{ConvertToType<ZTy>(std::move(x)),
|
|
ConvertToType<ZTy>(std::move(y))}}};
|
|
},
|
|
x.u, y.u);
|
|
}
|
|
|
|
// N.B. When a "typeless" BOZ literal constant appears as one (not both!) of
|
|
// the operands to a dyadic operation where one is permitted, it assumes the
|
|
// type and kind of the other operand.
|
|
template <template <typename> class OPR>
|
|
std::optional<Expr<SomeType>> NumericOperation(
|
|
parser::ContextualMessages &messages, Expr<SomeType> &&x,
|
|
Expr<SomeType> &&y, int defaultRealKind) {
|
|
return common::visit(
|
|
common::visitors{
|
|
[](Expr<SomeInteger> &&ix, Expr<SomeInteger> &&iy) {
|
|
return Package(PromoteAndCombine<OPR, TypeCategory::Integer>(
|
|
std::move(ix), std::move(iy)));
|
|
},
|
|
[](Expr<SomeReal> &&rx, Expr<SomeReal> &&ry) {
|
|
return Package(PromoteAndCombine<OPR, TypeCategory::Real>(
|
|
std::move(rx), std::move(ry)));
|
|
},
|
|
// Mixed REAL/INTEGER operations
|
|
[](Expr<SomeReal> &&rx, Expr<SomeInteger> &&iy) {
|
|
return MixedRealLeft<OPR>(std::move(rx), std::move(iy));
|
|
},
|
|
[](Expr<SomeInteger> &&ix, Expr<SomeReal> &&ry) {
|
|
return Package(common::visit(
|
|
[&](auto &&ryk) -> Expr<SomeReal> {
|
|
using resultType = ResultType<decltype(ryk)>;
|
|
return AsCategoryExpr(
|
|
OPR<resultType>{ConvertToType<resultType>(std::move(ix)),
|
|
std::move(ryk)});
|
|
},
|
|
std::move(ry.u)));
|
|
},
|
|
// Homogeneous and mixed COMPLEX operations
|
|
[](Expr<SomeComplex> &&zx, Expr<SomeComplex> &&zy) {
|
|
return Package(PromoteAndCombine<OPR, TypeCategory::Complex>(
|
|
std::move(zx), std::move(zy)));
|
|
},
|
|
[&](Expr<SomeComplex> &&zx, Expr<SomeInteger> &&iy) {
|
|
if (auto result{
|
|
MixedComplexLeft<OPR>(messages, zx, iy, defaultRealKind)}) {
|
|
return result;
|
|
} else {
|
|
return Package(PromoteAndCombine<OPR, TypeCategory::Complex>(
|
|
std::move(zx), ConvertTo(zx, std::move(iy))));
|
|
}
|
|
},
|
|
[&](Expr<SomeComplex> &&zx, Expr<SomeReal> &&ry) {
|
|
if (auto result{
|
|
MixedComplexLeft<OPR>(messages, zx, ry, defaultRealKind)}) {
|
|
return result;
|
|
} else {
|
|
return Package(
|
|
PromoteMixedComplexReal<OPR>(std::move(zx), std::move(ry)));
|
|
}
|
|
},
|
|
[&](Expr<SomeInteger> &&ix, Expr<SomeComplex> &&zy) {
|
|
if (auto result{MixedComplexRight<OPR>(
|
|
messages, ix, zy, defaultRealKind)}) {
|
|
return result;
|
|
} else {
|
|
return Package(PromoteAndCombine<OPR, TypeCategory::Complex>(
|
|
ConvertTo(zy, std::move(ix)), std::move(zy)));
|
|
}
|
|
},
|
|
[&](Expr<SomeReal> &&rx, Expr<SomeComplex> &&zy) {
|
|
if (auto result{MixedComplexRight<OPR>(
|
|
messages, rx, zy, defaultRealKind)}) {
|
|
return result;
|
|
} else {
|
|
return Package(
|
|
PromoteMixedComplexReal<OPR>(std::move(rx), std::move(zy)));
|
|
}
|
|
},
|
|
// Operations with one typeless operand
|
|
[&](BOZLiteralConstant &&bx, Expr<SomeInteger> &&iy) {
|
|
return NumericOperation<OPR>(messages,
|
|
AsGenericExpr(ConvertTo(iy, std::move(bx))), std::move(y),
|
|
defaultRealKind);
|
|
},
|
|
[&](BOZLiteralConstant &&bx, Expr<SomeReal> &&ry) {
|
|
return NumericOperation<OPR>(messages,
|
|
AsGenericExpr(ConvertTo(ry, std::move(bx))), std::move(y),
|
|
defaultRealKind);
|
|
},
|
|
[&](Expr<SomeInteger> &&ix, BOZLiteralConstant &&by) {
|
|
return NumericOperation<OPR>(messages, std::move(x),
|
|
AsGenericExpr(ConvertTo(ix, std::move(by))), defaultRealKind);
|
|
},
|
|
[&](Expr<SomeReal> &&rx, BOZLiteralConstant &&by) {
|
|
return NumericOperation<OPR>(messages, std::move(x),
|
|
AsGenericExpr(ConvertTo(rx, std::move(by))), defaultRealKind);
|
|
},
|
|
// Default case
|
|
[&](auto &&, auto &&) {
|
|
messages.Say("non-numeric operands to numeric operation"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
},
|
|
std::move(x.u), std::move(y.u));
|
|
}
|
|
|
|
template std::optional<Expr<SomeType>> NumericOperation<Power>(
|
|
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
|
|
int defaultRealKind);
|
|
template std::optional<Expr<SomeType>> NumericOperation<Multiply>(
|
|
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
|
|
int defaultRealKind);
|
|
template std::optional<Expr<SomeType>> NumericOperation<Divide>(
|
|
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
|
|
int defaultRealKind);
|
|
template std::optional<Expr<SomeType>> NumericOperation<Add>(
|
|
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
|
|
int defaultRealKind);
|
|
template std::optional<Expr<SomeType>> NumericOperation<Subtract>(
|
|
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
|
|
int defaultRealKind);
|
|
|
|
std::optional<Expr<SomeType>> Negation(
|
|
parser::ContextualMessages &messages, Expr<SomeType> &&x) {
|
|
return common::visit(
|
|
common::visitors{
|
|
[&](BOZLiteralConstant &&) {
|
|
messages.Say("BOZ literal cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](NullPointer &&) {
|
|
messages.Say("NULL() cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](ProcedureDesignator &&) {
|
|
messages.Say("Subroutine cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](ProcedureRef &&) {
|
|
messages.Say("Pointer to subroutine cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](Expr<SomeInteger> &&x) { return Package(-std::move(x)); },
|
|
[&](Expr<SomeReal> &&x) { return Package(-std::move(x)); },
|
|
[&](Expr<SomeComplex> &&x) { return Package(-std::move(x)); },
|
|
[&](Expr<SomeCharacter> &&) {
|
|
messages.Say("CHARACTER cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](Expr<SomeLogical> &&) {
|
|
messages.Say("LOGICAL cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
[&](Expr<SomeDerived> &&) {
|
|
messages.Say("Operand cannot be negated"_err_en_US);
|
|
return NoExpr();
|
|
},
|
|
},
|
|
std::move(x.u));
|
|
}
|
|
|
|
Expr<SomeLogical> LogicalNegation(Expr<SomeLogical> &&x) {
|
|
return common::visit(
|
|
[](auto &&xk) { return AsCategoryExpr(LogicalNegation(std::move(xk))); },
|
|
std::move(x.u));
|
|
}
|
|
|
|
template <TypeCategory CAT>
|
|
Expr<LogicalResult> PromoteAndRelate(
|
|
RelationalOperator opr, Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
|
|
return common::visit(
|
|
[=](auto &&xy) {
|
|
return PackageRelation(opr, std::move(xy[0]), std::move(xy[1]));
|
|
},
|
|
AsSameKindExprs(std::move(x), std::move(y)));
|
|
}
|
|
|
|
std::optional<Expr<LogicalResult>> Relate(parser::ContextualMessages &messages,
|
|
RelationalOperator opr, Expr<SomeType> &&x, Expr<SomeType> &&y) {
|
|
return common::visit(
|
|
common::visitors{
|
|
[=](Expr<SomeInteger> &&ix,
|
|
Expr<SomeInteger> &&iy) -> std::optional<Expr<LogicalResult>> {
|
|
return PromoteAndRelate(opr, std::move(ix), std::move(iy));
|
|
},
|
|
[=](Expr<SomeReal> &&rx,
|
|
Expr<SomeReal> &&ry) -> std::optional<Expr<LogicalResult>> {
|
|
return PromoteAndRelate(opr, std::move(rx), std::move(ry));
|
|
},
|
|
[&](Expr<SomeReal> &&rx, Expr<SomeInteger> &&iy) {
|
|
return Relate(messages, opr, std::move(x),
|
|
AsGenericExpr(ConvertTo(rx, std::move(iy))));
|
|
},
|
|
[&](Expr<SomeInteger> &&ix, Expr<SomeReal> &&ry) {
|
|
return Relate(messages, opr,
|
|
AsGenericExpr(ConvertTo(ry, std::move(ix))), std::move(y));
|
|
},
|
|
[&](Expr<SomeComplex> &&zx,
|
|
Expr<SomeComplex> &&zy) -> std::optional<Expr<LogicalResult>> {
|
|
if (opr == RelationalOperator::EQ ||
|
|
opr == RelationalOperator::NE) {
|
|
return PromoteAndRelate(opr, std::move(zx), std::move(zy));
|
|
} else {
|
|
messages.Say(
|
|
"COMPLEX data may be compared only for equality"_err_en_US);
|
|
return std::nullopt;
|
|
}
|
|
},
|
|
[&](Expr<SomeComplex> &&zx, Expr<SomeInteger> &&iy) {
|
|
return Relate(messages, opr, std::move(x),
|
|
AsGenericExpr(ConvertTo(zx, std::move(iy))));
|
|
},
|
|
[&](Expr<SomeComplex> &&zx, Expr<SomeReal> &&ry) {
|
|
return Relate(messages, opr, std::move(x),
|
|
AsGenericExpr(ConvertTo(zx, std::move(ry))));
|
|
},
|
|
[&](Expr<SomeInteger> &&ix, Expr<SomeComplex> &&zy) {
|
|
return Relate(messages, opr,
|
|
AsGenericExpr(ConvertTo(zy, std::move(ix))), std::move(y));
|
|
},
|
|
[&](Expr<SomeReal> &&rx, Expr<SomeComplex> &&zy) {
|
|
return Relate(messages, opr,
|
|
AsGenericExpr(ConvertTo(zy, std::move(rx))), std::move(y));
|
|
},
|
|
[&](Expr<SomeCharacter> &&cx, Expr<SomeCharacter> &&cy) {
|
|
return common::visit(
|
|
[&](auto &&cxk,
|
|
auto &&cyk) -> std::optional<Expr<LogicalResult>> {
|
|
using Ty = ResultType<decltype(cxk)>;
|
|
if constexpr (std::is_same_v<Ty, ResultType<decltype(cyk)>>) {
|
|
return PackageRelation(opr, std::move(cxk), std::move(cyk));
|
|
} else {
|
|
messages.Say(
|
|
"CHARACTER operands do not have same KIND"_err_en_US);
|
|
return std::nullopt;
|
|
}
|
|
},
|
|
std::move(cx.u), std::move(cy.u));
|
|
},
|
|
// Default case
|
|
[&](auto &&, auto &&) {
|
|
DIE("invalid types for relational operator");
|
|
return std::optional<Expr<LogicalResult>>{};
|
|
},
|
|
},
|
|
std::move(x.u), std::move(y.u));
|
|
}
|
|
|
|
Expr<SomeLogical> BinaryLogicalOperation(
|
|
LogicalOperator opr, Expr<SomeLogical> &&x, Expr<SomeLogical> &&y) {
|
|
CHECK(opr != LogicalOperator::Not);
|
|
return common::visit(
|
|
[=](auto &&xy) {
|
|
using Ty = ResultType<decltype(xy[0])>;
|
|
return Expr<SomeLogical>{BinaryLogicalOperation<Ty::kind>(
|
|
opr, std::move(xy[0]), std::move(xy[1]))};
|
|
},
|
|
AsSameKindExprs(std::move(x), std::move(y)));
|
|
}
|
|
|
|
template <TypeCategory TO>
|
|
std::optional<Expr<SomeType>> ConvertToNumeric(int kind, Expr<SomeType> &&x) {
|
|
static_assert(common::IsNumericTypeCategory(TO));
|
|
return common::visit(
|
|
[=](auto &&cx) -> std::optional<Expr<SomeType>> {
|
|
using cxType = std::decay_t<decltype(cx)>;
|
|
if constexpr (!common::HasMember<cxType, TypelessExpression>) {
|
|
if constexpr (IsNumericTypeCategory(ResultType<cxType>::category)) {
|
|
return Expr<SomeType>{ConvertToKind<TO>(kind, std::move(cx))};
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
},
|
|
std::move(x.u));
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> ConvertToType(
|
|
const DynamicType &type, Expr<SomeType> &&x) {
|
|
if (type.IsTypelessIntrinsicArgument()) {
|
|
return std::nullopt;
|
|
}
|
|
switch (type.category()) {
|
|
case TypeCategory::Integer:
|
|
if (auto *boz{std::get_if<BOZLiteralConstant>(&x.u)}) {
|
|
// Extension to C7109: allow BOZ literals to appear in integer contexts
|
|
// when the type is unambiguous.
|
|
return Expr<SomeType>{
|
|
ConvertToKind<TypeCategory::Integer>(type.kind(), std::move(*boz))};
|
|
}
|
|
return ConvertToNumeric<TypeCategory::Integer>(type.kind(), std::move(x));
|
|
case TypeCategory::Real:
|
|
if (auto *boz{std::get_if<BOZLiteralConstant>(&x.u)}) {
|
|
return Expr<SomeType>{
|
|
ConvertToKind<TypeCategory::Real>(type.kind(), std::move(*boz))};
|
|
}
|
|
return ConvertToNumeric<TypeCategory::Real>(type.kind(), std::move(x));
|
|
case TypeCategory::Complex:
|
|
return ConvertToNumeric<TypeCategory::Complex>(type.kind(), std::move(x));
|
|
case TypeCategory::Character:
|
|
if (auto *cx{UnwrapExpr<Expr<SomeCharacter>>(x)}) {
|
|
auto converted{
|
|
ConvertToKind<TypeCategory::Character>(type.kind(), std::move(*cx))};
|
|
if (auto length{type.GetCharLength()}) {
|
|
converted = common::visit(
|
|
[&](auto &&x) {
|
|
using CharacterType = ResultType<decltype(x)>;
|
|
return Expr<SomeCharacter>{
|
|
Expr<CharacterType>{SetLength<CharacterType::kind>{
|
|
std::move(x), std::move(*length)}}};
|
|
},
|
|
std::move(converted.u));
|
|
}
|
|
return Expr<SomeType>{std::move(converted)};
|
|
}
|
|
break;
|
|
case TypeCategory::Logical:
|
|
if (auto *cx{UnwrapExpr<Expr<SomeLogical>>(x)}) {
|
|
return Expr<SomeType>{
|
|
ConvertToKind<TypeCategory::Logical>(type.kind(), std::move(*cx))};
|
|
}
|
|
break;
|
|
case TypeCategory::Derived:
|
|
if (auto fromType{x.GetType()}) {
|
|
if (type.IsTkCompatibleWith(*fromType)) {
|
|
// "x" could be assigned or passed to "type", or appear in a
|
|
// structure constructor as a value for a component with "type"
|
|
return std::move(x);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> ConvertToType(
|
|
const DynamicType &to, std::optional<Expr<SomeType>> &&x) {
|
|
if (x) {
|
|
return ConvertToType(to, std::move(*x));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> ConvertToType(
|
|
const Symbol &symbol, Expr<SomeType> &&x) {
|
|
if (auto symType{DynamicType::From(symbol)}) {
|
|
return ConvertToType(*symType, std::move(x));
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> ConvertToType(
|
|
const Symbol &to, std::optional<Expr<SomeType>> &&x) {
|
|
if (x) {
|
|
return ConvertToType(to, std::move(*x));
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
bool IsAssumedRank(const Symbol &original) {
|
|
if (const auto *assoc{original.detailsIf<semantics::AssocEntityDetails>()}) {
|
|
if (assoc->rank()) {
|
|
return false; // in RANK(n) or RANK(*)
|
|
} else if (assoc->IsAssumedRank()) {
|
|
return true; // RANK DEFAULT
|
|
}
|
|
}
|
|
const Symbol &symbol{semantics::ResolveAssociations(original)};
|
|
const auto *object{symbol.detailsIf<semantics::ObjectEntityDetails>()};
|
|
return object && object->IsAssumedRank();
|
|
}
|
|
|
|
bool IsAssumedRank(const ActualArgument &arg) {
|
|
if (const auto *expr{arg.UnwrapExpr()}) {
|
|
return IsAssumedRank(*expr);
|
|
} else {
|
|
const Symbol *assumedTypeDummy{arg.GetAssumedTypeDummy()};
|
|
CHECK(assumedTypeDummy);
|
|
return IsAssumedRank(*assumedTypeDummy);
|
|
}
|
|
}
|
|
|
|
bool IsCoarray(const ActualArgument &arg) {
|
|
const auto *expr{arg.UnwrapExpr()};
|
|
return expr && IsCoarray(*expr);
|
|
}
|
|
|
|
bool IsCoarray(const Symbol &symbol) {
|
|
return GetAssociationRoot(symbol).Corank() > 0;
|
|
}
|
|
|
|
bool IsProcedure(const Expr<SomeType> &expr) {
|
|
return std::holds_alternative<ProcedureDesignator>(expr.u);
|
|
}
|
|
bool IsFunction(const Expr<SomeType> &expr) {
|
|
const auto *designator{std::get_if<ProcedureDesignator>(&expr.u)};
|
|
return designator && designator->GetType().has_value();
|
|
}
|
|
|
|
bool IsPointer(const Expr<SomeType> &expr) {
|
|
return IsObjectPointer(expr) || IsProcedurePointer(expr);
|
|
}
|
|
|
|
bool IsProcedurePointer(const Expr<SomeType> &expr) {
|
|
if (IsNullProcedurePointer(expr)) {
|
|
return true;
|
|
} else if (const auto *funcRef{UnwrapProcedureRef(expr)}) {
|
|
if (const Symbol * proc{funcRef->proc().GetSymbol()}) {
|
|
const Symbol *result{FindFunctionResult(*proc)};
|
|
return result && IsProcedurePointer(*result);
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
|
|
return IsProcedurePointer(proc->GetSymbol());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsProcedurePointerTarget(const Expr<SomeType> &expr) {
|
|
return common::visit(common::visitors{
|
|
[](const NullPointer &) { return true; },
|
|
[](const ProcedureDesignator &) { return true; },
|
|
[](const ProcedureRef &) { return true; },
|
|
[&](const auto &) {
|
|
const Symbol *last{GetLastSymbol(expr)};
|
|
return last && IsProcedurePointer(*last);
|
|
},
|
|
},
|
|
expr.u);
|
|
}
|
|
|
|
bool IsObjectPointer(const Expr<SomeType> &expr) {
|
|
if (IsNullObjectPointer(expr)) {
|
|
return true;
|
|
} else if (IsProcedurePointerTarget(expr)) {
|
|
return false;
|
|
} else if (const auto *funcRef{UnwrapProcedureRef(expr)}) {
|
|
return IsVariable(*funcRef);
|
|
} else if (const Symbol * symbol{UnwrapWholeSymbolOrComponentDataRef(expr)}) {
|
|
return IsPointer(symbol->GetUltimate());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// IsNullPointer() & variations
|
|
|
|
template <bool IS_PROC_PTR> struct IsNullPointerHelper {
|
|
template <typename A> bool operator()(const A &) const { return false; }
|
|
bool operator()(const ProcedureRef &call) const {
|
|
if constexpr (IS_PROC_PTR) {
|
|
const auto *intrinsic{call.proc().GetSpecificIntrinsic()};
|
|
return intrinsic &&
|
|
intrinsic->characteristics.value().attrs.test(
|
|
characteristics::Procedure::Attr::NullPointer);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
template <typename T> bool operator()(const FunctionRef<T> &call) const {
|
|
if constexpr (IS_PROC_PTR) {
|
|
return false;
|
|
} else {
|
|
const auto *intrinsic{call.proc().GetSpecificIntrinsic()};
|
|
return intrinsic &&
|
|
intrinsic->characteristics.value().attrs.test(
|
|
characteristics::Procedure::Attr::NullPointer);
|
|
}
|
|
}
|
|
template <typename T> bool operator()(const Designator<T> &x) const {
|
|
if (const auto *component{std::get_if<Component>(&x.u)}) {
|
|
if (const auto *baseSym{std::get_if<SymbolRef>(&component->base().u)}) {
|
|
const Symbol &base{**baseSym};
|
|
if (const auto *object{
|
|
base.detailsIf<semantics::ObjectEntityDetails>()}) {
|
|
// TODO: nested component and array references
|
|
if (IsNamedConstant(base) && object->init()) {
|
|
if (auto structCons{
|
|
GetScalarConstantValue<SomeDerived>(*object->init())}) {
|
|
auto iter{structCons->values().find(component->GetLastSymbol())};
|
|
if (iter != structCons->values().end()) {
|
|
return (*this)(iter->second.value());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool operator()(const NullPointer &) const { return true; }
|
|
template <typename T> bool operator()(const Parentheses<T> &x) const {
|
|
return (*this)(x.left());
|
|
}
|
|
template <typename T> bool operator()(const Expr<T> &x) const {
|
|
return common::visit(*this, x.u);
|
|
}
|
|
};
|
|
|
|
bool IsNullObjectPointer(const Expr<SomeType> &expr) {
|
|
return IsNullPointerHelper<false>{}(expr);
|
|
}
|
|
|
|
bool IsNullProcedurePointer(const Expr<SomeType> &expr) {
|
|
return IsNullPointerHelper<true>{}(expr);
|
|
}
|
|
|
|
bool IsNullPointer(const Expr<SomeType> &expr) {
|
|
return IsNullObjectPointer(expr) || IsNullProcedurePointer(expr);
|
|
}
|
|
|
|
bool IsBareNullPointer(const Expr<SomeType> *expr) {
|
|
return expr && std::holds_alternative<NullPointer>(expr->u);
|
|
}
|
|
|
|
// GetSymbolVector()
|
|
auto GetSymbolVectorHelper::operator()(const Symbol &x) const -> Result {
|
|
if (const auto *details{x.detailsIf<semantics::AssocEntityDetails>()}) {
|
|
if (IsVariable(details->expr()) && !UnwrapProcedureRef(*details->expr())) {
|
|
// associate(x => variable that is not a pointer returned by a function)
|
|
return (*this)(details->expr());
|
|
}
|
|
}
|
|
return {x.GetUltimate()};
|
|
}
|
|
auto GetSymbolVectorHelper::operator()(const Component &x) const -> Result {
|
|
Result result{(*this)(x.base())};
|
|
result.emplace_back(x.GetLastSymbol());
|
|
return result;
|
|
}
|
|
auto GetSymbolVectorHelper::operator()(const ArrayRef &x) const -> Result {
|
|
return GetSymbolVector(x.base());
|
|
}
|
|
auto GetSymbolVectorHelper::operator()(const CoarrayRef &x) const -> Result {
|
|
return x.base();
|
|
}
|
|
|
|
const Symbol *GetLastTarget(const SymbolVector &symbols) {
|
|
auto end{std::crend(symbols)};
|
|
// N.B. Neither clang nor g++ recognizes "symbols.crbegin()" here.
|
|
auto iter{std::find_if(std::crbegin(symbols), end, [](const Symbol &x) {
|
|
return x.attrs().HasAny(
|
|
{semantics::Attr::POINTER, semantics::Attr::TARGET});
|
|
})};
|
|
return iter == end ? nullptr : &**iter;
|
|
}
|
|
|
|
struct CollectSymbolsHelper
|
|
: public SetTraverse<CollectSymbolsHelper, semantics::UnorderedSymbolSet> {
|
|
using Base = SetTraverse<CollectSymbolsHelper, semantics::UnorderedSymbolSet>;
|
|
CollectSymbolsHelper() : Base{*this} {}
|
|
using Base::operator();
|
|
semantics::UnorderedSymbolSet operator()(const Symbol &symbol) const {
|
|
return {symbol};
|
|
}
|
|
};
|
|
template <typename A> semantics::UnorderedSymbolSet CollectSymbols(const A &x) {
|
|
return CollectSymbolsHelper{}(x);
|
|
}
|
|
template semantics::UnorderedSymbolSet CollectSymbols(const Expr<SomeType> &);
|
|
template semantics::UnorderedSymbolSet CollectSymbols(
|
|
const Expr<SomeInteger> &);
|
|
template semantics::UnorderedSymbolSet CollectSymbols(
|
|
const Expr<SubscriptInteger> &);
|
|
|
|
// HasVectorSubscript()
|
|
struct HasVectorSubscriptHelper : public AnyTraverse<HasVectorSubscriptHelper> {
|
|
using Base = AnyTraverse<HasVectorSubscriptHelper>;
|
|
HasVectorSubscriptHelper() : Base{*this} {}
|
|
using Base::operator();
|
|
bool operator()(const Subscript &ss) const {
|
|
return !std::holds_alternative<Triplet>(ss.u) && ss.Rank() > 0;
|
|
}
|
|
bool operator()(const ProcedureRef &) const {
|
|
return false; // don't descend into function call arguments
|
|
}
|
|
};
|
|
|
|
bool HasVectorSubscript(const Expr<SomeType> &expr) {
|
|
return HasVectorSubscriptHelper{}(expr);
|
|
}
|
|
|
|
parser::Message *AttachDeclaration(
|
|
parser::Message &message, const Symbol &symbol) {
|
|
const Symbol *unhosted{&symbol};
|
|
while (
|
|
const auto *assoc{unhosted->detailsIf<semantics::HostAssocDetails>()}) {
|
|
unhosted = &assoc->symbol();
|
|
}
|
|
if (const auto *binding{
|
|
unhosted->detailsIf<semantics::ProcBindingDetails>()}) {
|
|
if (binding->symbol().name() != symbol.name()) {
|
|
message.Attach(binding->symbol().name(),
|
|
"Procedure '%s' of type '%s' is bound to '%s'"_en_US, symbol.name(),
|
|
symbol.owner().GetName().value(), binding->symbol().name());
|
|
return &message;
|
|
}
|
|
unhosted = &binding->symbol();
|
|
}
|
|
if (const auto *use{symbol.detailsIf<semantics::UseDetails>()}) {
|
|
message.Attach(use->location(),
|
|
"'%s' is USE-associated with '%s' in module '%s'"_en_US, symbol.name(),
|
|
unhosted->name(), GetUsedModule(*use).name());
|
|
} else {
|
|
message.Attach(
|
|
unhosted->name(), "Declaration of '%s'"_en_US, unhosted->name());
|
|
}
|
|
return &message;
|
|
}
|
|
|
|
parser::Message *AttachDeclaration(
|
|
parser::Message *message, const Symbol &symbol) {
|
|
return message ? AttachDeclaration(*message, symbol) : nullptr;
|
|
}
|
|
|
|
class FindImpureCallHelper
|
|
: public AnyTraverse<FindImpureCallHelper, std::optional<std::string>> {
|
|
using Result = std::optional<std::string>;
|
|
using Base = AnyTraverse<FindImpureCallHelper, Result>;
|
|
|
|
public:
|
|
explicit FindImpureCallHelper(FoldingContext &c) : Base{*this}, context_{c} {}
|
|
using Base::operator();
|
|
Result operator()(const ProcedureRef &call) const {
|
|
if (auto chars{
|
|
characteristics::Procedure::Characterize(call.proc(), context_)}) {
|
|
if (chars->attrs.test(characteristics::Procedure::Attr::Pure)) {
|
|
return (*this)(call.arguments());
|
|
}
|
|
}
|
|
return call.proc().GetName();
|
|
}
|
|
|
|
private:
|
|
FoldingContext &context_;
|
|
};
|
|
|
|
std::optional<std::string> FindImpureCall(
|
|
FoldingContext &context, const Expr<SomeType> &expr) {
|
|
return FindImpureCallHelper{context}(expr);
|
|
}
|
|
std::optional<std::string> FindImpureCall(
|
|
FoldingContext &context, const ProcedureRef &proc) {
|
|
return FindImpureCallHelper{context}(proc);
|
|
}
|
|
|
|
// Common handling for procedure pointer compatibility of left- and right-hand
|
|
// sides. Returns nullopt if they're compatible. Otherwise, it returns a
|
|
// message that needs to be augmented by the names of the left and right sides
|
|
// and the content of the "whyNotCompatible" string.
|
|
std::optional<parser::MessageFixedText> CheckProcCompatibility(bool isCall,
|
|
const std::optional<characteristics::Procedure> &lhsProcedure,
|
|
const characteristics::Procedure *rhsProcedure,
|
|
const SpecificIntrinsic *specificIntrinsic, std::string &whyNotCompatible,
|
|
std::optional<std::string> &warning) {
|
|
std::optional<parser::MessageFixedText> msg;
|
|
if (!lhsProcedure) {
|
|
msg = "In assignment to object %s, the target '%s' is a procedure"
|
|
" designator"_err_en_US;
|
|
} else if (!rhsProcedure) {
|
|
msg = "In assignment to procedure %s, the characteristics of the target"
|
|
" procedure '%s' could not be determined"_err_en_US;
|
|
} else if (!isCall && lhsProcedure->functionResult &&
|
|
rhsProcedure->functionResult &&
|
|
!lhsProcedure->functionResult->IsCompatibleWith(
|
|
*rhsProcedure->functionResult, &whyNotCompatible)) {
|
|
msg =
|
|
"Function %s associated with incompatible function designator '%s': %s"_err_en_US;
|
|
} else if (lhsProcedure->IsCompatibleWith(*rhsProcedure, &whyNotCompatible,
|
|
specificIntrinsic, &warning)) {
|
|
// OK
|
|
} else if (isCall) {
|
|
msg = "Procedure %s associated with result of reference to function '%s'"
|
|
" that is an incompatible procedure pointer: %s"_err_en_US;
|
|
} else if (lhsProcedure->IsPure() && !rhsProcedure->IsPure()) {
|
|
msg = "PURE procedure %s may not be associated with non-PURE"
|
|
" procedure designator '%s'"_err_en_US;
|
|
} else if (lhsProcedure->IsFunction() && rhsProcedure->IsSubroutine()) {
|
|
msg = "Function %s may not be associated with subroutine"
|
|
" designator '%s'"_err_en_US;
|
|
} else if (lhsProcedure->IsSubroutine() && rhsProcedure->IsFunction()) {
|
|
msg = "Subroutine %s may not be associated with function"
|
|
" designator '%s'"_err_en_US;
|
|
} else if (lhsProcedure->HasExplicitInterface() &&
|
|
!rhsProcedure->HasExplicitInterface()) {
|
|
// Section 10.2.2.4, paragraph 3 prohibits associating a procedure pointer
|
|
// that has an explicit interface with a procedure whose characteristics
|
|
// don't match. That's the case if the target procedure has an implicit
|
|
// interface. But this case is allowed by several other compilers as long
|
|
// as the explicit interface can be called via an implicit interface.
|
|
if (!lhsProcedure->CanBeCalledViaImplicitInterface()) {
|
|
msg = "Procedure %s with explicit interface that cannot be called via "
|
|
"an implicit interface cannot be associated with procedure "
|
|
"designator with an implicit interface"_err_en_US;
|
|
}
|
|
} else if (!lhsProcedure->HasExplicitInterface() &&
|
|
rhsProcedure->HasExplicitInterface()) {
|
|
// OK if the target can be called via an implicit interface
|
|
if (!rhsProcedure->CanBeCalledViaImplicitInterface() &&
|
|
!specificIntrinsic) {
|
|
msg = "Procedure %s with implicit interface may not be associated "
|
|
"with procedure designator '%s' with explicit interface that "
|
|
"cannot be called via an implicit interface"_err_en_US;
|
|
}
|
|
} else {
|
|
msg = "Procedure %s associated with incompatible procedure"
|
|
" designator '%s': %s"_err_en_US;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
// GetLastPointerSymbol()
|
|
static const Symbol *GetLastPointerSymbol(const Symbol &symbol) {
|
|
return IsPointer(GetAssociationRoot(symbol)) ? &symbol : nullptr;
|
|
}
|
|
static const Symbol *GetLastPointerSymbol(const SymbolRef &symbol) {
|
|
return GetLastPointerSymbol(*symbol);
|
|
}
|
|
static const Symbol *GetLastPointerSymbol(const Component &x) {
|
|
const Symbol &c{x.GetLastSymbol()};
|
|
return IsPointer(c) ? &c : GetLastPointerSymbol(x.base());
|
|
}
|
|
static const Symbol *GetLastPointerSymbol(const NamedEntity &x) {
|
|
const auto *c{x.UnwrapComponent()};
|
|
return c ? GetLastPointerSymbol(*c) : GetLastPointerSymbol(x.GetLastSymbol());
|
|
}
|
|
static const Symbol *GetLastPointerSymbol(const ArrayRef &x) {
|
|
return GetLastPointerSymbol(x.base());
|
|
}
|
|
static const Symbol *GetLastPointerSymbol(const CoarrayRef &x) {
|
|
return nullptr;
|
|
}
|
|
const Symbol *GetLastPointerSymbol(const DataRef &x) {
|
|
return common::visit(
|
|
[](const auto &y) { return GetLastPointerSymbol(y); }, x.u);
|
|
}
|
|
|
|
template <TypeCategory TO, TypeCategory FROM>
|
|
static std::optional<Expr<SomeType>> DataConstantConversionHelper(
|
|
FoldingContext &context, const DynamicType &toType,
|
|
const Expr<SomeType> &expr) {
|
|
DynamicType sizedType{FROM, toType.kind()};
|
|
if (auto sized{
|
|
Fold(context, ConvertToType(sizedType, Expr<SomeType>{expr}))}) {
|
|
if (const auto *someExpr{UnwrapExpr<Expr<SomeKind<FROM>>>(*sized)}) {
|
|
return common::visit(
|
|
[](const auto &w) -> std::optional<Expr<SomeType>> {
|
|
using FromType = ResultType<decltype(w)>;
|
|
static constexpr int kind{FromType::kind};
|
|
if constexpr (IsValidKindOfIntrinsicType(TO, kind)) {
|
|
if (const auto *fromConst{UnwrapExpr<Constant<FromType>>(w)}) {
|
|
using FromWordType = typename FromType::Scalar;
|
|
using LogicalType = value::Logical<FromWordType::bits>;
|
|
using ElementType =
|
|
std::conditional_t<TO == TypeCategory::Logical, LogicalType,
|
|
typename LogicalType::Word>;
|
|
std::vector<ElementType> values;
|
|
auto at{fromConst->lbounds()};
|
|
auto shape{fromConst->shape()};
|
|
for (auto n{GetSize(shape)}; n-- > 0;
|
|
fromConst->IncrementSubscripts(at)) {
|
|
auto elt{fromConst->At(at)};
|
|
if constexpr (TO == TypeCategory::Logical) {
|
|
values.emplace_back(std::move(elt));
|
|
} else {
|
|
values.emplace_back(elt.word());
|
|
}
|
|
}
|
|
return {AsGenericExpr(AsExpr(Constant<Type<TO, kind>>{
|
|
std::move(values), std::move(shape)}))};
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
},
|
|
someExpr->u);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> DataConstantConversionExtension(
|
|
FoldingContext &context, const DynamicType &toType,
|
|
const Expr<SomeType> &expr0) {
|
|
Expr<SomeType> expr{Fold(context, Expr<SomeType>{expr0})};
|
|
if (!IsActuallyConstant(expr)) {
|
|
return std::nullopt;
|
|
}
|
|
if (auto fromType{expr.GetType()}) {
|
|
if (toType.category() == TypeCategory::Logical &&
|
|
fromType->category() == TypeCategory::Integer) {
|
|
return DataConstantConversionHelper<TypeCategory::Logical,
|
|
TypeCategory::Integer>(context, toType, expr);
|
|
}
|
|
if (toType.category() == TypeCategory::Integer &&
|
|
fromType->category() == TypeCategory::Logical) {
|
|
return DataConstantConversionHelper<TypeCategory::Integer,
|
|
TypeCategory::Logical>(context, toType, expr);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool IsAllocatableOrPointerObject(const Expr<SomeType> &expr) {
|
|
const semantics::Symbol *sym{UnwrapWholeSymbolOrComponentDataRef(expr)};
|
|
return (sym &&
|
|
semantics::IsAllocatableOrObjectPointer(&sym->GetUltimate())) ||
|
|
evaluate::IsObjectPointer(expr);
|
|
}
|
|
|
|
bool IsAllocatableDesignator(const Expr<SomeType> &expr) {
|
|
// Allocatable sub-objects are not themselves allocatable (9.5.3.1 NOTE 2).
|
|
if (const semantics::Symbol *
|
|
sym{UnwrapWholeSymbolOrComponentOrCoarrayRef(expr)}) {
|
|
return semantics::IsAllocatable(sym->GetUltimate());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MayBePassedAsAbsentOptional(const Expr<SomeType> &expr) {
|
|
const semantics::Symbol *sym{UnwrapWholeSymbolOrComponentDataRef(expr)};
|
|
// 15.5.2.12 1. is pretty clear that an unallocated allocatable/pointer actual
|
|
// may be passed to a non-allocatable/non-pointer optional dummy. Note that
|
|
// other compilers (like nag, nvfortran, ifort, gfortran and xlf) seems to
|
|
// ignore this point in intrinsic contexts (e.g CMPLX argument).
|
|
return (sym && semantics::IsOptional(*sym)) ||
|
|
IsAllocatableOrPointerObject(expr);
|
|
}
|
|
|
|
std::optional<Expr<SomeType>> HollerithToBOZ(FoldingContext &context,
|
|
const Expr<SomeType> &expr, const DynamicType &type) {
|
|
if (std::optional<std::string> chValue{GetScalarConstantValue<Ascii>(expr)}) {
|
|
// Pad on the right with spaces when short, truncate the right if long.
|
|
// TODO: big-endian targets
|
|
auto bytes{static_cast<std::size_t>(
|
|
ToInt64(type.MeasureSizeInBytes(context, false)).value())};
|
|
BOZLiteralConstant bits{0};
|
|
for (std::size_t j{0}; j < bytes; ++j) {
|
|
char ch{j >= chValue->size() ? ' ' : chValue->at(j)};
|
|
BOZLiteralConstant chBOZ{static_cast<unsigned char>(ch)};
|
|
bits = bits.IOR(chBOZ.SHIFTL(8 * j));
|
|
}
|
|
return ConvertToType(type, Expr<SomeType>{bits});
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// Extracts a whole symbol being used as a bound of a dummy argument,
|
|
// possibly wrapped with parentheses or MAX(0, ...).
|
|
template <int KIND>
|
|
static const Symbol *GetBoundSymbol(
|
|
const Expr<Type<TypeCategory::Integer, KIND>> &expr) {
|
|
using T = Type<TypeCategory::Integer, KIND>;
|
|
return common::visit(
|
|
common::visitors{
|
|
[](const Extremum<T> &max) -> const Symbol * {
|
|
if (max.ordering == Ordering::Greater) {
|
|
if (auto zero{ToInt64(max.left())}; zero && *zero == 0) {
|
|
return GetBoundSymbol(max.right());
|
|
}
|
|
}
|
|
return nullptr;
|
|
},
|
|
[](const Parentheses<T> &x) { return GetBoundSymbol(x.left()); },
|
|
[](const Designator<T> &x) -> const Symbol * {
|
|
if (const auto *ref{std::get_if<SymbolRef>(&x.u)}) {
|
|
return &**ref;
|
|
}
|
|
return nullptr;
|
|
},
|
|
[](const Convert<T, TypeCategory::Integer> &x) {
|
|
return common::visit(
|
|
[](const auto &y) -> const Symbol * {
|
|
using yType = std::decay_t<decltype(y)>;
|
|
using yResult = typename yType::Result;
|
|
if constexpr (yResult::kind <= KIND) {
|
|
return GetBoundSymbol(y);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
},
|
|
x.left().u);
|
|
},
|
|
[](const auto &) -> const Symbol * { return nullptr; },
|
|
},
|
|
expr.u);
|
|
}
|
|
|
|
std::optional<bool> AreEquivalentInInterface(
|
|
const Expr<SubscriptInteger> &x, const Expr<SubscriptInteger> &y) {
|
|
auto xVal{ToInt64(x)};
|
|
auto yVal{ToInt64(y)};
|
|
if (xVal && yVal) {
|
|
return *xVal == *yVal;
|
|
} else if (xVal || yVal) {
|
|
return false;
|
|
}
|
|
const Symbol *xSym{GetBoundSymbol(x)};
|
|
const Symbol *ySym{GetBoundSymbol(y)};
|
|
if (xSym && ySym) {
|
|
if (&xSym->GetUltimate() == &ySym->GetUltimate()) {
|
|
return true; // USE/host associated same symbol
|
|
}
|
|
auto xNum{semantics::GetDummyArgumentNumber(xSym)};
|
|
auto yNum{semantics::GetDummyArgumentNumber(ySym)};
|
|
if (xNum && yNum) {
|
|
if (*xNum == *yNum) {
|
|
auto xType{DynamicType::From(*xSym)};
|
|
auto yType{DynamicType::From(*ySym)};
|
|
return xType && yType && xType->IsEquivalentTo(*yType);
|
|
}
|
|
}
|
|
return false;
|
|
} else if (xSym || ySym) {
|
|
return false;
|
|
}
|
|
// Neither expression is an integer constant or a whole symbol.
|
|
if (x == y) {
|
|
return true;
|
|
} else {
|
|
return std::nullopt; // not sure
|
|
}
|
|
}
|
|
|
|
} // namespace Fortran::evaluate
|
|
|
|
namespace Fortran::semantics {
|
|
|
|
const Symbol &ResolveAssociations(const Symbol &original) {
|
|
const Symbol &symbol{original.GetUltimate()};
|
|
if (const auto *details{symbol.detailsIf<AssocEntityDetails>()}) {
|
|
if (!details->rank()) { // Not RANK(n) or RANK(*)
|
|
if (const Symbol * nested{UnwrapWholeSymbolDataRef(details->expr())}) {
|
|
return ResolveAssociations(*nested);
|
|
}
|
|
}
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
// When a construct association maps to a variable, and that variable
|
|
// is not an array with a vector-valued subscript, return the base
|
|
// Symbol of that variable, else nullptr. Descends into other construct
|
|
// associations when one associations maps to another.
|
|
static const Symbol *GetAssociatedVariable(const AssocEntityDetails &details) {
|
|
if (const auto &expr{details.expr()}) {
|
|
if (IsVariable(*expr) && !HasVectorSubscript(*expr)) {
|
|
if (const Symbol * varSymbol{GetFirstSymbol(*expr)}) {
|
|
return &GetAssociationRoot(*varSymbol);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const Symbol &GetAssociationRoot(const Symbol &original) {
|
|
const Symbol &symbol{ResolveAssociations(original)};
|
|
if (const auto *details{symbol.detailsIf<AssocEntityDetails>()}) {
|
|
if (const Symbol * root{GetAssociatedVariable(*details)}) {
|
|
return *root;
|
|
}
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
const Symbol *GetMainEntry(const Symbol *symbol) {
|
|
if (symbol) {
|
|
if (const auto *subpDetails{symbol->detailsIf<SubprogramDetails>()}) {
|
|
if (const Scope * scope{subpDetails->entryScope()}) {
|
|
if (const Symbol * main{scope->symbol()}) {
|
|
return main;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return symbol;
|
|
}
|
|
|
|
bool IsVariableName(const Symbol &original) {
|
|
const Symbol &ultimate{original.GetUltimate()};
|
|
return !IsNamedConstant(ultimate) &&
|
|
(ultimate.has<ObjectEntityDetails>() ||
|
|
ultimate.has<AssocEntityDetails>());
|
|
}
|
|
|
|
static bool IsPureProcedureImpl(
|
|
const Symbol &original, semantics::UnorderedSymbolSet &set) {
|
|
// An ENTRY is pure if its containing subprogram is
|
|
const Symbol &symbol{DEREF(GetMainEntry(&original.GetUltimate()))};
|
|
if (set.find(symbol) != set.end()) {
|
|
return true;
|
|
}
|
|
set.emplace(symbol);
|
|
if (const auto *procDetails{symbol.detailsIf<ProcEntityDetails>()}) {
|
|
if (procDetails->procInterface()) {
|
|
// procedure with a pure interface
|
|
return IsPureProcedureImpl(*procDetails->procInterface(), set);
|
|
}
|
|
} else if (const auto *details{symbol.detailsIf<ProcBindingDetails>()}) {
|
|
return IsPureProcedureImpl(details->symbol(), set);
|
|
} else if (!IsProcedure(symbol)) {
|
|
return false;
|
|
}
|
|
if (IsStmtFunction(symbol)) {
|
|
// Section 15.7(1) states that a statement function is PURE if it does not
|
|
// reference an IMPURE procedure or a VOLATILE variable
|
|
if (const auto &expr{symbol.get<SubprogramDetails>().stmtFunction()}) {
|
|
for (const SymbolRef &ref : evaluate::CollectSymbols(*expr)) {
|
|
if (&*ref == &symbol) {
|
|
return false; // error recovery, recursion is caught elsewhere
|
|
}
|
|
if (IsFunction(*ref) && !IsPureProcedureImpl(*ref, set)) {
|
|
return false;
|
|
}
|
|
if (ref->GetUltimate().attrs().test(Attr::VOLATILE)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true; // statement function was not found to be impure
|
|
}
|
|
return symbol.attrs().test(Attr::PURE) ||
|
|
(symbol.attrs().test(Attr::ELEMENTAL) &&
|
|
!symbol.attrs().test(Attr::IMPURE));
|
|
}
|
|
|
|
bool IsPureProcedure(const Symbol &original) {
|
|
semantics::UnorderedSymbolSet set;
|
|
return IsPureProcedureImpl(original, set);
|
|
}
|
|
|
|
bool IsPureProcedure(const Scope &scope) {
|
|
const Symbol *symbol{scope.GetSymbol()};
|
|
return symbol && IsPureProcedure(*symbol);
|
|
}
|
|
|
|
bool IsExplicitlyImpureProcedure(const Symbol &original) {
|
|
// An ENTRY is IMPURE if its containing subprogram is so
|
|
return DEREF(GetMainEntry(&original.GetUltimate()))
|
|
.attrs()
|
|
.test(Attr::IMPURE);
|
|
}
|
|
|
|
bool IsElementalProcedure(const Symbol &original) {
|
|
// An ENTRY is elemental if its containing subprogram is
|
|
const Symbol &symbol{DEREF(GetMainEntry(&original.GetUltimate()))};
|
|
if (IsProcedure(symbol)) {
|
|
auto &foldingContext{symbol.owner().context().foldingContext()};
|
|
auto restorer{foldingContext.messages().DiscardMessages()};
|
|
auto proc{evaluate::characteristics::Procedure::Characterize(
|
|
symbol, foldingContext)};
|
|
return proc &&
|
|
proc->attrs.test(evaluate::characteristics::Procedure::Attr::Elemental);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsFunction(const Symbol &symbol) {
|
|
const Symbol &ultimate{symbol.GetUltimate()};
|
|
return ultimate.test(Symbol::Flag::Function) ||
|
|
(!ultimate.test(Symbol::Flag::Subroutine) &&
|
|
common::visit(
|
|
common::visitors{
|
|
[](const SubprogramDetails &x) { return x.isFunction(); },
|
|
[](const ProcEntityDetails &x) {
|
|
const Symbol *ifc{x.procInterface()};
|
|
return x.type() || (ifc && IsFunction(*ifc));
|
|
},
|
|
[](const ProcBindingDetails &x) {
|
|
return IsFunction(x.symbol());
|
|
},
|
|
[](const auto &) { return false; },
|
|
},
|
|
ultimate.details()));
|
|
}
|
|
|
|
bool IsFunction(const Scope &scope) {
|
|
const Symbol *symbol{scope.GetSymbol()};
|
|
return symbol && IsFunction(*symbol);
|
|
}
|
|
|
|
bool IsProcedure(const Symbol &symbol) {
|
|
return common::visit(common::visitors{
|
|
[&symbol](const SubprogramDetails &) {
|
|
const Scope *scope{symbol.scope()};
|
|
// Main programs & BLOCK DATA are not procedures.
|
|
return !scope ||
|
|
scope->kind() == Scope::Kind::Subprogram;
|
|
},
|
|
[](const SubprogramNameDetails &) { return true; },
|
|
[](const ProcEntityDetails &) { return true; },
|
|
[](const GenericDetails &) { return true; },
|
|
[](const ProcBindingDetails &) { return true; },
|
|
[](const auto &) { return false; },
|
|
},
|
|
symbol.GetUltimate().details());
|
|
}
|
|
|
|
bool IsProcedure(const Scope &scope) {
|
|
const Symbol *symbol{scope.GetSymbol()};
|
|
return symbol && IsProcedure(*symbol);
|
|
}
|
|
|
|
bool IsProcedurePointer(const Symbol &original) {
|
|
const Symbol &symbol{GetAssociationRoot(original)};
|
|
return IsPointer(symbol) && IsProcedure(symbol);
|
|
}
|
|
|
|
bool IsProcedurePointer(const Symbol *symbol) {
|
|
return symbol && IsProcedurePointer(*symbol);
|
|
}
|
|
|
|
bool IsObjectPointer(const Symbol *original) {
|
|
if (original) {
|
|
const Symbol &symbol{GetAssociationRoot(*original)};
|
|
return IsPointer(symbol) && !IsProcedure(symbol);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsAllocatableOrObjectPointer(const Symbol *original) {
|
|
if (original) {
|
|
const Symbol &ultimate{original->GetUltimate()};
|
|
if (const auto *assoc{ultimate.detailsIf<AssocEntityDetails>()}) {
|
|
// Only SELECT RANK construct entities can be ALLOCATABLE/POINTER.
|
|
return (assoc->rank() || assoc->IsAssumedSize() ||
|
|
assoc->IsAssumedRank()) &&
|
|
IsAllocatableOrObjectPointer(UnwrapWholeSymbolDataRef(assoc->expr()));
|
|
} else {
|
|
return IsAllocatable(ultimate) ||
|
|
(IsPointer(ultimate) && !IsProcedure(ultimate));
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const Symbol *FindCommonBlockContaining(const Symbol &original) {
|
|
const Symbol &root{GetAssociationRoot(original)};
|
|
const auto *details{root.detailsIf<ObjectEntityDetails>()};
|
|
return details ? details->commonBlock() : nullptr;
|
|
}
|
|
|
|
// 3.11 automatic data object
|
|
bool IsAutomatic(const Symbol &original) {
|
|
const Symbol &symbol{original.GetUltimate()};
|
|
if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
|
|
if (!object->isDummy() && !IsAllocatable(symbol) && !IsPointer(symbol)) {
|
|
if (const DeclTypeSpec * type{symbol.GetType()}) {
|
|
// If a type parameter value is not a constant expression, the
|
|
// object is automatic.
|
|
if (type->category() == DeclTypeSpec::Character) {
|
|
if (const auto &length{
|
|
type->characterTypeSpec().length().GetExplicit()}) {
|
|
if (!evaluate::IsConstantExpr(*length)) {
|
|
return true;
|
|
}
|
|
}
|
|
} else if (const DerivedTypeSpec * derived{type->AsDerived()}) {
|
|
for (const auto &pair : derived->parameters()) {
|
|
if (const auto &value{pair.second.GetExplicit()}) {
|
|
if (!evaluate::IsConstantExpr(*value)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If an array bound is not a constant expression, the object is
|
|
// automatic.
|
|
for (const ShapeSpec &dim : object->shape()) {
|
|
if (const auto &lb{dim.lbound().GetExplicit()}) {
|
|
if (!evaluate::IsConstantExpr(*lb)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (const auto &ub{dim.ubound().GetExplicit()}) {
|
|
if (!evaluate::IsConstantExpr(*ub)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsSaved(const Symbol &original) {
|
|
const Symbol &symbol{GetAssociationRoot(original)};
|
|
const Scope &scope{symbol.owner()};
|
|
const common::LanguageFeatureControl &features{
|
|
scope.context().languageFeatures()};
|
|
auto scopeKind{scope.kind()};
|
|
if (symbol.has<AssocEntityDetails>()) {
|
|
return false; // ASSOCIATE(non-variable)
|
|
} else if (scopeKind == Scope::Kind::DerivedType) {
|
|
return false; // this is a component
|
|
} else if (symbol.attrs().test(Attr::SAVE)) {
|
|
return true; // explicit SAVE attribute
|
|
} else if (IsDummy(symbol) || IsFunctionResult(symbol) ||
|
|
IsAutomatic(symbol) || IsNamedConstant(symbol)) {
|
|
return false;
|
|
} else if (scopeKind == Scope::Kind::Module ||
|
|
(scopeKind == Scope::Kind::MainProgram &&
|
|
(symbol.attrs().test(Attr::TARGET) || evaluate::IsCoarray(symbol)))) {
|
|
// 8.5.16p4
|
|
// In main programs, implied SAVE matters only for pointer
|
|
// initialization targets and coarrays.
|
|
return true;
|
|
} else if (scopeKind == Scope::Kind::MainProgram &&
|
|
(features.IsEnabled(common::LanguageFeature::SaveMainProgram) ||
|
|
(features.IsEnabled(
|
|
common::LanguageFeature::SaveBigMainProgramVariables) &&
|
|
symbol.size() > 32))) {
|
|
// With SaveBigMainProgramVariables, keeping all unsaved main program
|
|
// variables of 32 bytes or less on the stack allows keeping numerical and
|
|
// logical scalars, small scalar characters or derived, small arrays, and
|
|
// scalar descriptors on the stack. This leaves more room for lower level
|
|
// optimizers to do register promotion or get easy aliasing information.
|
|
return true;
|
|
} else if (features.IsEnabled(common::LanguageFeature::DefaultSave) &&
|
|
(scopeKind == Scope::Kind::MainProgram ||
|
|
(scope.kind() == Scope::Kind::Subprogram &&
|
|
!(scope.symbol() &&
|
|
scope.symbol()->attrs().test(Attr::RECURSIVE))))) {
|
|
// -fno-automatic/-save/-Msave option applies to all objects in executable
|
|
// main programs and subprograms unless they are explicitly RECURSIVE.
|
|
return true;
|
|
} else if (symbol.test(Symbol::Flag::InDataStmt)) {
|
|
return true;
|
|
} else if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()};
|
|
object && object->init()) {
|
|
return true;
|
|
} else if (IsProcedurePointer(symbol) && symbol.has<ProcEntityDetails>() &&
|
|
symbol.get<ProcEntityDetails>().init()) {
|
|
return true;
|
|
} else if (scope.hasSAVE()) {
|
|
return true; // bare SAVE statement
|
|
} else if (const Symbol * block{FindCommonBlockContaining(symbol)};
|
|
block && block->attrs().test(Attr::SAVE)) {
|
|
return true; // in COMMON with SAVE
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsDummy(const Symbol &symbol) {
|
|
return common::visit(
|
|
common::visitors{[](const EntityDetails &x) { return x.isDummy(); },
|
|
[](const ObjectEntityDetails &x) { return x.isDummy(); },
|
|
[](const ProcEntityDetails &x) { return x.isDummy(); },
|
|
[](const SubprogramDetails &x) { return x.isDummy(); },
|
|
[](const auto &) { return false; }},
|
|
ResolveAssociations(symbol).details());
|
|
}
|
|
|
|
bool IsAssumedShape(const Symbol &symbol) {
|
|
const Symbol &ultimate{ResolveAssociations(symbol)};
|
|
const auto *object{ultimate.detailsIf<ObjectEntityDetails>()};
|
|
return object && object->CanBeAssumedShape() &&
|
|
!semantics::IsAllocatableOrObjectPointer(&ultimate);
|
|
}
|
|
|
|
bool IsDeferredShape(const Symbol &symbol) {
|
|
const Symbol &ultimate{ResolveAssociations(symbol)};
|
|
const auto *object{ultimate.detailsIf<ObjectEntityDetails>()};
|
|
return object && object->CanBeDeferredShape() &&
|
|
semantics::IsAllocatableOrObjectPointer(&ultimate);
|
|
}
|
|
|
|
bool IsFunctionResult(const Symbol &original) {
|
|
const Symbol &symbol{GetAssociationRoot(original)};
|
|
return common::visit(
|
|
common::visitors{
|
|
[](const EntityDetails &x) { return x.isFuncResult(); },
|
|
[](const ObjectEntityDetails &x) { return x.isFuncResult(); },
|
|
[](const ProcEntityDetails &x) { return x.isFuncResult(); },
|
|
[](const auto &) { return false; },
|
|
},
|
|
symbol.details());
|
|
}
|
|
|
|
bool IsKindTypeParameter(const Symbol &symbol) {
|
|
const auto *param{symbol.GetUltimate().detailsIf<TypeParamDetails>()};
|
|
return param && param->attr() == common::TypeParamAttr::Kind;
|
|
}
|
|
|
|
bool IsLenTypeParameter(const Symbol &symbol) {
|
|
const auto *param{symbol.GetUltimate().detailsIf<TypeParamDetails>()};
|
|
return param && param->attr() == common::TypeParamAttr::Len;
|
|
}
|
|
|
|
bool IsExtensibleType(const DerivedTypeSpec *derived) {
|
|
return derived && !IsIsoCType(derived) &&
|
|
!derived->typeSymbol().attrs().test(Attr::BIND_C) &&
|
|
!derived->typeSymbol().get<DerivedTypeDetails>().sequence();
|
|
}
|
|
|
|
bool IsBuiltinDerivedType(const DerivedTypeSpec *derived, const char *name) {
|
|
if (!derived) {
|
|
return false;
|
|
} else {
|
|
const auto &symbol{derived->typeSymbol()};
|
|
return &symbol.owner() == symbol.owner().context().GetBuiltinsScope() &&
|
|
symbol.name() == "__builtin_"s + name;
|
|
}
|
|
}
|
|
|
|
bool IsBuiltinCPtr(const Symbol &symbol) {
|
|
if (const DeclTypeSpec *declType = symbol.GetType()) {
|
|
if (const DerivedTypeSpec *derived = declType->AsDerived()) {
|
|
return IsIsoCType(derived);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsIsoCType(const DerivedTypeSpec *derived) {
|
|
return IsBuiltinDerivedType(derived, "c_ptr") ||
|
|
IsBuiltinDerivedType(derived, "c_funptr");
|
|
}
|
|
|
|
bool IsEventType(const DerivedTypeSpec *derived) {
|
|
return IsBuiltinDerivedType(derived, "event_type");
|
|
}
|
|
|
|
bool IsLockType(const DerivedTypeSpec *derived) {
|
|
return IsBuiltinDerivedType(derived, "lock_type");
|
|
}
|
|
|
|
bool IsTeamType(const DerivedTypeSpec *derived) {
|
|
return IsBuiltinDerivedType(derived, "team_type");
|
|
}
|
|
|
|
bool IsBadCoarrayType(const DerivedTypeSpec *derived) {
|
|
return IsTeamType(derived) || IsIsoCType(derived);
|
|
}
|
|
|
|
bool IsEventTypeOrLockType(const DerivedTypeSpec *derivedTypeSpec) {
|
|
return IsEventType(derivedTypeSpec) || IsLockType(derivedTypeSpec);
|
|
}
|
|
|
|
int CountLenParameters(const DerivedTypeSpec &type) {
|
|
return llvm::count_if(
|
|
type.parameters(), [](const auto &pair) { return pair.second.isLen(); });
|
|
}
|
|
|
|
int CountNonConstantLenParameters(const DerivedTypeSpec &type) {
|
|
return llvm::count_if(type.parameters(), [](const auto &pair) {
|
|
if (!pair.second.isLen()) {
|
|
return false;
|
|
} else if (const auto &expr{pair.second.GetExplicit()}) {
|
|
return !IsConstantExpr(*expr);
|
|
} else {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
const Symbol &GetUsedModule(const UseDetails &details) {
|
|
return DEREF(details.symbol().owner().symbol());
|
|
}
|
|
|
|
static const Symbol *FindFunctionResult(
|
|
const Symbol &original, UnorderedSymbolSet &seen) {
|
|
const Symbol &root{GetAssociationRoot(original)};
|
|
;
|
|
if (!seen.insert(root).second) {
|
|
return nullptr; // don't loop
|
|
}
|
|
return common::visit(
|
|
common::visitors{[](const SubprogramDetails &subp) {
|
|
return subp.isFunction() ? &subp.result() : nullptr;
|
|
},
|
|
[&](const ProcEntityDetails &proc) {
|
|
const Symbol *iface{proc.procInterface()};
|
|
return iface ? FindFunctionResult(*iface, seen) : nullptr;
|
|
},
|
|
[&](const ProcBindingDetails &binding) {
|
|
return FindFunctionResult(binding.symbol(), seen);
|
|
},
|
|
[](const auto &) -> const Symbol * { return nullptr; }},
|
|
root.details());
|
|
}
|
|
|
|
const Symbol *FindFunctionResult(const Symbol &symbol) {
|
|
UnorderedSymbolSet seen;
|
|
return FindFunctionResult(symbol, seen);
|
|
}
|
|
|
|
// These are here in Evaluate/tools.cpp so that Evaluate can use
|
|
// them; they cannot be defined in symbol.h due to the dependence
|
|
// on Scope.
|
|
|
|
bool SymbolSourcePositionCompare::operator()(
|
|
const SymbolRef &x, const SymbolRef &y) const {
|
|
return x->GetSemanticsContext().allCookedSources().Precedes(
|
|
x->name(), y->name());
|
|
}
|
|
bool SymbolSourcePositionCompare::operator()(
|
|
const MutableSymbolRef &x, const MutableSymbolRef &y) const {
|
|
return x->GetSemanticsContext().allCookedSources().Precedes(
|
|
x->name(), y->name());
|
|
}
|
|
|
|
SemanticsContext &Symbol::GetSemanticsContext() const {
|
|
return DEREF(owner_).context();
|
|
}
|
|
|
|
bool AreTkCompatibleTypes(const DeclTypeSpec *x, const DeclTypeSpec *y) {
|
|
if (x && y) {
|
|
if (auto xDt{evaluate::DynamicType::From(*x)}) {
|
|
if (auto yDt{evaluate::DynamicType::From(*y)}) {
|
|
return xDt->IsTkCompatibleWith(*yDt);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
common::IgnoreTKRSet GetIgnoreTKR(const Symbol &symbol) {
|
|
common::IgnoreTKRSet result;
|
|
if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
|
|
result = object->ignoreTKR();
|
|
if (const Symbol * ownerSymbol{symbol.owner().symbol()}) {
|
|
if (const auto *ownerSubp{ownerSymbol->detailsIf<SubprogramDetails>()}) {
|
|
if (ownerSubp->defaultIgnoreTKR()) {
|
|
result |= common::ignoreTKRAll;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::optional<int> GetDummyArgumentNumber(const Symbol *symbol) {
|
|
if (symbol) {
|
|
if (IsDummy(*symbol)) {
|
|
if (const Symbol * subpSym{symbol->owner().symbol()}) {
|
|
if (const auto *subp{subpSym->detailsIf<SubprogramDetails>()}) {
|
|
int j{0};
|
|
for (const Symbol *dummy : subp->dummyArgs()) {
|
|
if (dummy == symbol) {
|
|
return j;
|
|
}
|
|
++j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace Fortran::semantics
|