Skip to content

Commit 802c5cb

Browse files
authored
[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 (llvm/llvm-test-suite#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.
1 parent 46dc97c commit 802c5cb

28 files changed

+1178
-14
lines changed

flang/examples/FeatureList/FeatureList.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ struct NodeVisitor {
311311
READ_FEATURE(Expr::NEQV)
312312
READ_FEATURE(Expr::DefinedBinary)
313313
READ_FEATURE(Expr::ComplexConstructor)
314+
READ_FEATURE(ConditionalExpr)
314315
READ_FEATURE(External)
315316
READ_FEATURE(ExternalStmt)
316317
READ_FEATURE(FailImageStmt)

flang/include/flang/Evaluate/expression.h

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,34 @@ struct LogicalOperation
390390
LogicalOperator logicalOperator;
391391
};
392392

393+
// Fortran 2023 conditional expression: (cond ? val : cond ? val : ... : else)
394+
// All branches have the same type and rank (verified during semantic analysis).
395+
template <typename T> class ConditionalExpr {
396+
public:
397+
using Result = T;
398+
CLASS_BOILERPLATE(ConditionalExpr)
399+
ConditionalExpr(Expr<LogicalResult> &&cond, Expr<Result> &&thenVal,
400+
Expr<Result> &&elseVal)
401+
: condition_{std::move(cond)}, thenValue_{std::move(thenVal)},
402+
elseValue_{std::move(elseVal)} {}
403+
bool operator==(const ConditionalExpr &) const;
404+
Expr<LogicalResult> &condition() { return condition_.value(); }
405+
const Expr<LogicalResult> &condition() const { return condition_.value(); }
406+
Expr<Result> &thenValue() { return thenValue_.value(); }
407+
const Expr<Result> &thenValue() const { return thenValue_.value(); }
408+
Expr<Result> &elseValue() { return elseValue_.value(); }
409+
const Expr<Result> &elseValue() const { return elseValue_.value(); }
410+
int Rank() const { return thenValue().Rank(); }
411+
std::optional<DynamicType> GetType() const { return thenValue().GetType(); }
412+
static constexpr int Corank() { return 0; }
413+
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
414+
415+
private:
416+
common::CopyableIndirection<Expr<LogicalResult>> condition_;
417+
common::CopyableIndirection<Expr<Result>> thenValue_;
418+
common::CopyableIndirection<Expr<Result>> elseValue_;
419+
};
420+
393421
// Array constructors
394422
template <typename RESULT> class ArrayConstructorValues;
395423

@@ -536,7 +564,7 @@ class Expr<Type<TypeCategory::Integer, KIND>>
536564
Convert<Result, TypeCategory::Unsigned>>;
537565
using Operations = std::tuple<Parentheses<Result>, Negate<Result>,
538566
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
539-
Power<Result>, Extremum<Result>>;
567+
Power<Result>, Extremum<Result>, ConditionalExpr<Result>>;
540568
using Indices = std::conditional_t<KIND == ImpliedDoIndex::Result::kind,
541569
std::tuple<ImpliedDoIndex>, std::tuple<>>;
542570
using TypeParamInquiries =
@@ -568,7 +596,7 @@ class Expr<Type<TypeCategory::Unsigned, KIND>>
568596
Convert<Result, TypeCategory::Unsigned>>;
569597
using Operations = std::tuple<Parentheses<Result>, Negate<Result>,
570598
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
571-
Power<Result>, Extremum<Result>>;
599+
Power<Result>, Extremum<Result>, ConditionalExpr<Result>>;
572600
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
573601
Designator<Result>, FunctionRef<Result>>;
574602

@@ -594,7 +622,8 @@ class Expr<Type<TypeCategory::Real, KIND>>
594622
Convert<Result, TypeCategory::Unsigned>>;
595623
using Operations = std::variant<ComplexComponent<KIND>, Parentheses<Result>,
596624
Negate<Result>, Add<Result>, Subtract<Result>, Multiply<Result>,
597-
Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>>;
625+
Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>,
626+
ConditionalExpr<Result>>;
598627
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
599628
Designator<Result>, FunctionRef<Result>>;
600629

@@ -612,7 +641,7 @@ class Expr<Type<TypeCategory::Complex, KIND>>
612641
using Operations = std::variant<Parentheses<Result>, Negate<Result>,
613642
Convert<Result, TypeCategory::Complex>, Add<Result>, Subtract<Result>,
614643
Multiply<Result>, Divide<Result>, Power<Result>, RealToIntPower<Result>,
615-
ComplexConstructor<KIND>>;
644+
ComplexConstructor<KIND>, ConditionalExpr<Result>>;
616645
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
617646
Designator<Result>, FunctionRef<Result>>;
618647

@@ -638,7 +667,7 @@ class Expr<Type<TypeCategory::Character, KIND>>
638667

639668
std::variant<Constant<Result>, ArrayConstructor<Result>, Designator<Result>,
640669
FunctionRef<Result>, Parentheses<Result>, Convert<Result>, Concat<KIND>,
641-
Extremum<Result>, SetLength<KIND>>
670+
Extremum<Result>, SetLength<KIND>, ConditionalExpr<Result>>
642671
u;
643672
};
644673

@@ -710,7 +739,7 @@ class Expr<Type<TypeCategory::Logical, KIND>>
710739

711740
private:
712741
using Operations = std::tuple<Convert<Result>, Parentheses<Result>, Not<KIND>,
713-
LogicalOperation<KIND>>;
742+
LogicalOperation<KIND>, ConditionalExpr<Result>>;
714743
using Relations = std::conditional_t<KIND == LogicalResult::kind,
715744
std::tuple<Relational<SomeType>>, std::tuple<>>;
716745
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
@@ -788,7 +817,8 @@ template <> class Expr<SomeDerived> : public ExpressionBase<SomeDerived> {
788817
using Result = SomeDerived;
789818
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
790819
std::variant<Constant<Result>, ArrayConstructor<Result>, StructureConstructor,
791-
Designator<Result>, FunctionRef<Result>, Parentheses<Result>>
820+
Designator<Result>, FunctionRef<Result>, Parentheses<Result>,
821+
ConditionalExpr<Result>>
792822
u;
793823
};
794824

@@ -929,6 +959,7 @@ FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructor, )
929959
template class Relational<SomeType>; \
930960
FOR_EACH_TYPE_AND_KIND(template class ExpressionBase, ) \
931961
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructorValues, ) \
932-
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructor, )
962+
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructor, ) \
963+
FOR_EACH_INTRINSIC_KIND(template class ConditionalExpr, )
933964
} // namespace Fortran::evaluate
934965
#endif // FORTRAN_EVALUATE_EXPRESSION_H_

