Hi all,

This patch introduces support for conditional expressions (also known as ternary operators in some languages) to Fortran. I decided to implement this feature after discovering that this common functionality, widely used in C/C++, wasn't available until the Fortran 2023 standard. This was also a great opportunity to learn more about gfortran's internals, as it required touching both the front-end and back-end.

For the front-end, I manually parsed the right-associative conditional expression, which required some special logic. On the back-end, I simply forwarded it to COND_EXPR. I also added support for tools like the parse-tree dump.

I'm not sure if we need to handle constant folding ourselves, or if COND_EXPR will take care of it.

I plan to add more tests and polish the patch. However, I want to get some early feedback on the general approach. Please take a look when you have a moment.

Thanks in advance,
Yuao
From 8b0312442ade17f64ae7c8059daa3af46a0bceda Mon Sep 17 00:00:00 2001
From: Yuao Ma <c...@outlook.com>
Date: Wed, 30 Jul 2025 22:38:57 +0800
Subject: [PATCH] fortran: implement conditional expression for fortran 2023

TBD

gcc/fortran/ChangeLog:

        * dump-parse-tree.cc (show_expr):
        * expr.cc (gfc_get_conditional_expr):
        (gfc_copy_expr):
        (free_expr0):
        * gfortran.h (enum expr_t):
        (gfc_get_operator_expr):
        (gfc_get_conditional_expr):
        * matchexp.cc (gfc_match_expr):
        (match_binary):
        * resolve.cc (resolve_conditional):
        (gfc_resolve_expr):
        * trans-expr.cc (gfc_conv_conditional_expr):
        (gfc_conv_expr):

gcc/testsuite/ChangeLog:

        * gfortran.dg/conditional_1.f90: New test.
---
 gcc/fortran/dump-parse-tree.cc              | 10 ++++
 gcc/fortran/expr.cc                         | 34 +++++++++++++
 gcc/fortran/gfortran.h                      | 30 +++++++++--
 gcc/fortran/matchexp.cc                     | 55 +++++++++++++++++++--
 gcc/fortran/resolve.cc                      | 39 +++++++++++++++
 gcc/fortran/trans-expr.cc                   | 35 +++++++++++++
 gcc/testsuite/gfortran.dg/conditional_1.f90 | 12 +++++
 7 files changed, 206 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/conditional_1.f90

diff --git a/gcc/fortran/dump-parse-tree.cc b/gcc/fortran/dump-parse-tree.cc
index 3cd2eeef11a..eda0659d6e2 100644
--- a/gcc/fortran/dump-parse-tree.cc
+++ b/gcc/fortran/dump-parse-tree.cc
@@ -767,6 +767,16 @@ show_expr (gfc_expr *p)
 
       break;
 
+    case EXPR_CONDITIONAL:
+      fputc ('(', dumpfile);
+      show_expr (p->value.conditional.condition);
+      fputs (" ? ", dumpfile);
+      show_expr (p->value.conditional.true_expr);
+      fputs (" : ", dumpfile);
+      show_expr (p->value.conditional.false_expr);
+      fputc (')', dumpfile);
+      break;
+
     case EXPR_COMPCALL:
       show_compcall (p);
       break;
diff --git a/gcc/fortran/expr.cc b/gcc/fortran/expr.cc
index b8d04ff6f36..2ccd8248a8a 100644
--- a/gcc/fortran/expr.cc
+++ b/gcc/fortran/expr.cc
@@ -116,6 +116,25 @@ gfc_get_operator_expr (locus *where, gfc_intrinsic_op op,
   return e;
 }
 
+/* Get a new expression node that is an conditional expression node.  */
+
+gfc_expr *
+gfc_get_conditional_expr (locus *where, gfc_expr *condition,
+                         gfc_expr *true_expr, gfc_expr *false_expr)
+{
+  gfc_expr *e;
+
+  e = gfc_get_expr ();
+  e->expr_type = EXPR_CONDITIONAL;
+  e->value.conditional.condition = condition;
+  e->value.conditional.true_expr = true_expr;
+  e->value.conditional.false_expr = false_expr;
+
+  if (where)
+    e->where = *where;
+
+  return e;
+}
 
 /* Get a new expression node that is an structure constructor
    of given type and kind.  */
@@ -393,6 +412,15 @@ gfc_copy_expr (gfc_expr *p)
 
       break;
 
+    case EXPR_CONDITIONAL:
+      q->value.conditional.condition
+       = gfc_copy_expr (p->value.conditional.condition);
+      q->value.conditional.true_expr
+       = gfc_copy_expr (p->value.conditional.true_expr);
+      q->value.conditional.false_expr
+       = gfc_copy_expr (p->value.conditional.false_expr);
+      break;
+
     case EXPR_FUNCTION:
       q->value.function.actual =
        gfc_copy_actual_arglist (p->value.function.actual);
