[flang-commits] [flang] 802c5cb - [flang] Implement conditional expressions parser/semantics (F2023) (#186489)
via flang-commits
flang-commits at lists.llvm.org
Wed Apr 8 09:27:19 PDT 2026
Author: Caroline Newcombe
Date: 2026-04-08T11:27:12-05:00
New Revision: 802c5cbe5b4be7eb7b442ddbd76acb781921e5f7
URL: https://github.com/llvm/llvm-project/commit/802c5cbe5b4be7eb7b442ddbd76acb781921e5f7
DIFF: https://github.com/llvm/llvm-project/commit/802c5cbe5b4be7eb7b442ddbd76acb781921e5f7.diff
LOG: [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.
Added:
flang/test/Evaluate/fold-conditional-expr.f90
flang/test/Parser/conditional-expr.f90
flang/test/Semantics/conditional-expr.f90
Modified:
flang/examples/FeatureList/FeatureList.cpp
flang/include/flang/Evaluate/expression.h
flang/include/flang/Evaluate/fold.h
flang/include/flang/Evaluate/shape.h
flang/include/flang/Evaluate/tools.h
flang/include/flang/Evaluate/traverse.h
flang/include/flang/Parser/characters.h
flang/include/flang/Parser/dump-parse-tree.h
flang/include/flang/Parser/parse-tree.h
flang/include/flang/Semantics/dump-expr.h
flang/include/flang/Semantics/expression.h
flang/lib/Evaluate/check-expression.cpp
flang/lib/Evaluate/expression.cpp
flang/lib/Evaluate/fold-implementation.h
flang/lib/Evaluate/formatting.cpp
flang/lib/Evaluate/tools.cpp
flang/lib/Lower/ConvertExpr.cpp
flang/lib/Lower/ConvertExprToHLFIR.cpp
flang/lib/Lower/IterationSpace.cpp
flang/lib/Lower/Support/Utils.cpp
flang/lib/Parser/expr-parsers.cpp
flang/lib/Parser/unparse.cpp
flang/lib/Semantics/definable.cpp
flang/lib/Semantics/expression.cpp
flang/lib/Semantics/openmp-utils.cpp
Removed:
################################################################################
diff --git a/flang/examples/FeatureList/FeatureList.cpp b/flang/examples/FeatureList/FeatureList.cpp
index 355d79a04e4ba..bee18096f9fb2 100644
--- a/flang/examples/FeatureList/FeatureList.cpp
+++ b/flang/examples/FeatureList/FeatureList.cpp
@@ -311,6 +311,7 @@ struct NodeVisitor {
READ_FEATURE(Expr::NEQV)
READ_FEATURE(Expr::DefinedBinary)
READ_FEATURE(Expr::ComplexConstructor)
+ READ_FEATURE(ConditionalExpr)
READ_FEATURE(External)
READ_FEATURE(ExternalStmt)
READ_FEATURE(FailImageStmt)
diff --git a/flang/include/flang/Evaluate/expression.h b/flang/include/flang/Evaluate/expression.h
index f7a1f9b955181..c8570e4a52e78 100644
--- a/flang/include/flang/Evaluate/expression.h
+++ b/flang/include/flang/Evaluate/expression.h
@@ -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 @@ class Expr<Type<TypeCategory::Integer, KIND>>
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 @@ class Expr<Type<TypeCategory::Unsigned, KIND>>
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 @@ class Expr<Type<TypeCategory::Real, KIND>>
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 @@ class Expr<Type<TypeCategory::Complex, KIND>>
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 @@ class Expr<Type<TypeCategory::Character, KIND>>
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 @@ class Expr<Type<TypeCategory::Logical, KIND>>
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 @@ template <> class Expr<SomeDerived> : public ExpressionBase<SomeDerived> {
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_
diff --git a/flang/include/flang/Evaluate/fold.h b/flang/include/flang/Evaluate/fold.h
index b21c0f311fd35..df43489aa679c 100644
--- a/flang/include/flang/Evaluate/fold.h
+++ b/flang/include/flang/Evaluate/fold.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) {
diff --git a/flang/include/flang/Evaluate/shape.h b/flang/include/flang/Evaluate/shape.h
index f0505cfcdf2d7..e5c2d6e8cb63d 100644
--- a/flang/include/flang/Evaluate/shape.h
+++ b/flang/include/flang/Evaluate/shape.h
@@ -189,6 +189,21 @@ class GetShapeHelper
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) {
diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 51dc0582fcdea..cab6ddbb6b27c 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -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,
diff --git a/flang/include/flang/Evaluate/traverse.h b/flang/include/flang/Evaluate/traverse.h
index d63c16f93230a..44cfaa2a7073d 100644
--- a/flang/include/flang/Evaluate/traverse.h
+++ b/flang/include/flang/Evaluate/traverse.h
@@ -224,6 +224,10 @@ class Traverse {
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.
diff --git a/flang/include/flang/Parser/characters.h b/flang/include/flang/Parser/characters.h
index 3761700ad348c..620c6b357f948 100644
--- a/flang/include/flang/Parser/characters.h
+++ b/flang/include/flang/Parser/characters.h
@@ -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
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 84c7b8d2a5349..eefab487413da 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -252,6 +252,7 @@ class ParseTreeDumper {
NODE(parser, ComputedGotoStmt)
NODE(parser, ConcurrentControl)
NODE(parser, ConcurrentHeader)
+ NODE(parser, ConditionalExpr)
NODE(parser, ConnectSpec)
NODE(ConnectSpec, CharExpr)
NODE_ENUM(ConnectSpec::CharExpr, Kind)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index a0106cac84620..960ebbcc99efb 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -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;
};
diff --git a/flang/include/flang/Semantics/dump-expr.h b/flang/include/flang/Semantics/dump-expr.h
index 8cbb78b585f4a..d79a294258ff1 100644
--- a/flang/include/flang/Semantics/dump-expr.h
+++ b/flang/include/flang/Semantics/dump-expr.h
@@ -201,6 +201,19 @@ class DumpEvaluateExpr {
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);
diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index e2817ea542131..f93b9a892715a 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -169,6 +169,7 @@ class ExpressionAnalyzer {
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());
diff --git a/flang/lib/Evaluate/check-expression.cpp b/flang/lib/Evaluate/check-expression.cpp
index e73a4d82951af..be7db0f20821d 100644
--- a/flang/lib/Evaluate/check-expression.cpp
+++ b/flang/lib/Evaluate/check-expression.cpp
@@ -112,6 +112,19 @@ class IsConstantExprHelper
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 @@ class IsInitialDataTargetHelper
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 @@ class IsContiguousHelper
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 @@ class CollectUsedSymbolValuesHelper
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)}) {
diff --git a/flang/lib/Evaluate/expression.cpp b/flang/lib/Evaluate/expression.cpp
index 759fe5bc71b69..128c39b3eb004 100644
--- a/flang/lib/Evaluate/expression.cpp
+++ b/flang/lib/Evaluate/expression.cpp
@@ -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);
diff --git a/flang/lib/Evaluate/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h
index 529e3a9ad5a08..d4d7f2b705b3d 100644
--- a/flang/lib/Evaluate/fold-implementation.h
+++ b/flang/lib/Evaluate/fold-implementation.h
@@ -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,
diff --git a/flang/lib/Evaluate/formatting.cpp b/flang/lib/Evaluate/formatting.cpp
index 5632015857ab3..09cb8b08dda81 100644
--- a/flang/lib/Evaluate/formatting.cpp
+++ b/flang/lib/Evaluate/formatting.cpp
@@ -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;
diff --git a/flang/lib/Evaluate/tools.cpp b/flang/lib/Evaluate/tools.cpp
index e931f93d91b38..8aff09aa84c13 100644
--- a/flang/lib/Evaluate/tools.cpp
+++ b/flang/lib/Evaluate/tools.cpp
@@ -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));
diff --git a/flang/lib/Lower/ConvertExpr.cpp b/flang/lib/Lower/ConvertExpr.cpp
index a7e0239d335fd..32cd710e9b5b4 100644
--- a/flang/lib/Lower/ConvertExpr.cpp
+++ b/flang/lib/Lower/ConvertExpr.cpp
@@ -927,6 +927,11 @@ class ScalarExprLowering {
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 @@ class ArrayExprLowering {
};
}
+ 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)
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index 0c015bc9a2f1b..7ddd09e59c262 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1788,6 +1788,12 @@ class HlfirBuilder {
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();
diff --git a/flang/lib/Lower/IterationSpace.cpp b/flang/lib/Lower/IterationSpace.cpp
index 203fec508f795..52a15223bc1e6 100644
--- a/flang/lib/Lower/IterationSpace.cpp
+++ b/flang/lib/Lower/IterationSpace.cpp
@@ -212,6 +212,14 @@ class ArrayBaseFinder {
(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 {};
diff --git a/flang/lib/Lower/Support/Utils.cpp b/flang/lib/Lower/Support/Utils.cpp
index 384636a659875..280968975ea96 100644
--- a/flang/lib/Lower/Support/Utils.cpp
+++ b/flang/lib/Lower/Support/Utils.cpp
@@ -158,6 +158,11 @@ class HashEvaluateExpr {
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 @@ class IsEqualEvaluateExpr {
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) {
diff --git a/flang/lib/Parser/expr-parsers.cpp b/flang/lib/Parser/expr-parsers.cpp
index b6832a7999c5b..db29b15a1fa05 100644
--- a/flang/lib/Parser/expr-parsers.cpp
+++ b/flang/lib/Parser/expr-parsers.cpp
@@ -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.
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index c31eac0b3ff68..5ddd0cfc3a1ef 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -900,6 +900,17 @@ class UnparseVisitor {
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(')');
}
diff --git a/flang/lib/Semantics/definable.cpp b/flang/lib/Semantics/definable.cpp
index fdb4a2dcf8808..a82ca80ddd1b8 100644
--- a/flang/lib/Semantics/definable.cpp
+++ b/flang/lib/Semantics/definable.cpp
@@ -316,6 +316,10 @@ class DuplicatedSubscriptFinder
}
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_;
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 97e9bd39f0630..bd4e2a44df8ae 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -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
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index f183510668449..52bd66eae1dfa 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -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
diff --git a/flang/test/Evaluate/fold-conditional-expr.f90 b/flang/test/Evaluate/fold-conditional-expr.f90
new file mode 100644
index 0000000000000..0240859cae9ff
--- /dev/null
+++ b/flang/test/Evaluate/fold-conditional-expr.f90
@@ -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
diff --git a/flang/test/Parser/conditional-expr.f90 b/flang/test/Parser/conditional-expr.f90
new file mode 100644
index 0000000000000..f16472f486d42
--- /dev/null
+++ b/flang/test/Parser/conditional-expr.f90
@@ -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
diff erent 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
diff erent 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
diff erent 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
diff --git a/flang/test/Semantics/conditional-expr.f90 b/flang/test/Semantics/conditional-expr.f90
new file mode 100644
index 0000000000000..12fbea7e86488
--- /dev/null
+++ b/flang/test/Semantics/conditional-expr.f90
@@ -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
diff erences 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
More information about the flang-commits
mailing list