[flang] Implement conditional expressions parser/semantics (F2023) (#186489)
## Implement Fortran 2023 Conditional Expressions (R1002) ***This PR contains the implementation for parsing and semantic analysis. Lowering is implemented in a separate PR (#186490)*** Implements Fortran 2023 conditional expressions with syntax: `result = (condition ? value1 : condition2 ? value2 : ... : elseValue)` Issue: #176999 Discourse: https://discourse.llvm.org/t/rfc-adding-conditional-expressions-in-flang-f2023/89869/1 -- note that some of the details provided in the RFC post are no longer accurate ### Implementation Details **Parser:** - Added ConditionalExpr as primary expression (F2023 R1002) - Right-associative chaining for multi-way conditionals **Semantics:** - Expression tree node ConditionalExpr<T> with N conditions and N+1 values - Strict type checking: all values must have identical type, kind, and rank - Conditions must be scalar LOGICAL **LIT Testing:** - Parser tests: Syntax validation, precedence, nesting - Semantic tests: Type checking, error messages - Note: Executable tests will be added to the llvm-test-suite repo (https://github.com/llvm/llvm-test-suite/pull/369) **Limitations** - Conditional arguments are not yet supported. This work is planned - #180592 - Polymorphic types (CLASS) not yet supported in lowering - Both limitations will emit clear error message if encountered ### Examples ``` ! Simple conditional x = (flag ? 10 : 20) ! Chained result = (x > 0 ? 1 : x < 0 ? -1 : 0) ! Examples from F2023 ( ABS (RESIDUAL)<=TOLERANCE ? ’ok’ : ’did not converge’ ) ( I>0 .AND. I<=SIZE (A) ? A (I) : PRESENT (VAL) ? VAL : 0.0 ) ``` AI Usage Disclosure: AI tools (Claude Sonnet 4.5) were used to assist with implementation of this feature and test code generation. I have reviewed, modified, and tested all AI-generated code.
This commit is contained in:
parent
46dc97c0ea
commit
802c5cbe5b
28 changed files with 1178 additions and 14 deletions
|
|
@ -311,6 +311,7 @@ public:
|
|||
READ_FEATURE(Expr::NEQV)
|
||||
READ_FEATURE(Expr::DefinedBinary)
|
||||
READ_FEATURE(Expr::ComplexConstructor)
|
||||
READ_FEATURE(ConditionalExpr)
|
||||
READ_FEATURE(External)
|
||||
READ_FEATURE(ExternalStmt)
|
||||
READ_FEATURE(FailImageStmt)
|
||||
|
|
|
|||
|
|
@ -390,6 +390,34 @@ struct LogicalOperation
|
|||
LogicalOperator logicalOperator;
|
||||
};
|
||||
|
||||
// Fortran 2023 conditional expression: (cond ? val : cond ? val : ... : else)
|
||||
// All branches have the same type and rank (verified during semantic analysis).
|
||||
template <typename T> class ConditionalExpr {
|
||||
public:
|
||||
using Result = T;
|
||||
CLASS_BOILERPLATE(ConditionalExpr)
|
||||
ConditionalExpr(Expr<LogicalResult> &&cond, Expr<Result> &&thenVal,
|
||||
Expr<Result> &&elseVal)
|
||||
: condition_{std::move(cond)}, thenValue_{std::move(thenVal)},
|
||||
elseValue_{std::move(elseVal)} {}
|
||||
bool operator==(const ConditionalExpr &) const;
|
||||
Expr<LogicalResult> &condition() { return condition_.value(); }
|
||||
const Expr<LogicalResult> &condition() const { return condition_.value(); }
|
||||
Expr<Result> &thenValue() { return thenValue_.value(); }
|
||||
const Expr<Result> &thenValue() const { return thenValue_.value(); }
|
||||
Expr<Result> &elseValue() { return elseValue_.value(); }
|
||||
const Expr<Result> &elseValue() const { return elseValue_.value(); }
|
||||
int Rank() const { return thenValue().Rank(); }
|
||||
std::optional<DynamicType> GetType() const { return thenValue().GetType(); }
|
||||
static constexpr int Corank() { return 0; }
|
||||
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
|
||||
|
||||
private:
|
||||
common::CopyableIndirection<Expr<LogicalResult>> condition_;
|
||||
common::CopyableIndirection<Expr<Result>> thenValue_;
|
||||
common::CopyableIndirection<Expr<Result>> elseValue_;
|
||||
};
|
||||
|
||||
// Array constructors
|
||||
template <typename RESULT> class ArrayConstructorValues;
|
||||
|
||||
|
|
@ -536,7 +564,7 @@ private:
|
|||
Convert<Result, TypeCategory::Unsigned>>;
|
||||
using Operations = std::tuple<Parentheses<Result>, Negate<Result>,
|
||||
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
|
||||
Power<Result>, Extremum<Result>>;
|
||||
Power<Result>, Extremum<Result>, ConditionalExpr<Result>>;
|
||||
using Indices = std::conditional_t<KIND == ImpliedDoIndex::Result::kind,
|
||||
std::tuple<ImpliedDoIndex>, std::tuple<>>;
|
||||
using TypeParamInquiries =
|
||||
|
|
@ -568,7 +596,7 @@ private:
|
|||
Convert<Result, TypeCategory::Unsigned>>;
|
||||
using Operations = std::tuple<Parentheses<Result>, Negate<Result>,
|
||||
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
|
||||
Power<Result>, Extremum<Result>>;
|
||||
Power<Result>, Extremum<Result>, ConditionalExpr<Result>>;
|
||||
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
|
||||
Designator<Result>, FunctionRef<Result>>;
|
||||
|
||||
|
|
@ -594,7 +622,8 @@ private:
|
|||
Convert<Result, TypeCategory::Unsigned>>;
|
||||
using Operations = std::variant<ComplexComponent<KIND>, Parentheses<Result>,
|
||||
Negate<Result>, Add<Result>, Subtract<Result>, Multiply<Result>,
|
||||
Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>>;
|
||||
Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>,
|
||||
ConditionalExpr<Result>>;
|
||||
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
|
||||
Designator<Result>, FunctionRef<Result>>;
|
||||
|
||||
|
|
@ -612,7 +641,7 @@ public:
|
|||
using Operations = std::variant<Parentheses<Result>, Negate<Result>,
|
||||
Convert<Result, TypeCategory::Complex>, Add<Result>, Subtract<Result>,
|
||||
Multiply<Result>, Divide<Result>, Power<Result>, RealToIntPower<Result>,
|
||||
ComplexConstructor<KIND>>;
|
||||
ComplexConstructor<KIND>, ConditionalExpr<Result>>;
|
||||
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
|
||||
Designator<Result>, FunctionRef<Result>>;
|
||||
|
||||
|
|
@ -638,7 +667,7 @@ public:
|
|||
|
||||
std::variant<Constant<Result>, ArrayConstructor<Result>, Designator<Result>,
|
||||
FunctionRef<Result>, Parentheses<Result>, Convert<Result>, Concat<KIND>,
|
||||
Extremum<Result>, SetLength<KIND>>
|
||||
Extremum<Result>, SetLength<KIND>, ConditionalExpr<Result>>
|
||||
u;
|
||||
};
|
||||
|
||||
|
|
@ -710,7 +739,7 @@ public:
|
|||
|
||||
private:
|
||||
using Operations = std::tuple<Convert<Result>, Parentheses<Result>, Not<KIND>,
|
||||
LogicalOperation<KIND>>;
|
||||
LogicalOperation<KIND>, ConditionalExpr<Result>>;
|
||||
using Relations = std::conditional_t<KIND == LogicalResult::kind,
|
||||
std::tuple<Relational<SomeType>>, std::tuple<>>;
|
||||
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
|
||||
|
|
@ -788,7 +817,8 @@ public:
|
|||
using Result = SomeDerived;
|
||||
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
|
||||
std::variant<Constant<Result>, ArrayConstructor<Result>, StructureConstructor,
|
||||
Designator<Result>, FunctionRef<Result>, Parentheses<Result>>
|
||||
Designator<Result>, FunctionRef<Result>, Parentheses<Result>,
|
||||
ConditionalExpr<Result>>
|
||||
u;
|
||||
};
|
||||
|
||||
|
|
@ -929,6 +959,7 @@ FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructor, )
|
|||
template class Relational<SomeType>; \
|
||||
FOR_EACH_TYPE_AND_KIND(template class ExpressionBase, ) \
|
||||
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructorValues, ) \
|
||||
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructor, )
|
||||
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructor, ) \
|
||||
FOR_EACH_INTRINSIC_KIND(template class ConditionalExpr, )
|
||||
} // namespace Fortran::evaluate
|
||||
#endif // FORTRAN_EVALUATE_EXPRESSION_H_
|
||||
|
|
|
|||
|
|
@ -105,6 +105,15 @@ std::optional<std::int64_t> ToInt64(const Expr<SomeUnsigned> &);
|
|||
std::optional<std::int64_t> ToInt64(const Expr<SomeType> &);
|
||||
std::optional<std::int64_t> ToInt64(const ActualArgument &);
|
||||
|
||||
// When an expression is a constant logical scalar, ToLogical() extracts its
|
||||
// value.
|
||||
inline std::optional<bool> ToLogical(const Expr<LogicalResult> &expr) {
|
||||
if (auto val{GetScalarConstantValue<LogicalResult>(expr)}) {
|
||||
return val->IsTrue();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename A>
|
||||
std::optional<std::int64_t> ToInt64(const std::optional<A> &x) {
|
||||
if (x) {
|
||||
|
|
|
|||
|
|
@ -189,6 +189,21 @@ public:
|
|||
Result operator()(const ArrayConstructor<T> &aconst) const {
|
||||
return Shape{GetArrayConstructorExtent(aconst)};
|
||||
}
|
||||
template <typename T>
|
||||
Result operator()(const ConditionalExpr<T> &conditional) const {
|
||||
// Per F2023 10.1.4(7), the shape is that of the selected branch.
|
||||
// When all branches have identical static extents, return the common shape.
|
||||
int rank{conditional.thenValue().Rank()};
|
||||
Result thenShape{(*this)(conditional.thenValue())};
|
||||
if (!thenShape) {
|
||||
return Shape(rank, std::nullopt);
|
||||
}
|
||||
Result elseShape{(*this)(conditional.elseValue())};
|
||||
if (thenShape != elseShape) {
|
||||
return Shape(rank, std::nullopt);
|
||||
}
|
||||
return thenShape;
|
||||
}
|
||||
template <typename D, typename R, typename LO, typename RO>
|
||||
Result operator()(const Operation<D, R, LO, RO> &operation) const {
|
||||
if (int rr{operation.right().Rank()}; rr > 0) {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ struct IsVariableHelper
|
|||
Result operator()(const CoarrayRef &) const { return true; }
|
||||
Result operator()(const ComplexPart &) const { return true; }
|
||||
Result operator()(const ProcedureDesignator &) const;
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &) const {
|
||||
return false;
|
||||
}
|
||||
template <typename T> Result operator()(const Expr<T> &x) const {
|
||||
if constexpr (common::HasMember<T, AllIntrinsicTypes> ||
|
||||
std::is_same_v<T, SomeDerived>) {
|
||||
|
|
@ -1410,6 +1413,7 @@ enum class Operator {
|
|||
Call,
|
||||
Constant,
|
||||
Convert,
|
||||
Conditional,
|
||||
Div,
|
||||
Eq,
|
||||
Eqv,
|
||||
|
|
|
|||
|
|
@ -224,6 +224,10 @@ public:
|
|||
Result operator()(const StructureConstructor &x) const {
|
||||
return visitor_.Combine(visitor_(x.derivedTypeSpec()), CombineContents(x));
|
||||
}
|
||||
// Conditional expressions (Fortran 2023)
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &x) const {
|
||||
return Combine(x.condition(), x.thenValue(), x.elseValue());
|
||||
}
|
||||
|
||||
// Operations and wrappers
|
||||
// Have a single operator() for all Operations.
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ inline constexpr bool IsValidFortranTokenCharacter(char ch) {
|
|||
case '<':
|
||||
case '=':
|
||||
case '>':
|
||||
case '?': // Used in conditional expressions (Fortran 2023)
|
||||
case '[':
|
||||
case ']':
|
||||
case '{': // Used in OpenMP context selector specification
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ public:
|
|||
NODE(parser, ComputedGotoStmt)
|
||||
NODE(parser, ConcurrentControl)
|
||||
NODE(parser, ConcurrentHeader)
|
||||
NODE(parser, ConditionalExpr)
|
||||
NODE(parser, ConnectSpec)
|
||||
NODE(ConnectSpec, CharExpr)
|
||||
NODE_ENUM(ConnectSpec::CharExpr, Kind)
|
||||
|
|
|
|||
|
|
@ -1678,6 +1678,17 @@ struct ImageSelector {
|
|||
std::tuple<std::list<Cosubscript>, std::list<ImageSelectorSpec>> t;
|
||||
};
|
||||
|
||||
// F2023 R1002 conditional-expr ->
|
||||
// ( scalar-logical-expr ? expr
|
||||
// [ : scalar-logical-expr ? expr ]...
|
||||
// : expr )
|
||||
struct ConditionalExpr {
|
||||
TUPLE_CLASS_BOILERPLATE(ConditionalExpr);
|
||||
std::tuple<ScalarLogicalExpr, common::Indirection<Expr>,
|
||||
common::Indirection<Expr>>
|
||||
t;
|
||||
};
|
||||
|
||||
// R1001 - R1022 expressions
|
||||
struct Expr {
|
||||
UNION_CLASS_BOILERPLATE(Expr);
|
||||
|
|
@ -1776,11 +1787,12 @@ struct Expr {
|
|||
CharBlock source;
|
||||
|
||||
std::variant<common::Indirection<CharLiteralConstantSubstring>,
|
||||
LiteralConstant, common::Indirection<Designator>, ArrayConstructor,
|
||||
StructureConstructor, common::Indirection<FunctionReference>, Parentheses,
|
||||
UnaryPlus, Negate, NOT, PercentLoc, DefinedUnary, Power, Multiply, Divide,
|
||||
Add, Subtract, Concat, LT, LE, EQ, NE, GE, GT, AND, OR, EQV, NEQV,
|
||||
DefinedBinary, ComplexConstructor, common::Indirection<SubstringInquiry>>
|
||||
LiteralConstant, ConditionalExpr, common::Indirection<Designator>,
|
||||
ArrayConstructor, StructureConstructor,
|
||||
common::Indirection<FunctionReference>, Parentheses, UnaryPlus, Negate,
|
||||
NOT, PercentLoc, DefinedUnary, Power, Multiply, Divide, Add, Subtract,
|
||||
Concat, LT, LE, EQ, NE, GE, GT, AND, OR, EQV, NEQV, DefinedBinary,
|
||||
ComplexConstructor, common::Indirection<SubstringInquiry>>
|
||||
u;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,19 @@ private:
|
|||
Show(op.right());
|
||||
Outdent();
|
||||
}
|
||||
template <typename T> void Show(const evaluate::ConditionalExpr<T> &x) {
|
||||
Indent("conditional expr "s + std::string(TypeOf<T>::name));
|
||||
Indent("condition");
|
||||
Show(x.condition());
|
||||
Outdent();
|
||||
Indent("then");
|
||||
Show(x.thenValue());
|
||||
Outdent();
|
||||
Indent("else");
|
||||
Show(x.elseValue());
|
||||
Outdent();
|
||||
Outdent();
|
||||
}
|
||||
void Show(const evaluate::Relational<evaluate::SomeType> &x);
|
||||
template <typename T> void Show(const evaluate::Expr<T> &x) {
|
||||
Indent("expr <"s + std::string(TypeOf<T>::name) + ">"s);
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ public:
|
|||
MaybeExpr Analyze(const parser::DataStmtValue &);
|
||||
MaybeExpr Analyze(const parser::AllocateObject &);
|
||||
MaybeExpr Analyze(const parser::PointerObject &);
|
||||
MaybeExpr Analyze(const parser::ConditionalExpr &);
|
||||
|
||||
template <typename A> MaybeExpr Analyze(const common::Indirection<A> &x) {
|
||||
return Analyze(x.value());
|
||||
|
|
|
|||
|
|
@ -112,6 +112,19 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
template <typename T> bool operator()(const ConditionalExpr<T> &x) const {
|
||||
// A conditional expression is a primary. Therefore, only the selected
|
||||
// branch must be constant. If the condition is a constant expression
|
||||
// whose value cannot yet be determined, both branches must be constant.
|
||||
if (!(*this)(x.condition())) {
|
||||
return false;
|
||||
} else if (auto condVal{ToLogical(x.condition())}) {
|
||||
return *condVal ? (*this)(x.thenValue()) : (*this)(x.elseValue());
|
||||
} else {
|
||||
return (*this)(x.thenValue()) && (*this)(x.elseValue());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsConstantStructureConstructorComponent(
|
||||
const Symbol &, const Expr<SomeType> &) const;
|
||||
|
|
@ -358,6 +371,10 @@ public:
|
|||
bool operator()(const Operation<D, R, O...> &) const {
|
||||
return false;
|
||||
}
|
||||
template <typename T> bool operator()(const ConditionalExpr<T> &) const {
|
||||
// A conditional expression cannot be an initial data target
|
||||
return false;
|
||||
}
|
||||
template <typename T> bool operator()(const Parentheses<T> &x) const {
|
||||
return (*this)(x.left());
|
||||
}
|
||||
|
|
@ -1193,6 +1210,12 @@ public:
|
|||
|
||||
Result operator()(const NullPointer &) const { return true; }
|
||||
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &x) {
|
||||
// Conditional expressions are never variables; expression results are
|
||||
// always contiguous.
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns "true" for a provably empty or simply contiguous array section;
|
||||
// return "false" for a provably nonempty discontiguous section or for use
|
||||
|
|
@ -1760,6 +1783,12 @@ public:
|
|||
return {}; // doesn't count as a use
|
||||
}
|
||||
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &condExpr) {
|
||||
auto restorer{common::ScopedSet(isDefinition_, false)};
|
||||
return Combine((*this)(condExpr.condition()),
|
||||
Combine((*this)(condExpr.thenValue()), (*this)(condExpr.elseValue())));
|
||||
}
|
||||
|
||||
private:
|
||||
static bool IsBindingUsedAsProcedure(const Expr<SomeType> &expr) {
|
||||
if (const auto *pd{std::get_if<ProcedureDesignator>(&expr.u)}) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,16 @@ Expr<Type<TypeCategory::Character, KIND>>::LEN() const {
|
|||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
[](const ConditionalExpr<Result> &x) -> T {
|
||||
if (auto tlen{x.thenValue().LEN()}) {
|
||||
if (auto elen{x.elseValue().LEN()}) {
|
||||
if (*tlen == *elen) {
|
||||
return tlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
[](const Designator<Result> &dr) { return dr.LEN(); },
|
||||
[](const FunctionRef<Result> &fr) { return fr.LEN(); },
|
||||
[](const SetLength<KIND> &x) -> T { return x.right(); },
|
||||
|
|
@ -141,6 +151,12 @@ template <typename A> bool Extremum<A>::operator==(const Extremum &that) const {
|
|||
return ordering == that.ordering && Base::operator==(that);
|
||||
}
|
||||
|
||||
template <typename A>
|
||||
bool ConditionalExpr<A>::operator==(const ConditionalExpr &that) const {
|
||||
return condition_ == that.condition_ && thenValue_ == that.thenValue_ &&
|
||||
elseValue_ == that.elseValue_;
|
||||
}
|
||||
|
||||
template <int KIND>
|
||||
bool LogicalOperation<KIND>::operator==(const LogicalOperation &that) const {
|
||||
return logicalOperator == that.logicalOperator && Base::operator==(that);
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ Expr<ImpliedDoIndex::Result> FoldOperation(
|
|||
template <typename T>
|
||||
Expr<T> FoldOperation(FoldingContext &, ArrayConstructor<T> &&);
|
||||
Expr<SomeDerived> FoldOperation(FoldingContext &, StructureConstructor &&);
|
||||
template <typename T>
|
||||
Expr<T> FoldOperation(FoldingContext &, ConditionalExpr<T> &&);
|
||||
|
||||
template <typename T>
|
||||
std::optional<Constant<T>> Folder<T>::GetNamedConstant(const Symbol &symbol0) {
|
||||
|
|
@ -2211,6 +2213,17 @@ Expr<T> FoldOperation(FoldingContext &context, RealToIntPower<T> &&x) {
|
|||
x.right().u);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Expr<T> FoldOperation(FoldingContext &context, ConditionalExpr<T> &&x) {
|
||||
x.condition() = Fold(context, std::move(x.condition()));
|
||||
// If the condition is a scalar logical constant, select the branch.
|
||||
if (auto cst{GetScalarConstantValue<LogicalResult>(x.condition())}) {
|
||||
return cst->IsTrue() ? Fold(context, std::move(x.thenValue()))
|
||||
: Fold(context, std::move(x.elseValue()));
|
||||
}
|
||||
return Expr<T>{std::move(x)};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Expr<T> FoldOperation(FoldingContext &context, Extremum<T> &&x) {
|
||||
if (auto array{ApplyElementwise(context, x,
|
||||
|
|
|
|||
|
|
@ -587,6 +587,29 @@ llvm::raw_ostream &ArrayConstructor<SomeDerived>::AsFortran(
|
|||
return o << ']';
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
llvm::raw_ostream &ConditionalExpr<T>::AsFortran(llvm::raw_ostream &o) const {
|
||||
// Iterate over chained else-branches to avoid adding extra parentheses for
|
||||
// chained conditional expressions.
|
||||
o << '(';
|
||||
const ConditionalExpr<T> *node{this};
|
||||
while (true) {
|
||||
node->condition().AsFortran(o);
|
||||
o << " ? ";
|
||||
node->thenValue().AsFortran(o);
|
||||
o << " : ";
|
||||
// Continue chain for nested ConditionalExpr; else emit terminal value.
|
||||
if (const auto *nested{
|
||||
std::get_if<ConditionalExpr<T>>(&node->elseValue().u)}) {
|
||||
node = nested;
|
||||
} else {
|
||||
node->elseValue().AsFortran(o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return o << ')';
|
||||
}
|
||||
|
||||
template <typename RESULT>
|
||||
std::string ExpressionBase<RESULT>::AsFortran() const {
|
||||
std::string buf;
|
||||
|
|
|
|||
|
|
@ -1195,6 +1195,9 @@ struct HasVectorSubscriptHelper
|
|||
bool operator()(const ProcedureRef &) const {
|
||||
return false; // don't descend into function call arguments
|
||||
}
|
||||
template <typename T> bool operator()(const ConditionalExpr<T> &) const {
|
||||
return false; // not a variable designator
|
||||
}
|
||||
};
|
||||
|
||||
bool HasVectorSubscript(const Expr<SomeType> &expr) {
|
||||
|
|
@ -1736,6 +1739,14 @@ struct ArgumentExtractor
|
|||
return {operation::OperationCode(x), {AsSomeExpr(x)}};
|
||||
}
|
||||
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &x) const {
|
||||
// Return the condition and then/else branches as immediate operands;
|
||||
// nested conditionals are not permitted in an OpenMP atomic context.
|
||||
return {Operator::Conditional,
|
||||
{AsSomeExpr(x.condition()), AsSomeExpr(x.thenValue()),
|
||||
AsSomeExpr(x.elseValue())}};
|
||||
}
|
||||
|
||||
template <typename... Rs>
|
||||
Result Combine(Result &&result, Rs &&...results) const {
|
||||
// There shouldn't be any combining needed, since we're stopping the
|
||||
|
|
@ -1773,6 +1784,8 @@ std::string operation::ToString(operation::Operator op) {
|
|||
return "ASSOCIATED";
|
||||
case Operator::Call:
|
||||
return "function-call";
|
||||
case Operator::Conditional:
|
||||
return "conditional";
|
||||
case Operator::Constant:
|
||||
return "constant";
|
||||
case Operator::Convert:
|
||||
|
|
@ -1901,6 +1914,13 @@ struct ConvertCollector
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T> Result operator()(const ConditionalExpr<T> &x) const {
|
||||
// ConvertCollector tracks the typed-value conversion chain (for OMP ATOMIC
|
||||
// validation); the condition is a LOGICAL(4) selector, not a value output,
|
||||
// so only the value branches are collected.
|
||||
return Combine((*this)(x.thenValue()), (*this)(x.elseValue()));
|
||||
}
|
||||
|
||||
template <typename... Rs>
|
||||
Result Combine(Result &&result, Rs &&...results) const {
|
||||
Result v(std::move(result));
|
||||
|
|
|
|||
|
|
@ -927,6 +927,11 @@ public:
|
|||
return builder.createNullConstant(getLoc());
|
||||
}
|
||||
|
||||
template <typename A>
|
||||
ExtValue genval(const Fortran::evaluate::ConditionalExpr<A> &) {
|
||||
fir::emitFatalError(getLoc(), "ConditionalExpr should be lowered to HLFIR");
|
||||
}
|
||||
|
||||
static bool
|
||||
isDerivedTypeWithLenParameters(const Fortran::semantics::Symbol &sym) {
|
||||
if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
|
||||
|
|
@ -5366,6 +5371,11 @@ private:
|
|||
};
|
||||
}
|
||||
|
||||
template <typename A>
|
||||
CC genarr(const Fortran::evaluate::ConditionalExpr<A> &) {
|
||||
fir::emitFatalError(getLoc(), "ConditionalExpr should be lowered to HLFIR");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
CC genarr(const Fortran::evaluate::Constant<T> &x) {
|
||||
if (x.Rank() == 0)
|
||||
|
|
|
|||
|
|
@ -1788,6 +1788,12 @@ private:
|
|||
TODO(getLoc(), "lowering type parameter inquiry to HLFIR");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
hlfir::EntityWithAttributes
|
||||
gen(const Fortran::evaluate::ConditionalExpr<T> &) {
|
||||
TODO(getLoc(), "lowering conditional expression to HLFIR");
|
||||
}
|
||||
|
||||
hlfir::EntityWithAttributes
|
||||
gen(const Fortran::evaluate::DescriptorInquiry &desc) {
|
||||
mlir::Location loc = getLoc();
|
||||
|
|
|
|||
|
|
@ -212,6 +212,14 @@ private:
|
|||
(void)find(op.right());
|
||||
return false;
|
||||
}
|
||||
template <typename T>
|
||||
RT find(const Fortran::evaluate::ConditionalExpr<T> &x) {
|
||||
// Find array bases in condition and values
|
||||
(void)find(x.condition());
|
||||
(void)find(x.thenValue());
|
||||
(void)find(x.elseValue());
|
||||
return {};
|
||||
}
|
||||
RT find(const Fortran::evaluate::Relational<Fortran::evaluate::SomeType> &x) {
|
||||
(void)find(x.u);
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -158,6 +158,11 @@ public:
|
|||
static_cast<unsigned>(TC) + static_cast<unsigned>(KIND) +
|
||||
static_cast<unsigned>(x.ordering) * 7u;
|
||||
}
|
||||
template <typename T>
|
||||
static unsigned getHashValue(const Fortran::evaluate::ConditionalExpr<T> &x) {
|
||||
return getHashValue(x.condition()) * 151u -
|
||||
getHashValue(x.thenValue()) * 3u + getHashValue(x.elseValue());
|
||||
}
|
||||
template <Fortran::common::TypeCategory TC, int KIND>
|
||||
static unsigned getHashValue(
|
||||
const Fortran::evaluate::RealToIntPower<Fortran::evaluate::Type<TC, KIND>>
|
||||
|
|
@ -416,6 +421,13 @@ public:
|
|||
const Fortran::evaluate::Extremum<A> &y) {
|
||||
return isBinaryEqual(x, y);
|
||||
}
|
||||
template <typename T>
|
||||
static bool isEqual(const Fortran::evaluate::ConditionalExpr<T> &x,
|
||||
const Fortran::evaluate::ConditionalExpr<T> &y) {
|
||||
return isEqual(x.condition(), y.condition()) &&
|
||||
isEqual(x.thenValue(), y.thenValue()) &&
|
||||
isEqual(x.elseValue(), y.elseValue());
|
||||
}
|
||||
template <typename A>
|
||||
static bool isEqual(const Fortran::evaluate::RealToIntPower<A> &x,
|
||||
const Fortran::evaluate::RealToIntPower<A> &y) {
|
||||
|
|
|
|||
|
|
@ -61,15 +61,50 @@ TYPE_PARSER(parenthesized(
|
|||
TYPE_PARSER(construct<AcImpliedDoControl>(
|
||||
maybe(integerTypeSpec / "::"), loopBounds(scalarIntExpr)))
|
||||
|
||||
// Conditional expression lookahead helper: checks if input starting with '('
|
||||
// contains '?' at nesting level 1. This avoids exponential backtracking when
|
||||
// parsing deeply nested parentheses that are not conditional expressions.
|
||||
struct ConditionalExprLookahead {
|
||||
using resultType = Success;
|
||||
constexpr ConditionalExprLookahead() {}
|
||||
std::optional<Success> Parse(ParseState &state) const {
|
||||
ParseState scan{state};
|
||||
if (!attempt("("_tok).Parse(scan)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
int nestLevel{1};
|
||||
while (!scan.IsAtEnd()) {
|
||||
if (attempt(charLiteralConstant).Parse(scan)) {
|
||||
// Skip character literals; don't check contents.
|
||||
} else if (attempt("("_tok).Parse(scan)) {
|
||||
++nestLevel;
|
||||
} else if (attempt(")"_tok).Parse(scan)) {
|
||||
if (--nestLevel == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (attempt("?"_tok).Parse(scan)) {
|
||||
if (nestLevel == 1) {
|
||||
return {Success{}};
|
||||
}
|
||||
} else {
|
||||
scan.UncheckedAdvance();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
// R1001 primary ->
|
||||
// literal-constant | designator | array-constructor |
|
||||
// structure-constructor | function-reference | type-param-inquiry |
|
||||
// type-param-name | ( expr )
|
||||
// type-param-name | ( expr ) | conditional-expr
|
||||
// type-param-inquiry is parsed as a structure component, except for
|
||||
// substring%KIND/LEN
|
||||
constexpr auto primary{instrumented("primary"_en_US,
|
||||
first(construct<Expr>(indirect(charLiteralConstantSubstring)),
|
||||
construct<Expr>(literalConstant),
|
||||
construct<Expr>(ConditionalExprLookahead{} >>
|
||||
parenthesized(Parser<ConditionalExpr>{})),
|
||||
construct<Expr>(construct<Expr::Parentheses>("(" >>
|
||||
expr / !","_tok / recovery(")"_tok, SkipPastNested<'(', ')'>{}))),
|
||||
construct<Expr>(indirect(functionReference) / !"("_tok / !"%"_tok),
|
||||
|
|
@ -94,6 +129,17 @@ constexpr auto level1Expr{sourced(
|
|||
primary || // must come before define op to resolve .TRUE._8 ambiguity
|
||||
construct<Expr>(construct<Expr::DefinedUnary>(definedOpName, primary)))};
|
||||
|
||||
// F2023 R1002 conditional-expr ->
|
||||
// ( scalar-logical-expr ? expr
|
||||
// [ : scalar-logical-expr ? expr ]...
|
||||
// : expr )
|
||||
// The chained list form is encoded as a right-associative tree: the else-expr
|
||||
// is either a chained conditional-expr (which need not be separately
|
||||
// parenthesized) or a terminal expr.
|
||||
TYPE_PARSER(
|
||||
construct<ConditionalExpr>(scalarLogicalExpr / "?", indirect(expr) / ":",
|
||||
indirect(construct<Expr>(Parser<ConditionalExpr>{}) || expr)))
|
||||
|
||||
// R1004 mult-operand -> level-1-expr [power-op mult-operand]
|
||||
// R1007 power-op -> **
|
||||
// Exponentiation (**) is Fortran's only right-associative binary operation.
|
||||
|
|
|
|||
|
|
@ -900,6 +900,17 @@ public:
|
|||
void Unparse(const Expr::OR &x) { Walk(x.t, ".OR."); }
|
||||
void Unparse(const Expr::EQV &x) { Walk(x.t, ".EQV."); }
|
||||
void Unparse(const Expr::NEQV &x) { Walk(x.t, ".NEQV."); }
|
||||
void Unparse(const ConditionalExpr &x) { // F2023 R1002
|
||||
// Note: chained conditionals produce extra parentheses due to recursive
|
||||
// else-expr unparsing; the result is still valid.
|
||||
Put("( ");
|
||||
Walk(std::get<0>(x.t)); // scalar-logical-expr
|
||||
Put(" ? ");
|
||||
Walk(std::get<1>(x.t)); // then-expr
|
||||
Put(" : ");
|
||||
Walk(std::get<2>(x.t)); // else-expr
|
||||
Put(" )");
|
||||
}
|
||||
void Unparse(const Expr::ComplexConstructor &x) {
|
||||
Put('('), Walk(x.t, ","), Put(')');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,6 +316,10 @@ public:
|
|||
}
|
||||
return anyVector ? false : (*this)(aRef.base());
|
||||
}
|
||||
template <typename T> bool operator()(const evaluate::ConditionalExpr<T> &) {
|
||||
// A conditional expression is not a variable and cannot be definable.
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
evaluate::FoldingContext &foldingContext_;
|
||||
|
|
|
|||
|
|
@ -3878,6 +3878,111 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::PercentLoc &x) {
|
|||
return MakeFunctionRef(loc, ActualArguments{std::move(*arg)});
|
||||
}
|
||||
|
||||
MaybeExpr ExpressionAnalyzer::Analyze(const parser::ConditionalExpr &x) {
|
||||
// Chained else-expressions recurse automatically through Analyze(Expr).
|
||||
MaybeExpr condExpr{Analyze(std::get<0>(x.t))};
|
||||
MaybeExpr thenExpr{Analyze(std::get<1>(x.t).value())};
|
||||
MaybeExpr elseExpr{Analyze(std::get<2>(x.t).value())};
|
||||
if (!condExpr || !thenExpr || !elseExpr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (std::holds_alternative<BOZLiteralConstant>(thenExpr->u) ||
|
||||
std::holds_alternative<BOZLiteralConstant>(elseExpr->u)) {
|
||||
Say("BOZ literal constant in conditional expression must have explicit "
|
||||
"type (e.g., INT(z'FF'), REAL(z'3F800000'))"_err_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (IsNullPointerOrAllocatable(&*thenExpr) ||
|
||||
IsNullPointerOrAllocatable(&*elseExpr)) {
|
||||
Say("NULL() not allowed in a conditional expression"_err_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (semantics::IsAssumedRank(*thenExpr) ||
|
||||
semantics::IsAssumedRank(*elseExpr)) {
|
||||
Say("An assumed-rank dummy argument may not be used as a value in a conditional expression"_err_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
if ((ExtractDataRef(thenExpr) &&
|
||||
ExtractCoarrayRef(*ExtractDataRef(thenExpr))) ||
|
||||
(ExtractDataRef(elseExpr) &&
|
||||
ExtractCoarrayRef(*ExtractDataRef(elseExpr)))) {
|
||||
Say("Conditional expression values may not be coindexed"_err_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
// F2023 C1004: then-expr and else-expr must have the same declared type,
|
||||
// kind type parameters, and rank.
|
||||
if (thenExpr->Rank() != elseExpr->Rank()) {
|
||||
Say("All values in conditional expression must have the same rank; have rank %d and %d"_err_en_US,
|
||||
thenExpr->Rank(), elseExpr->Rank());
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::optional<DynamicType> thenType{thenExpr->GetType()};
|
||||
const std::optional<DynamicType> elseType{elseExpr->GetType()};
|
||||
if (!thenType || !elseType) {
|
||||
Say("Cannot determine type of conditional expression"_err_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
const TypeCategory thenCat{thenType->category()};
|
||||
const TypeCategory elseCat{elseType->category()};
|
||||
if (thenCat != elseCat ||
|
||||
(thenCat != TypeCategory::Derived &&
|
||||
thenType->kind() != elseType->kind())) {
|
||||
Say("All values in conditional expression must have the same type and kind; have %s and %s"_err_en_US,
|
||||
thenType->AsFortran(), elseType->AsFortran());
|
||||
return std::nullopt;
|
||||
}
|
||||
if (thenCat == TypeCategory::Derived &&
|
||||
(thenType->IsPolymorphic() || elseType->IsPolymorphic())) {
|
||||
Say("Conditional expressions with polymorphic types (CLASS) are not yet supported"_todo_en_US);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (thenCat == TypeCategory::Derived &&
|
||||
!AreSameDerivedType(
|
||||
thenType->GetDerivedTypeSpec(), elseType->GetDerivedTypeSpec())) {
|
||||
Say("All values in conditional expression must be the same derived type; have %s and %s"_err_en_US,
|
||||
thenType->AsFortran(), elseType->AsFortran());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Dispatch on the else-expr to recover the concrete kind type T.
|
||||
return common::visit(
|
||||
common::visitors{
|
||||
[&](Expr<SomeDerived> &&elseVal) -> MaybeExpr {
|
||||
Expr<LogicalResult> cond{ConvertToType<LogicalResult>(
|
||||
std::move(std::get<Expr<SomeLogical>>(condExpr->u)))};
|
||||
Expr<SomeDerived> thenVal{
|
||||
std::move(std::get<Expr<SomeDerived>>(thenExpr->u))};
|
||||
return AsGenericExpr(
|
||||
Expr<SomeDerived>{evaluate::ConditionalExpr<SomeDerived>{
|
||||
std::move(cond), std::move(thenVal), std::move(elseVal)}});
|
||||
},
|
||||
[&](auto &&elseCatExpr) -> MaybeExpr {
|
||||
using CategoryType = std::decay_t<decltype(elseCatExpr)>;
|
||||
if constexpr (std::is_same_v<CategoryType, BOZLiteralConstant> ||
|
||||
std::is_same_v<CategoryType, NullPointer> ||
|
||||
std::is_same_v<CategoryType, ProcedureDesignator> ||
|
||||
std::is_same_v<CategoryType, ProcedureRef>) {
|
||||
DIE("Invalid expression type in conditional expression");
|
||||
} else {
|
||||
return common::visit(
|
||||
[&](auto &&elseKindExpr) -> MaybeExpr {
|
||||
using T =
|
||||
typename std::decay_t<decltype(elseKindExpr)>::Result;
|
||||
Expr<LogicalResult> cond{ConvertToType<LogicalResult>(
|
||||
std::move(std::get<Expr<SomeLogical>>(condExpr->u)))};
|
||||
Expr<T> thenVal{std::move(std::get<Expr<T>>(
|
||||
std::get<CategoryType>(thenExpr->u).u))};
|
||||
return AsGenericExpr(CategoryType{
|
||||
Expr<T>{evaluate::ConditionalExpr<T>{std::move(cond),
|
||||
std::move(thenVal), std::move(elseKindExpr)}}});
|
||||
},
|
||||
elseCatExpr.u);
|
||||
}
|
||||
},
|
||||
},
|
||||
std::move(elseExpr->u));
|
||||
}
|
||||
|
||||
MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::DefinedUnary &x) {
|
||||
const auto &name{std::get<parser::DefinedOpName>(x.t).v};
|
||||
ArgumentAnalyzer analyzer{*this, name.source};
|
||||
|
|
@ -5142,6 +5247,12 @@ std::optional<ActualArgument> ArgumentAnalyzer::AnalyzeExpr(
|
|||
}
|
||||
context_.SayAt(expr.source,
|
||||
"TYPE(*) dummy argument may only be used as an actual argument"_err_en_US);
|
||||
} else if (isProcedureCall_ &&
|
||||
std::holds_alternative<parser::ConditionalExpr>(expr.u)) {
|
||||
// Check parse tree before analysis to avoid wasted work
|
||||
context_.SayAt(expr.source,
|
||||
"Conditional expressions are not yet supported as actual arguments"_todo_en_US);
|
||||
return std::nullopt;
|
||||
} else if (MaybeExpr argExpr{AnalyzeExprOrWholeAssumedSizeArray(expr)}) {
|
||||
if (isProcedureCall_ || !IsProcedureDesignator(*argExpr)) {
|
||||
// Pad Hollerith actual argument with spaces up to a multiple of 8
|
||||
|
|
|
|||
|
|
@ -329,6 +329,12 @@ struct LogicalConstantVistor : public evaluate::Traverse<LogicalConstantVistor,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result operator()(const evaluate::ConditionalExpr<T> &) const {
|
||||
// A conditional expression is not treated as a constant logical value.
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
|
|
|||
42
flang/test/Evaluate/fold-conditional-expr.f90
Normal file
42
flang/test/Evaluate/fold-conditional-expr.f90
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
! RUN: %python %S/test_folding.py %s %flang_fc1
|
||||
! Tests folding of conditional expressions (Fortran 2023)
|
||||
module m
|
||||
! Basic scalar folding: constant condition selects the chosen branch.
|
||||
logical, parameter :: test_true_int = (.true. ? 1 : 2) == 1
|
||||
logical, parameter :: test_false_int = (.false. ? 1 : 2) == 2
|
||||
logical, parameter :: test_true_real = (.true. ? 1.0 : 2.0) == 1.0
|
||||
logical, parameter :: test_false_real = (.false. ? 1.0 : 2.0) == 2.0
|
||||
logical, parameter :: test_true_logical = (.true. ? .true. : .false.)
|
||||
logical, parameter :: test_false_logical = (.false. ? .false. : .true.)
|
||||
|
||||
! Multi-branch: right-skewed tree folds correctly.
|
||||
! (.true. ? 10 : .false. ? 20 : 30) == 10
|
||||
logical, parameter :: test_multi_first = (.true. ? 10 : .false. ? 20 : 30) == 10
|
||||
! (.false. ? 10 : .true. ? 20 : 30) == 20
|
||||
logical, parameter :: test_multi_second = (.false. ? 10 : .true. ? 20 : 30) == 20
|
||||
! (.false. ? 10 : .false. ? 20 : 30) == 30
|
||||
logical, parameter :: test_multi_third = (.false. ? 10 : .false. ? 20 : 30) == 30
|
||||
|
||||
! Named constant expressions in branches are folded.
|
||||
integer, parameter :: x = 5
|
||||
logical, parameter :: test_branch_fold = (.true. ? x + 1 : x + 2) == 6
|
||||
|
||||
! Named constant as condition.
|
||||
logical, parameter :: cond = .true.
|
||||
logical, parameter :: test_named_cond = (cond ? 42 : 0) == 42
|
||||
|
||||
! Character: constant condition selects the branch value.
|
||||
logical, parameter :: test_char = (.true. ? 'yes' : 'no') == 'yes'
|
||||
|
||||
! Non-constant branch: only the selected branch need be constant (F2023 10.1.12).
|
||||
integer :: non_const = 99
|
||||
logical, parameter :: test_true_const_else_nonconstant = (.true. ? 10 : non_const) == 10
|
||||
logical, parameter :: test_false_const_then_nonconstant = (.false. ? non_const : 10) == 10
|
||||
|
||||
! Named constant condition with a non-constant branch.
|
||||
logical, parameter :: flag = .true.
|
||||
logical, parameter :: test_named_cond_nonconstant = (flag ? 1 : non_const) == 1
|
||||
logical, parameter :: flag_false = .false.
|
||||
logical, parameter :: test_named_cond_false_nonconstant = (flag_false ? non_const : 1) == 1
|
||||
|
||||
end module
|
||||
286
flang/test/Parser/conditional-expr.f90
Normal file
286
flang/test/Parser/conditional-expr.f90
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
! RUN: %flang_fc1 -fdebug-unparse-no-sema %s 2>&1 | FileCheck %s
|
||||
! RUN: %flang_fc1 -fdebug-dump-parse-tree-no-sema %s 2>&1 | FileCheck %s -check-prefix=TREE
|
||||
|
||||
! Test parsing of conditional expressions (Fortran 2023 R1002)
|
||||
|
||||
! Simple two-branch conditional
|
||||
subroutine simple_conditional(x, y, z)
|
||||
integer :: x, y, z
|
||||
! CHECK-LABEL: simple_conditional
|
||||
! CHECK: z = ( x>5 ? y : 10 )
|
||||
! TREE: ConditionalExpr
|
||||
! TREE-NEXT: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> Designator -> DataRef -> Name = 'y'
|
||||
! TREE: Expr -> LiteralConstant -> IntLiteralConstant = '10'
|
||||
z = (x > 5 ? y : 10)
|
||||
end subroutine
|
||||
|
||||
! Three-branch conditional (multiple conditions)
|
||||
subroutine multi_branch_conditional(x, y, z)
|
||||
integer :: x, y, z
|
||||
! CHECK-LABEL: multi_branch_conditional
|
||||
! CHECK: z = ( x>10 ? 100 : ( y<5 ? 50 : 0 ) )
|
||||
! TREE: ConditionalExpr
|
||||
! TREE-NEXT: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> LiteralConstant -> IntLiteralConstant = '100'
|
||||
! TREE: Expr -> ConditionalExpr
|
||||
! TREE: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> LiteralConstant -> IntLiteralConstant = '50'
|
||||
! TREE: Expr -> LiteralConstant -> IntLiteralConstant = '0'
|
||||
z = (x > 10 ? 100 : y < 5 ? 50 : 0)
|
||||
end subroutine
|
||||
|
||||
! Nested conditionals
|
||||
subroutine nested_conditionals(x, y, w, z, flag1, flag2)
|
||||
integer :: x, y, w, z
|
||||
logical :: flag1, flag2
|
||||
! CHECK-LABEL: nested_conditionals
|
||||
! Nested in value position
|
||||
! CHECK: z = ( flag1 ? ( x>y ? x : y ) : 0 )
|
||||
! TREE: ConditionalExpr
|
||||
! TREE-NEXT: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> ConditionalExpr
|
||||
! TREE: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> Designator -> DataRef -> Name = 'x'
|
||||
! TREE: Expr -> Designator -> DataRef -> Name = 'y'
|
||||
! TREE: Expr -> LiteralConstant -> IntLiteralConstant = '0'
|
||||
z = (flag1 ? (x > y ? x : y) : 0)
|
||||
! Nested in condition
|
||||
! CHECK: z = ( ( x>5 ? flag1 : flag2 ) ? y : 10 )
|
||||
z = ((x > 5 ? flag1 : flag2) ? y : 10)
|
||||
! Multiple nested
|
||||
! CHECK: z = ( x>10 ? ( y>20 ? 1 : 2 ) : ( w>30 ? 3 : 4 ) )
|
||||
z = (x > 10 ? (y > 20 ? 1 : 2) : (w > 30 ? 3 : 4))
|
||||
end subroutine
|
||||
|
||||
! Basic type conditionals
|
||||
subroutine basic_types(x, a, b, c, flag1, str1)
|
||||
integer :: x
|
||||
real :: a, b, c
|
||||
logical :: flag1
|
||||
character(len=10) :: str1
|
||||
! CHECK-LABEL: basic_types
|
||||
! Real type
|
||||
! CHECK: c = ( a>b ? a : b )
|
||||
c = (a > b ? a : b)
|
||||
! Logical type
|
||||
! CHECK: flag1 = ( x>5 ? .TRUE. : .FALSE. )
|
||||
flag1 = (x > 5 ? .true. : .false.)
|
||||
! Character type
|
||||
! CHECK: str1 = ( flag1 ? "HELLO" : "WORLD" )
|
||||
str1 = (flag1 ? "HELLO" : "WORLD")
|
||||
end subroutine
|
||||
|
||||
! Complex expressions in conditions and branches
|
||||
subroutine complex_expressions(x, y, z, flag1)
|
||||
integer :: x, y, z
|
||||
logical :: flag1
|
||||
! CHECK-LABEL: complex_expressions
|
||||
! Complex expressions in branches
|
||||
! CHECK: z = ( x>y ? x*2+1 : y*3-2 )
|
||||
z = (x > y ? x*2+1 : y*3-2)
|
||||
! Complex logical condition
|
||||
! CHECK: z = ( x>5.AND.y<10 ? x+y : x-y )
|
||||
z = (x > 5 .and. y < 10 ? x+y : x-y)
|
||||
! Logical NOT
|
||||
! CHECK: z = ( .NOT.flag1 ? x : y )
|
||||
z = (.not. flag1 ? x : y)
|
||||
! Comparison chains
|
||||
! CHECK: z = ( x>5.AND.x<10 ? x : 0 )
|
||||
z = (x > 5 .and. x < 10 ? x : 0)
|
||||
! Parenthesized expressions in branches
|
||||
! CHECK: z = ( x>5 ? (y+z) : (y-z) )
|
||||
z = (x > 5 ? (y+z) : (y-z))
|
||||
end subroutine
|
||||
|
||||
! Many-branch conditionals
|
||||
subroutine many_branches(x, z)
|
||||
integer :: x, z
|
||||
! CHECK-LABEL: many_branches
|
||||
! Four branches
|
||||
! CHECK: z = ( x>10 ? 100 : ( x>5 ? 50 : ( x>0 ? 10 : 0 ) ) )
|
||||
z = (x > 10 ? 100 : x > 5 ? 50 : x > 0 ? 10 : 0)
|
||||
! Five branches
|
||||
! CHECK: z = ( x>20 ? 1 : ( x>15 ? 2 : ( x>10 ? 3 : ( x>5 ? 4 : 5 ) ) ) )
|
||||
z = (x > 20 ? 1 : x > 15 ? 2 : x > 10 ? 3 : x > 5 ? 4 : 5)
|
||||
end subroutine
|
||||
|
||||
! Conditionals with arrays and functions
|
||||
subroutine arrays_and_functions(x, y, z, arr, flag1)
|
||||
integer :: x, y, z, arr(5)
|
||||
logical :: flag1
|
||||
! CHECK-LABEL: arrays_and_functions
|
||||
! Array element in conditional
|
||||
! CHECK: z = ( arr(1)>arr(2) ? arr(1) : arr(2) )
|
||||
z = (arr(1) > arr(2) ? arr(1) : arr(2))
|
||||
! Function calls in conditional
|
||||
! CHECK: x = ( abs(y)>10 ? abs(y) : y )
|
||||
x = (abs(y) > 10 ? abs(y) : y)
|
||||
! Array constructor elements
|
||||
! CHECK: arr(1:3) = [( flag1 ? x : y ), ( .NOT.flag1 ? x : y ), ( x>y ? x : y )]
|
||||
arr(1:3) = [(flag1 ? x : y), (.not. flag1 ? x : y), (x > y ? x : y)]
|
||||
end subroutine
|
||||
|
||||
! Literals in conditionals
|
||||
subroutine literals(x, z, a, c)
|
||||
integer :: x, z
|
||||
real :: a, c
|
||||
! CHECK-LABEL: literals
|
||||
! Real literals
|
||||
! CHECK: c = ( a>0.0 ? 1.5 : 2.5 )
|
||||
c = (a > 0.0 ? 1.5 : 2.5)
|
||||
! Negative values
|
||||
! CHECK: z = ( x<0 ? -1 : 1 )
|
||||
z = (x < 0 ? -1 : 1)
|
||||
end subroutine
|
||||
|
||||
! Conditional in specification expression context
|
||||
function spec_expr_conditional(n, flag) result(res)
|
||||
integer, intent(in) :: n
|
||||
logical, intent(in) :: flag
|
||||
integer :: res
|
||||
! CHECK-LABEL: spec_expr_conditional
|
||||
! CHECK: res = ( flag ? n*2 : n )
|
||||
res = (flag ? n*2 : n)
|
||||
end function
|
||||
|
||||
! Conditional with different integer kinds
|
||||
subroutine integer_kinds(cond)
|
||||
integer(kind=4) :: i4a, i4b, i4c
|
||||
integer(kind=8) :: i8a, i8b, i8c
|
||||
logical :: cond
|
||||
! CHECK-LABEL: integer_kinds
|
||||
! CHECK: i4c = ( cond ? i4a : i4b )
|
||||
i4c = (cond ? i4a : i4b)
|
||||
! CHECK: i8c = ( cond ? i8a : i8b )
|
||||
i8c = (cond ? i8a : i8b)
|
||||
end subroutine
|
||||
|
||||
! Conditional with different real kinds
|
||||
subroutine real_kinds(cond)
|
||||
real(kind=4) :: r4a, r4b, r4c
|
||||
real(kind=8) :: r8a, r8b, r8c
|
||||
logical :: cond
|
||||
! CHECK-LABEL: real_kinds
|
||||
! CHECK: r4c = ( cond ? r4a : r4b )
|
||||
r4c = (cond ? r4a : r4b)
|
||||
! CHECK: r8c = ( cond ? r8a : r8b )
|
||||
r8c = (cond ? r8a : r8b)
|
||||
end subroutine
|
||||
|
||||
! Conditional in various statement contexts
|
||||
subroutine statement_contexts(flag)
|
||||
integer :: x, y, arr(10)
|
||||
logical :: flag
|
||||
! CHECK-LABEL: statement_contexts
|
||||
! In array constructor
|
||||
! CHECK: arr(1:3) = [1, ( flag ? x : y ), 3]
|
||||
arr(1:3) = [1, (flag ? x : y), 3]
|
||||
! In if statement condition
|
||||
! CHECK: IF (( flag ? x : y )>5) THEN
|
||||
if ((flag ? x : y) > 5) then
|
||||
x = 1
|
||||
end if
|
||||
! In print statement
|
||||
! CHECK: PRINT *, ( flag ? x : y )
|
||||
print *, (flag ? x : y)
|
||||
! In assignment to array element
|
||||
! CHECK: arr(5) = ( flag ? x : y )
|
||||
arr(5) = (flag ? x : y)
|
||||
end subroutine
|
||||
|
||||
! Complex type conditionals
|
||||
subroutine complex_type(flag)
|
||||
complex :: c1, c2, c3
|
||||
complex(kind=8) :: c8a, c8b, c8c
|
||||
logical :: flag
|
||||
! CHECK-LABEL: complex_type
|
||||
! CHECK: c3 = ( flag ? c1 : c2 )
|
||||
c3 = (flag ? c1 : c2)
|
||||
! CHECK: c8c = ( flag ? c8a : c8b )
|
||||
c8c = (flag ? c8a : c8b)
|
||||
! With complex literals
|
||||
! CHECK: c3 = ( flag ? (1.0,2.0) : (3.0,4.0) )
|
||||
c3 = (flag ? (1.0, 2.0) : (3.0, 4.0))
|
||||
end subroutine
|
||||
|
||||
! Array-valued conditionals (F2023 10.1.4)
|
||||
subroutine array_valued(flag)
|
||||
integer :: arr1(5), arr2(5), arr3(5)
|
||||
real :: mat1(3,3), mat2(3,3), mat3(3,3)
|
||||
logical :: flag
|
||||
! CHECK-LABEL: array_valued
|
||||
! Whole array conditional
|
||||
! CHECK: arr3 = ( flag ? arr1 : arr2 )
|
||||
! TREE: ConditionalExpr
|
||||
! TREE-NEXT: Scalar -> Logical -> Expr
|
||||
! TREE: Expr -> Designator -> DataRef -> Name = 'arr1'
|
||||
! TREE: Expr -> Designator -> DataRef -> Name = 'arr2'
|
||||
arr3 = (flag ? arr1 : arr2)
|
||||
! Multidimensional array conditional
|
||||
! CHECK: mat3 = ( flag ? mat1 : mat2 )
|
||||
mat3 = (flag ? mat1 : mat2)
|
||||
! Array section conditional
|
||||
! CHECK: arr3(1:3) = ( flag ? arr1(1:3) : arr2(1:3) )
|
||||
arr3(1:3) = (flag ? arr1(1:3) : arr2(1:3))
|
||||
end subroutine
|
||||
|
||||
! Derived type conditionals
|
||||
subroutine derived_types(flag)
|
||||
type :: point
|
||||
real :: x, y
|
||||
end type
|
||||
type(point) :: p1, p2, p3
|
||||
logical :: flag
|
||||
! CHECK-LABEL: derived_types
|
||||
! CHECK: p3 = ( flag ? p1 : p2 )
|
||||
p3 = (flag ? p1 : p2)
|
||||
end subroutine
|
||||
|
||||
! Character with different lengths
|
||||
subroutine character_lengths(flag)
|
||||
character(len=5) :: short1, short2
|
||||
character(len=10) :: medium1, medium2
|
||||
character(len=20) :: long_result
|
||||
logical :: flag
|
||||
! CHECK-LABEL: character_lengths
|
||||
! Same length characters
|
||||
! CHECK: short1 = ( flag ? "HELLO" : "WORLD" )
|
||||
short1 = (flag ? "HELLO" : "WORLD")
|
||||
! Different length literals (type conformance rules apply)
|
||||
! CHECK: long_result = ( flag ? "SHORT" : "MUCH LONGER STRING" )
|
||||
long_result = (flag ? "SHORT" : "MUCH LONGER STRING")
|
||||
! Mixed variables and literals
|
||||
! CHECK: medium1 = ( flag ? short1 : medium2 )
|
||||
medium1 = (flag ? short1 : medium2)
|
||||
end subroutine
|
||||
|
||||
! Verify that '?' inside string literals and comments does not interfere
|
||||
! with conditional expression parsing.
|
||||
subroutine question_mark_chars(flag, str1, str2, str3)
|
||||
logical :: flag
|
||||
character(len=20) :: str1, str2, str3
|
||||
! CHECK-LABEL: question_mark_chars
|
||||
! CHECK: str1 = "HELLO?"
|
||||
str1 = "HELLO?"
|
||||
! CHECK: str2 = ( flag ? "YES?" : "NO?" )
|
||||
str2 = (flag ? "YES?" : "NO?")
|
||||
! CHECK: str3 = "WHAT? WHY? HOW?"
|
||||
str3 = "WHAT? WHY? HOW?" ! ? in a comment
|
||||
! CHECK: str2 = ( flag ? "MAYBE?" : "NOPE" )
|
||||
str2 = (flag ? "MAYBE?" : "NOPE") ! ? in a trailing comment
|
||||
end subroutine
|
||||
|
||||
! Verify that '(' and ')' inside character literals are handled correctly by
|
||||
! ConditionalExprLookahead.
|
||||
subroutine paren_in_char_literal(c, i)
|
||||
character(*), intent(in) :: c
|
||||
integer, intent(out) :: i
|
||||
! CHECK-LABEL: paren_in_char_literal
|
||||
! CHECK: i = ( c==")" ? 1 : 2 )
|
||||
i = (c == ")" ? 1 : 2)
|
||||
! CHECK: i = ( c=="(" ? 1 : 2 )
|
||||
i = (c == "(" ? 1 : 2)
|
||||
! CHECK: i = ( c=="()" ? 1 : 2 )
|
||||
i = (c == "()" ? 1 : 2)
|
||||
end subroutine
|
||||
429
flang/test/Semantics/conditional-expr.f90
Normal file
429
flang/test/Semantics/conditional-expr.f90
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
! RUN: %python %S/test_errors.py %s %flang_fc1
|
||||
! Test semantic analysis of conditional expressions (Fortran 2023)
|
||||
|
||||
! Valid cases with basic types
|
||||
subroutine valid_basic_types(flag)
|
||||
logical :: flag
|
||||
integer :: i1, i2, i3
|
||||
real :: r1, r2, r3
|
||||
complex :: c1, c2, c3
|
||||
logical :: l1, l2, l3
|
||||
character(len=5) :: ch1, ch2, ch3
|
||||
|
||||
! INTEGER conditionals
|
||||
i3 = (flag ? i1 : i2)
|
||||
|
||||
! REAL conditionals
|
||||
r3 = (flag ? r1 : r2)
|
||||
|
||||
! COMPLEX conditionals
|
||||
c3 = (flag ? c1 : c2)
|
||||
|
||||
! LOGICAL conditionals
|
||||
l3 = (flag ? l1 : l2)
|
||||
|
||||
! CHARACTER conditionals
|
||||
ch3 = (flag ? ch1 : ch2)
|
||||
end subroutine
|
||||
|
||||
! Valid cases with same kind
|
||||
subroutine valid_same_kind(flag)
|
||||
logical :: flag
|
||||
integer(kind=4) :: i4a, i4b, i4c
|
||||
integer(kind=8) :: i8a, i8b, i8c
|
||||
real(kind=4) :: r4a, r4b, r4c
|
||||
real(kind=8) :: r8a, r8b, r8c
|
||||
|
||||
! Same kind - valid
|
||||
i4c = (flag ? i4a : i4b)
|
||||
i8c = (flag ? i8a : i8b)
|
||||
r4c = (flag ? r4a : r4b)
|
||||
r8c = (flag ? r8a : r8b)
|
||||
end subroutine
|
||||
|
||||
! Valid cases with literals
|
||||
subroutine valid_literals(flag)
|
||||
logical :: flag
|
||||
integer :: i
|
||||
real :: r
|
||||
character(len=10) :: ch
|
||||
|
||||
i = (flag ? 10 : 20)
|
||||
r = (flag ? 1.0 : 2.0)
|
||||
ch = (flag ? "HELLO" : "WORLD")
|
||||
end subroutine
|
||||
|
||||
! Valid cases with nested conditionals
|
||||
subroutine valid_nested(flag1, flag2, x, y, z, w)
|
||||
logical :: flag1, flag2
|
||||
integer :: x, y, z, w, result
|
||||
|
||||
! Nested in value position
|
||||
result = (flag1 ? (flag2 ? x : y) : z)
|
||||
|
||||
! Nested in condition (condition is logical)
|
||||
result = ((x > y ? flag1 : flag2) ? w : z)
|
||||
|
||||
! Multi-branch
|
||||
result = (x > 10 ? 100 : x > 5 ? 50 : 0)
|
||||
end subroutine
|
||||
|
||||
! Valid cases with arrays
|
||||
subroutine valid_arrays(flag)
|
||||
logical :: flag
|
||||
integer :: arr1(10), arr2(10), arr3(10)
|
||||
real :: mat1(3,3), mat2(3,3), mat3(3,3)
|
||||
|
||||
! Whole array conditional
|
||||
arr3 = (flag ? arr1 : arr2)
|
||||
|
||||
! Multidimensional arrays
|
||||
mat3 = (flag ? mat1 : mat2)
|
||||
|
||||
! Array sections
|
||||
arr3(1:5) = (flag ? arr1(1:5) : arr2(1:5))
|
||||
end subroutine
|
||||
|
||||
! Valid cases with derived types
|
||||
subroutine valid_derived_types(flag)
|
||||
type :: point
|
||||
real :: x, y
|
||||
end type
|
||||
|
||||
logical :: flag
|
||||
type(point) :: p1, p2, p3
|
||||
|
||||
p3 = (flag ? p1 : p2)
|
||||
end subroutine
|
||||
|
||||
! Valid cases with character lengths
|
||||
subroutine valid_character_lengths(flag)
|
||||
logical :: flag
|
||||
character(len=5) :: short1, short2, short3
|
||||
character(len=10) :: medium
|
||||
character(len=20) :: long
|
||||
|
||||
! Same length
|
||||
short3 = (flag ? short1 : short2)
|
||||
|
||||
! Different lengths - padding/truncation applies
|
||||
medium = (flag ? short1 : medium)
|
||||
long = (flag ? short1 : "A LONGER STRING")
|
||||
end subroutine
|
||||
|
||||
! Valid: deferred-length character scalars
|
||||
subroutine valid_deferred_length_character(flag)
|
||||
logical :: flag
|
||||
character(len=:), allocatable :: str1, str2, result
|
||||
|
||||
str1 = "SHORT"
|
||||
str2 = "A MUCH LONGER STRING"
|
||||
! Result length is determined by selected branch
|
||||
result = (flag ? str1 : str2)
|
||||
end subroutine
|
||||
|
||||
! Valid: assumed-length character arguments
|
||||
subroutine valid_assumed_length_character(flag, str1, str2)
|
||||
logical :: flag
|
||||
character(len=*) :: str1, str2
|
||||
character(len=100) :: result
|
||||
|
||||
result = (flag ? str1 : str2)
|
||||
end subroutine
|
||||
|
||||
! Error: condition must be logical
|
||||
subroutine error_non_logical_condition()
|
||||
integer :: i, x, y
|
||||
real :: r
|
||||
character :: ch
|
||||
|
||||
!ERROR: Must have LOGICAL type, but is INTEGER(4)
|
||||
i = (i ? x : y)
|
||||
|
||||
!ERROR: Must have LOGICAL type, but is REAL(4)
|
||||
i = (r ? x : y)
|
||||
|
||||
!ERROR: Must have LOGICAL type, but is CHARACTER(KIND=1,LEN=1_8)
|
||||
i = (ch ? x : y)
|
||||
end subroutine
|
||||
|
||||
! Error: type mismatch between branches
|
||||
subroutine error_type_mismatch(flag)
|
||||
logical :: flag
|
||||
integer :: i1, i2
|
||||
real :: r
|
||||
character :: ch
|
||||
complex :: c
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have INTEGER(4) and REAL(4)
|
||||
i1 = (flag ? i2 : r)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have INTEGER(4) and CHARACTER(KIND=1,LEN=1_8)
|
||||
i1 = (flag ? i2 : ch)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have REAL(4) and COMPLEX(4)
|
||||
r = (flag ? r : c)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have LOGICAL(4) and INTEGER(4)
|
||||
flag = (flag ? flag : i1)
|
||||
end subroutine
|
||||
|
||||
! Error: kind mismatch (F2023 C1004)
|
||||
subroutine error_kind_mismatch(flag)
|
||||
logical :: flag
|
||||
integer(kind=4) :: i4
|
||||
integer(kind=8) :: i8
|
||||
real(kind=4) :: r4
|
||||
real(kind=8) :: r8
|
||||
complex(kind=4) :: c4
|
||||
complex(kind=8) :: c8
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have INTEGER(4) and INTEGER(8)
|
||||
i4 = (flag ? i4 : i8)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have REAL(4) and REAL(8)
|
||||
r4 = (flag ? r4 : r8)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have COMPLEX(4) and COMPLEX(8)
|
||||
c4 = (flag ? c4 : c8)
|
||||
end subroutine
|
||||
|
||||
! Error: derived type mismatch
|
||||
subroutine error_derived_type_mismatch(flag)
|
||||
type :: type1
|
||||
integer :: i
|
||||
end type
|
||||
|
||||
type :: type2
|
||||
integer :: i
|
||||
end type
|
||||
|
||||
logical :: flag
|
||||
type(type1) :: t1
|
||||
type(type2) :: t2
|
||||
|
||||
!ERROR: All values in conditional expression must be the same derived type; have type1 and type2
|
||||
t1 = (flag ? t1 : t2)
|
||||
end subroutine
|
||||
|
||||
! Error: derived type vs intrinsic type mismatch
|
||||
subroutine error_derived_vs_intrinsic(flag)
|
||||
type :: my_type
|
||||
integer :: i
|
||||
end type
|
||||
|
||||
logical :: flag
|
||||
type(my_type) :: t
|
||||
integer :: i
|
||||
real :: r
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have my_type and INTEGER(4)
|
||||
t = (flag ? t : i)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have INTEGER(4) and my_type
|
||||
t = (flag ? i : t)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have my_type and REAL(4)
|
||||
t = (flag ? t : r)
|
||||
end subroutine
|
||||
|
||||
! Error: array rank mismatch
|
||||
subroutine error_array_rank_mismatch(flag)
|
||||
logical :: flag
|
||||
integer :: arr1(10), mat1(3,3), result(10)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same rank; have rank 1 and 2
|
||||
result = (flag ? arr1 : mat1)
|
||||
end subroutine
|
||||
|
||||
! Error: scalar vs array mismatch
|
||||
subroutine error_scalar_array_mismatch(flag)
|
||||
logical :: flag
|
||||
integer :: scalar, arr(10), result(10)
|
||||
|
||||
!ERROR: All values in conditional expression must have the same rank; have rank 0 and 1
|
||||
result = (flag ? scalar : arr)
|
||||
end subroutine
|
||||
|
||||
! Error: condition must be scalar
|
||||
subroutine error_array_condition()
|
||||
logical :: flags(5)
|
||||
integer :: x(5), y(5), result(5)
|
||||
|
||||
!ERROR: Must be a scalar value, but is a rank-1 array
|
||||
result = (flags ? x : y)
|
||||
end subroutine
|
||||
|
||||
! Valid cases with intrinsic functions
|
||||
subroutine valid_intrinsic_functions(x, y, flag)
|
||||
integer :: x, y
|
||||
logical :: flag
|
||||
integer :: result
|
||||
|
||||
result = (flag ? abs(x) : abs(y))
|
||||
result = (flag ? max(x, y) : min(x, y))
|
||||
end subroutine
|
||||
|
||||
! Valid: conditional in array constructor
|
||||
subroutine valid_in_array_constructor(flag, x, y)
|
||||
logical :: flag
|
||||
integer :: x, y, arr(3)
|
||||
|
||||
arr = [(flag ? x : y), (flag ? x + 1 : y + 1), (flag ? x + 2 : y + 2)]
|
||||
end subroutine
|
||||
|
||||
! Valid: conditional in expression context
|
||||
subroutine valid_in_expression(flag, x, y)
|
||||
logical :: flag
|
||||
integer :: x, y, z
|
||||
|
||||
z = (flag ? x : y) + 10
|
||||
z = 2 * (flag ? x : y)
|
||||
|
||||
if ((flag ? x : y) > 5) then
|
||||
z = 1
|
||||
end if
|
||||
end subroutine
|
||||
|
||||
! Note: allocatable/pointer differences are handled by assignment semantics
|
||||
! The conditional expression just requires matching types
|
||||
|
||||
! Valid: both branches allocatable
|
||||
subroutine valid_both_allocatable(flag)
|
||||
logical :: flag
|
||||
integer, allocatable :: alloc1, alloc2, result
|
||||
|
||||
allocate(result)
|
||||
result = (flag ? alloc1 : alloc2)
|
||||
end subroutine
|
||||
|
||||
! Valid: both branches pointer
|
||||
subroutine valid_both_pointer(flag)
|
||||
logical :: flag
|
||||
integer, pointer :: ptr1, ptr2, result
|
||||
|
||||
result = (flag ? ptr1 : ptr2)
|
||||
end subroutine
|
||||
|
||||
! Valid: elemental context
|
||||
elemental integer function conditional_elemental(flag, x, y)
|
||||
logical, intent(in) :: flag
|
||||
integer, intent(in) :: x, y
|
||||
|
||||
conditional_elemental = (flag ? x : y)
|
||||
end function
|
||||
|
||||
! Valid: pure context
|
||||
pure integer function conditional_pure(flag, x, y)
|
||||
logical, intent(in) :: flag
|
||||
integer, intent(in) :: x, y
|
||||
|
||||
conditional_pure = (flag ? x : y)
|
||||
end function
|
||||
|
||||
! Valid: recursive context
|
||||
recursive integer function conditional_recursive(n, flag, x, y) result(res)
|
||||
integer, intent(in) :: n
|
||||
logical, intent(in) :: flag
|
||||
integer, intent(in) :: x, y
|
||||
|
||||
if (n <= 0) then
|
||||
res = (flag ? x : y)
|
||||
else
|
||||
res = conditional_recursive(n - 1, flag, x, y)
|
||||
end if
|
||||
end function
|
||||
|
||||
! Valid: nested multi-branch
|
||||
subroutine valid_multi_branch(x)
|
||||
integer :: x, result
|
||||
|
||||
! Five-branch conditional
|
||||
result = (x > 20 ? 1 : x > 15 ? 2 : x > 10 ? 3 : x > 5 ? 4 : 5)
|
||||
end subroutine
|
||||
|
||||
! Error: polymorphic types not yet supported
|
||||
subroutine error_polymorphic(flag)
|
||||
type :: base_t
|
||||
integer :: i
|
||||
end type
|
||||
|
||||
logical :: flag
|
||||
class(base_t), allocatable :: poly1, poly2, result
|
||||
|
||||
!ERROR: not yet implemented: Conditional expressions with polymorphic types (CLASS) are not yet supported
|
||||
result = (flag ? poly1 : poly2)
|
||||
end subroutine
|
||||
|
||||
! Error: mismatched character kinds
|
||||
subroutine error_character_kind_mismatch(flag)
|
||||
logical :: flag
|
||||
character(kind=1, len=5) :: ch1
|
||||
character(kind=4, len=5) :: ch4
|
||||
|
||||
!ERROR: All values in conditional expression must have the same type and kind; have CHARACTER(KIND=1,LEN=5_8) and CHARACTER(KIND=4,LEN=5_8)
|
||||
ch1 = (flag ? ch1 : ch4)
|
||||
end subroutine
|
||||
|
||||
! Valid: optional arguments
|
||||
subroutine valid_optional_args(flag, opt_x, opt_y)
|
||||
logical :: flag
|
||||
integer, optional :: opt_x, opt_y
|
||||
integer :: result
|
||||
|
||||
if (present(opt_x) .and. present(opt_y)) then
|
||||
result = (flag ? opt_x : opt_y)
|
||||
end if
|
||||
end subroutine
|
||||
|
||||
! Valid: mix of expressions and designators
|
||||
subroutine valid_mixed_expressions(flag, x, y)
|
||||
logical :: flag
|
||||
integer :: x, y, result
|
||||
|
||||
result = (flag ? x + y : x - y)
|
||||
result = (flag ? 2 * x : y / 2)
|
||||
end subroutine
|
||||
|
||||
! Constant-folding: when the condition is a constant, only the selected
|
||||
! branch must be a constant expression (F2023 10.1.12).
|
||||
subroutine constant_folding_cases()
|
||||
integer :: non_const = 99
|
||||
|
||||
! Valid: .true. selects 10; non_const is in the unselected else-branch.
|
||||
integer, parameter :: p_true_const = (.true. ? 10 : non_const)
|
||||
|
||||
! Valid: .false. selects 10; non_const is in the unselected then-branch.
|
||||
integer, parameter :: p_false_const = (.false. ? non_const : 10)
|
||||
|
||||
! Error: .false. selects non_const — not a constant expression.
|
||||
!ERROR: Must be a constant value
|
||||
integer, parameter :: p_false_nconst = (.false. ? 10 : non_const)
|
||||
|
||||
! Error: .true. selects non_const — not a constant expression.
|
||||
!ERROR: Must be a constant value
|
||||
integer, parameter :: p_true_nconst = (.true. ? non_const : 10)
|
||||
end subroutine
|
||||
|
||||
! Module serialization: conditional expressions in a module must be correctly
|
||||
! written to and read back from the .mod file.
|
||||
module conditional_expr_mod
|
||||
implicit none
|
||||
contains
|
||||
subroutine mod_mixed_expressions(flag, x, y, result)
|
||||
logical, intent(in) :: flag
|
||||
integer, intent(in) :: x, y
|
||||
integer, intent(out) :: result
|
||||
|
||||
result = (flag ? x + y : x - y)
|
||||
result = (flag ? 2 * x : y / 2)
|
||||
end subroutine
|
||||
end module
|
||||
|
||||
subroutine valid_use_from_module(flag, x, y)
|
||||
use conditional_expr_mod
|
||||
logical :: flag
|
||||
integer :: x, y, result
|
||||
|
||||
call mod_mixed_expressions(flag, x, y, result)
|
||||
end subroutine
|
||||
Loading…
Reference in a new issue