@@ -502,6 +530,12 @@ free_expr0 (gfc_expr *e)
        gfc_free_expr (e->value.op.op2);
       break;
 
+    case EXPR_CONDITIONAL:
+      gfc_free_expr (e->value.conditional.condition);
+      gfc_free_expr (e->value.conditional.true_expr);
+      gfc_free_expr (e->value.conditional.false_expr);
+      break;
+
     case EXPR_FUNCTION:
       gfc_free_actual_arglist (e->value.function.actual);
       break;
diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
index d9dcd1b504f..dde892d235a 100644
--- a/gcc/fortran/gfortran.h
+++ b/gcc/fortran/gfortran.h
@@ -176,8 +176,19 @@ enum gfc_source_form
 
 /* Expression node types.  */
 enum expr_t
-  { EXPR_UNKNOWN = 0, EXPR_OP = 1, EXPR_FUNCTION, EXPR_CONSTANT, EXPR_VARIABLE,
-  EXPR_SUBSTRING, EXPR_STRUCTURE, EXPR_ARRAY, EXPR_NULL, EXPR_COMPCALL, 
EXPR_PPC
+{
+  EXPR_UNKNOWN = 0,
+  EXPR_OP = 1,
+  EXPR_FUNCTION,
+  EXPR_CONSTANT,
+  EXPR_VARIABLE,
+  EXPR_SUBSTRING,
+  EXPR_STRUCTURE,
+  EXPR_ARRAY,
+  EXPR_NULL,
+  EXPR_COMPCALL,
+  EXPR_PPC,
+  EXPR_CONDITIONAL,
 };
 
 /* Array types.  */
@@ -2808,8 +2819,14 @@ typedef struct gfc_expr
     character;
 
     gfc_constructor_base constructor;
-  }
-  value;
+
+    struct
+    {
+      struct gfc_expr *condition;
+      struct gfc_expr *true_expr;
+      struct gfc_expr *false_expr;
+    } conditional;
+  } value;
 
   /* Used to store PDT expression lists associated with expressions.  */
   gfc_actual_arglist *param_list;
@@ -3924,7 +3941,10 @@ bool gfc_is_ptr_fcn (gfc_expr *);
 gfc_expr *gfc_get_expr (void);
 gfc_expr *gfc_get_array_expr (bt type, int kind, locus *);
 gfc_expr *gfc_get_null_expr (locus *);
-gfc_expr *gfc_get_operator_expr (locus *, gfc_intrinsic_op,gfc_expr *, 
gfc_expr *);
+gfc_expr *gfc_get_operator_expr (locus *, gfc_intrinsic_op, gfc_expr *,
+                                gfc_expr *);
+gfc_expr *gfc_get_conditional_expr (locus *, gfc_expr *, gfc_expr *,
+                                   gfc_expr *);
 gfc_expr *gfc_get_structure_constructor_expr (bt, int, locus *);
 gfc_expr *gfc_get_constant_expr (bt, int, locus *);
 gfc_expr *gfc_get_character_expr (int, locus *, const char *, gfc_charlen_t 
len);
diff --git a/gcc/fortran/matchexp.cc b/gcc/fortran/matchexp.cc
index 9b66243b4fa..83b37cfaf6a 100644
--- a/gcc/fortran/matchexp.cc
+++ b/gcc/fortran/matchexp.cc
@@ -856,12 +856,11 @@ match_level_5 (gfc_expr **result)
   return MATCH_YES;
 }
 
-
-/* Match an expression.  At this level, we are stringing together
+/* Match a binary expression.  At this level, we are stringing together
    level 5 expressions separated by binary operators.  */
 
-match
-gfc_match_expr (gfc_expr **result)
+static match
+match_binary (gfc_expr **result)
 {
   gfc_expr *all, *e;
   gfc_user_op *uop;
@@ -902,3 +901,51 @@ gfc_match_expr (gfc_expr **result)
   *result = all;
   return MATCH_YES;
 }
+
+/* Match an expression.  */
+
+match
+gfc_match_expr (gfc_expr **result)
+{
+  gfc_expr *condition, *true_expr, *false_expr;
+  locus where;
+  match m;
+
+  m = match_binary (&condition);
+  if (m != MATCH_YES)
+    return m;
+
+  if (gfc_match_char ('?') != MATCH_YES)
+    {
+      *result = condition;
+      return MATCH_YES;
+    }
+  where = gfc_current_locus;
+
+  m = match_binary (&true_expr);
+  if (m != MATCH_YES)
+    {
+      gfc_free_expr (condition);
+      return m;
+    }
+
+  if (gfc_match_char (':') != MATCH_YES)
+    {
+      gfc_error ("Expected ':' in conditional expression at %C");
+      gfc_free_expr (condition);
+      gfc_free_expr (true_expr);
+      return MATCH_ERROR;
+    }
+  where = gfc_current_locus;
+
+  m = gfc_match_expr (&false_expr);
+  if (m != MATCH_YES)
+    {
+      gfc_free_expr (condition);
+      gfc_free_expr (true_expr);
+      return m;
+    }
+
+  *result = gfc_get_conditional_expr (&where, condition, true_expr, 
false_expr);
+  return MATCH_YES;
+}
diff --git a/gcc/fortran/resolve.cc b/gcc/fortran/resolve.cc
index c33bd17da2d..a9b2f57f360 100644
--- a/gcc/fortran/resolve.cc
+++ b/gcc/fortran/resolve.cc
@@ -4989,6 +4989,41 @@ simplify_op:
   return t;
 }
 
