[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:
Caroline Newcombe 2026-04-08 11:27:12 -05:00 committed by GitHub
parent 46dc97c0ea
commit 802c5cbe5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1178 additions and 14 deletions

View file

@ -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)

View file

@ -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_

View file

@ -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) {

View file

@ -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) {

View file

@ -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,

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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;
};

View file

@ -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);

View file

@ -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());

View file

@ -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)}) {

View file

@ -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);

View file

@ -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,

View file

@ -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;

View file

@ -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));

View file

@ -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)

View file

@ -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();

View file

@ -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 {};

View file

@ -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) {

View file

@ -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.

View file

@ -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(')');
}

View file

@ -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_;

View file

@ -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

View file

@ -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

View 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

View 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

View 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