flang/include/flang/Evaluate/fold.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ std::optional<std::int64_t> ToInt64(const Expr<SomeUnsigned> &);
105105
std::optional<std::int64_t> ToInt64(const Expr<SomeType> &);
106106
std::optional<std::int64_t> ToInt64(const ActualArgument &);
107107

108+
// When an expression is a constant logical scalar, ToLogical() extracts its
109+
// value.
110+
inline std::optional<bool> ToLogical(const Expr<LogicalResult> &expr) {
111+
if (auto val{GetScalarConstantValue<LogicalResult>(expr)}) {
112+
return val->IsTrue();
113+
}
114+
return std::nullopt;
115+
}
116+
108117
template <typename A>
109118
std::optional<std::int64_t> ToInt64(const std::optional<A> &x) {
110119
if (x) {

flang/include/flang/Evaluate/shape.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ class GetShapeHelper
189189
Result operator()(const ArrayConstructor<T> &aconst) const {
190190
return Shape{GetArrayConstructorExtent(aconst)};
191191
}
192+
template <typename T>
193+
Result operator()(const ConditionalExpr<T> &conditional) const {
194+
// Per F2023 10.1.4(7), the shape is that of the selected branch.
195+
// When all branches have identical static extents, return the common shape.
196+
int rank{conditional.thenValue().Rank()};
197+
Result thenShape{(*this)(conditional.thenValue())};
198+
if (!thenShape) {
199+
return Shape(rank, std::nullopt);
200+
}
201+
Result elseShape{(*this)(conditional.elseValue())};
202+
if (thenShape != elseShape) {
203+
return Shape(rank, std::nullopt);
204+
}
205+
return thenShape;
206+
}
192207
template <typename D, typename R, typename LO, typename RO>
193208
Result operator()(const Operation<D, R, LO, RO> &operation) const {
194209
if (int rr{operation.right().Rank()}; rr > 0) {

flang/include/flang/Evaluate/tools.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ struct IsVariableHelper
5050
Result operator()(const CoarrayRef &) const { return true; }
5151
Result operator()(const ComplexPart &) const { return true; }
5252
Result operator()(const ProcedureDesignator &) const;
53+
template <typename T> Result operator()(const ConditionalExpr<T> &) const {
54+
return false;
55+
}
5356
template <typename T> Result operator()(const Expr<T> &x) const {
5457
if constexpr (common::HasMember<T, AllIntrinsicTypes> ||
5558
std::is_same_v<T, SomeDerived>) {
@@ -1410,6 +1413,7 @@ enum class Operator {
14101413
Call,
14111414
Constant,
14121415
Convert,
1416+
Conditional,
14131417
Div,
14141418
Eq,
14151419
Eqv,

flang/include/flang/Evaluate/traverse.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ class Traverse {
224224
Result operator()(const StructureConstructor &x) const {
225225
return visitor_.Combine(visitor_(x.derivedTypeSpec()), CombineContents(x));
226226
}
227+
// Conditional expressions (Fortran 2023)
228+
template <typename T> Result operator()(const ConditionalExpr<T> &x) const {
229+
return Combine(x.condition(), x.thenValue(), x.elseValue());
230+
}
227231

228232
// Operations and wrappers
229233
// Have a single operator() for all Operations.

flang/include/flang/Parser/characters.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ inline constexpr bool IsValidFortranTokenCharacter(char ch) {
170170
case '<':
171171
case '=':
172172
case '>':
173+
case '?': // Used in conditional expressions (Fortran 2023)
173174
case '[':
174175
case ']':
175176
case '{': // Used in OpenMP context selector specification

flang/include/flang/Parser/dump-parse-tree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ class ParseTreeDumper {
252252
NODE(parser, ComputedGotoStmt)
253253
NODE(parser, ConcurrentControl)
254254
NODE(parser, ConcurrentHeader)
255+
NODE(parser, ConditionalExpr)
255256
NODE(parser, ConnectSpec)
256257
NODE(ConnectSpec, CharExpr)
257258
NODE_ENUM(ConnectSpec::CharExpr, Kind)

flang/include/flang/Parser/parse-tree.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,6 +1678,17 @@ struct ImageSelector {
16781678
std::tuple<std::list<Cosubscript>, std::list<ImageSelectorSpec>> t;
16791679
};
16801680

1681+
// F2023 R1002 conditional-expr ->
1682+
// ( scalar-logical-expr ? expr
1683+
// [ : scalar-logical-expr ? expr ]...
1684+
// : expr )
1685+
struct ConditionalExpr {
1686+
TUPLE_CLASS_BOILERPLATE(ConditionalExpr);
1687+
std::tuple<ScalarLogicalExpr, common::Indirection<Expr>,
1688+
common::Indirection<Expr>>
1689+
t;
1690+
};
1691+
16811692
// R1001 - R1022 expressions
16821693
struct Expr {
16831694
UNION_CLASS_BOILERPLATE(Expr);
@@ -1776,11 +1787,12 @@ struct Expr {
17761787
CharBlock source;
17771788

17781789
std::variant<common::Indirection<CharLiteralConstantSubstring>,
1779-
LiteralConstant, common::Indirection<Designator>, ArrayConstructor,
1780-
StructureConstructor, common::Indirection<FunctionReference>, Parentheses,
1781-
UnaryPlus, Negate, NOT, PercentLoc, DefinedUnary, Power, Multiply, Divide,
1782-
Add, Subtract, Concat, LT, LE, EQ, NE, GE, GT, AND, OR, EQV, NEQV,
1783-
DefinedBinary, ComplexConstructor, common::Indirection<SubstringInquiry>>
1790+
LiteralConstant, ConditionalExpr, common::Indirection<Designator>,
1791+
ArrayConstructor, StructureConstructor,
1792+
common::Indirection<FunctionReference>, Parentheses, UnaryPlus, Negate,
1793+
NOT, PercentLoc, DefinedUnary, Power, Multiply, Divide, Add, Subtract,
1794+
Concat, LT, LE, EQ, NE, GE, GT, AND, OR, EQV, NEQV, DefinedBinary,
1795+
ComplexConstructor, common::Indirection<SubstringInquiry>>
17841796
u;
17851797
};
17861798

flang/include/flang/Semantics/dump-expr.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,19 @@ class DumpEvaluateExpr {
201201
Show(op.right());
202202
Outdent();
203203
}
204+
template <typename T> void Show(const evaluate::ConditionalExpr<T> &x) {
205+
Indent("conditional expr "s + std::string(TypeOf<T>::name));
206+
Indent("condition");
207+
Show(x.condition());
208+
Outdent();
209+
Indent("then");
210+
Show(x.thenValue());
211+
Outdent();
212+
Indent("else");
213+
Show(x.elseValue());
214+
Outdent();
215+
Outdent();
216+
}
204217
void Show(const evaluate::Relational<evaluate::SomeType> &x);
205218
template <typename T> void Show(const evaluate::Expr<T> &x) {
206219
Indent("expr <"s + std::string(TypeOf<T>::name) + ">"s);

0 commit comments

Comments
 (0)