+static bool
+resolve_conditional (gfc_expr *expr)
+{
+  gfc_expr *condition, *true_expr, *false_expr;
+
+  condition = expr->value.conditional.condition;
+  true_expr = expr->value.conditional.true_expr;
+  false_expr = expr->value.conditional.false_expr;
+
+  if (!gfc_resolve_expr (condition) || !gfc_resolve_expr (true_expr)
+      || !gfc_resolve_expr (false_expr))
+    return false;
+
+  if (condition->ts.type != BT_LOGICAL)
+    {
+      gfc_error ("Condition in conditional expression must be logical at %L",
+                &condition->where);
+      return false;
+    }
+
+  if (!gfc_type_compatible (&true_expr->ts, &false_expr->ts))
+    {
+      if (!gfc_convert_type (true_expr, &false_expr->ts, 2)
+         && !gfc_convert_type (false_expr, &true_expr->ts, 2))
+       {
+         gfc_error ("Incompatible types in conditional expression at %L",
+                    &expr->where);
+         return false;
+       }
+    }
+
+  expr->ts = true_expr->ts;
+  expr->rank = MAX (true_expr->rank, false_expr->rank);
+  return true;
+}
 
 /************** Array resolution subroutines **************/
 
@@ -7980,6 +8015,10 @@ gfc_resolve_expr (gfc_expr *e)
       t = resolve_operator (e);
       break;
 
+    case EXPR_CONDITIONAL:
+      t = resolve_conditional (e);
+      break;
+
     case EXPR_FUNCTION:
     case EXPR_VARIABLE:
 
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index 0db7ba3fd52..56e0dbf1808 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -4369,6 +4369,37 @@ gfc_conv_expr_op (gfc_se * se, gfc_expr * expr)
   gfc_add_block_to_block (&se->post, &lse.post);
 }
 
+static void
+gfc_conv_conditional_expr (gfc_se *se, gfc_expr *expr)
+{
+  gfc_se cond_se, true_se, false_se;
+  tree condition, true_val, false_val;
+  tree type;
+
+  gfc_init_se (&cond_se, se);
+  gfc_init_se (&true_se, se);
+  gfc_init_se (&false_se, se);
+
+  gfc_conv_expr (&cond_se, expr->value.conditional.condition);
+  gfc_add_block_to_block (&se->pre, &cond_se.pre);
+  condition = gfc_evaluate_now (cond_se.expr, &se->pre);
+
+  gfc_conv_expr (&true_se, expr->value.conditional.true_expr);
+  gfc_add_block_to_block (&se->pre, &true_se.pre);
+  true_val = true_se.expr;
+
+  gfc_conv_expr (&false_se, expr->value.conditional.false_expr);
+  gfc_add_block_to_block (&se->pre, &false_se.pre);
+  false_val = false_se.expr;
+
+  type = gfc_typenode_for_spec (&expr->ts);
+  true_val = fold_convert (type, true_val);
+  false_val = fold_convert (type, false_val);
+
+  se->expr = fold_build3_loc (input_location, COND_EXPR, type, condition,
+                             true_val, false_val);
+}
+
 /* If a string's length is one, we convert it to a single character.  */
 
 tree
@@ -10447,6 +10478,10 @@ gfc_conv_expr (gfc_se * se, gfc_expr * expr)
       gfc_conv_expr_op (se, expr);
       break;
 
+    case EXPR_CONDITIONAL:
+      gfc_conv_conditional_expr (se, expr);
+      break;
+
     case EXPR_FUNCTION:
       gfc_conv_function_expr (se, expr);
       break;
diff --git a/gcc/testsuite/gfortran.dg/conditional_1.f90 
b/gcc/testsuite/gfortran.dg/conditional_1.f90
new file mode 100644
index 00000000000..e19a33dbfb8
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/conditional_1.f90
@@ -0,0 +1,12 @@
+! { dg-do run }
+program simple_conditional
+  implicit none
+  integer :: i = 42
+
+  i = i > 0 ? 1 : -1
+  if (i /= 1) stop 1
+
+  i = 0
+  i = i > 0 ? 1 : i < 0 ? -1 : 0
+  if (i /= 0) stop 2
+end program simple_conditional
-- 
2.43.0

Reply via email to