[PATCH] PR125528 ICE or wrong-code for ASSOCIATE selector that is a, type-bound user-defined operator

Jerry D <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.fortran
Message-ID <[email protected]>
This one was what we call a 3-fer. Next in line on the PR125515 list.

See attached patch.

Regression tested on x86-64.

OK for mainline and then backport to 16?

Regards,

Jerry

---

fortran: ICE or wrong-code for ASSOCIATE selector that is a
  type-bound user-defined operator

Three related bugs prevented ASSOCIATE selectors that are type-bound
user-defined operator expressions from compiling correctly.

Bug 1 (class.cc): find_typebound_proc_uop returned NULL immediately when
the derived type has no f2k_derived namespace, bypassing the parent-type
inheritance walk.  This caused inherited UDOs to be silently not found.
Fix: set root = NULL and let the loop reach the parent type instead.

Bug 2 (resolve.cc): resolve_typebound_procedures called resolve_symbol on
the parent type only after an early return that fires when the derived type
has no direct type-bound bindings.  This left parent-type bindings
unresolved when searched via gfc_find_typebound_user_op.
Fix: move resolve_symbol(super_type) before the early return.

Bug 3 (match.cc): match_association_list did not handle ASSOCIATE selectors
of the form .uop. expr or the nested case .uop2. (.uop1. expr).  When the
selector's type was BT_UNKNOWN at parse time the name of the associate
variable was left untyped, producing a "Syntax error in expression" ICE in
the body of the ASSOCIATE construct.
Fix: add three helpers before match_association_list:
   - resolve_assoc_operand: attempts gfc_resolve_expr on EXPR_FUNCTION
     operands and falls back to gfc_find_dt_in_generic for constructor calls
     whose argument types are not yet known.
   - infer_typebound_uop_type: reads the return type of a type-bound UDO
     directly from specific_st->n.tb->u.specific->n.sym without calling
     gfc_resolve_symbol, avoiding a resolve_symbol_called race condition.
   - extend_assoc_op: walks the expression tree bottom-up, propagating
     types through INTRINSIC_PARENTHESES wrappers before calling the two
     helpers above on each INTRINSIC_USER node.
When the selector is an INTRINSIC_USER EXPR_OP with BT_UNKNOWN type,
call extend_assoc_op on the operands, then gfc_extend_expr (errors
suppressed).  Accept the result when gfc_extend_expr returns MATCH_YES or
when it returns MATCH_ERROR but has already converted the node to
EXPR_COMPCALL with a known type (the full resolution pass finishes it).

PR fortran/125528

Assisted by: Claude Sonnet 4.6

gcc/fortran/ChangeLog:

	PR fortran/125528
	* class.cc (find_typebound_proc_uop): Set root = NULL instead of
	returning NULL when derived type lacks f2k_derived, so parent-type
	type-bound procedures and operators are still found via inheritance.
	* match.cc (resolve_assoc_operand): New helper.
	(infer_typebound_uop_type): New helper.
	(extend_assoc_op): New helper.
	(match_association_list): Handle ASSOCIATE selectors that are
	type-bound user-defined operator expressions, including nested cases.
	* resolve.cc (resolve_typebound_procedures): Move resolve_symbol
	call for the parent type before the early return so inherited
	type-bound bindings are resolved even when the child type has none
	of its own.

gcc/testsuite/ChangeLog:

	PR fortran/125528
	* gfortran.dg/associate_80.f90: New test.
---
PR125528-associate-typebound-udo.patch (text/x-patch, 15.5 KB)
From 9bab576f32c66218f9b8e7f3c95d9e12988579ea Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Wed, 27 May 2026 15:10:07 -0700
Subject: [PATCH] fortran: ICE or wrong-code for ASSOCIATE selector that is a
 type-bound user-defined operator

Three related bugs prevented ASSOCIATE selectors that are type-bound
user-defined operator expressions from compiling correctly.

Bug 1 (class.cc): find_typebound_proc_uop returned NULL immediately when
the derived type has no f2k_derived namespace, bypassing the parent-type
inheritance walk.  This caused inherited UDOs to be silently not found.
Fix: set root = NULL and let the loop reach the parent type instead.

Bug 2 (resolve.cc): resolve_typebound_procedures called resolve_symbol on
the parent type only after an early return that fires when the derived type
has no direct type-bound bindings.  This left parent-type bindings
unresolved when searched via gfc_find_typebound_user_op.
Fix: move resolve_symbol(super_type) before the early return.

Bug 3 (match.cc): match_association_list did not handle ASSOCIATE selectors
of the form .uop. expr or the nested case .uop2. (.uop1. expr).  When the
selector's type was BT_UNKNOWN at parse time the name of the associate
variable was left untyped, producing a "Syntax error in expression" ICE in
the body of the ASSOCIATE construct.
Fix: add three helpers before match_association_list:
  - resolve_assoc_operand: attempts gfc_resolve_expr on EXPR_FUNCTION
    operands and falls back to gfc_find_dt_in_generic for constructor calls
    whose argument types are not yet known.
  - infer_typebound_uop_type: reads the return type of a type-bound UDO
    directly from specific_st->n.tb->u.specific->n.sym without calling
    gfc_resolve_symbol, avoiding a resolve_symbol_called race condition.
  - extend_assoc_op: walks the expression tree bottom-up, propagating
    types through INTRINSIC_PARENTHESES wrappers before calling the two
    helpers above on each INTRINSIC_USER node.
When the selector is an INTRINSIC_USER EXPR_OP with BT_UNKNOWN type,
call extend_assoc_op on the operands, then gfc_extend_expr (errors
suppressed).  Accept the result when gfc_extend_expr returns MATCH_YES or
when it returns MATCH_ERROR but has already converted the node to
EXPR_COMPCALL with a known type (the full resolution pass finishes it).

PR fortran/125528

Assisted by: Claude Sonnet 4.6

gcc/fortran/ChangeLog:

	PR fortran/125528
	* class.cc (find_typebound_proc_uop): Set root = NULL instead of
	returning NULL when derived type lacks f2k_derived, so parent-type
	type-bound procedures and operators are still found via inheritance.
	* match.cc (resolve_assoc_operand): New helper.
	(infer_typebound_uop_type): New helper.
	(extend_assoc_op): New helper.
	(match_association_list): Handle ASSOCIATE selectors that are
	type-bound user-defined operator expressions, including nested cases.
	* resolve.cc (resolve_typebound_procedures): Move resolve_symbol
	call for the parent type before the early return so inherited
	type-bound bindings are resolved even when the child type has none
	of its own.

gcc/testsuite/ChangeLog:

	PR fortran/125528
	* gfortran.dg/associate_80.f90: New test.
---
 gcc/fortran/class.cc                       |   4 +-
 gcc/fortran/match.cc                       | 186 ++++++++++++++++++++-
 gcc/fortran/resolve.cc                     |  10 +-
 gcc/testsuite/gfortran.dg/associate_80.f90 |  83 +++++++++
 4 files changed, 273 insertions(+), 10 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/associate_80.f90

diff --git a/gcc/fortran/class.cc b/gcc/fortran/class.cc
index a4c3c37104a..d811ee2929c 100644
--- a/gcc/fortran/class.cc
+++ b/gcc/fortran/class.cc
@@ -3136,7 +3136,9 @@ find_typebound_proc_uop (gfc_symbol* derived, bool* t,
     root = (uop ? derived->f2k_derived->tb_uop_root
 		: derived->f2k_derived->tb_sym_root);
   else
-    return NULL;
+    /* No f2k_derived namespace; allow the extension check below to proceed
+       so inherited type-bound procedures/operators are still found.  */
+    root = NULL;
 
   /* Try to find it in the current type's namespace.  */
   res = gfc_find_symtree (root, name);
diff --git a/gcc/fortran/match.cc b/gcc/fortran/match.cc
index d892a4588b2..9172f46ed7c 100644
--- a/gcc/fortran/match.cc
+++ b/gcc/fortran/match.cc
@@ -1980,6 +1980,155 @@ check_coarray_assoc (const char *name, gfc_association_list *assoc)
   return true;
 }
 
+/* Try to resolve an EXPR_FUNCTION operand so its return type is known.
+   Called during ASSOCIATE selector parsing, before type-bound operator
+   extension, when the operand is an unresolved generic constructor call
+   such as `scalar_1D_t(initializer, order=2, ...)`.  Errors are suppressed
+   since we are still in the parsing phase.  */
+
+static void
+resolve_assoc_operand (gfc_expr *e)
+{
+  if (!e || e->ts.type != BT_UNKNOWN || e->expr_type != EXPR_FUNCTION)
+    return;
+
+  /* First, try full expression resolution (works when argument types are
+     already known at parse time).  */
+  gfc_push_suppress_errors ();
+  gfc_resolve_expr (e);
+  gfc_pop_suppress_errors ();
+
+  if (e->ts.type != BT_UNKNOWN)
+    return;
+
+  /* Fallback for generic constructor interfaces such as
+       scalar_1D_t(initializer, order=2, cells=16, x_min=0D0, x_max=5D0)
+     where full argument resolution is not possible at parse time.
+     If the function name resolves to a generic interface that wraps a
+     derived type (a constructor interface), infer the return type as
+     that derived type.  */
+  if (!e->symtree || !e->symtree->n.sym)
+    return;
+
+  gfc_symbol *dt_sym = gfc_find_dt_in_generic (e->symtree->n.sym);
+  if (dt_sym && gfc_fl_struct (dt_sym->attr.flavor))
+    {
+      e->ts.type = BT_DERIVED;
+      e->ts.u.derived = dt_sym;
+    }
+}
+
+/* Infer the return type of a type-bound user-defined operator without
+   converting the expression node or triggering gfc_resolve_symbol on the
+   return type.  This is used during ASSOCIATE selector parsing to propagate
+   type information bottom-up through nested UDO expressions such as
+   (.div. (.grad. x)), so that the outer gfc_extend_expr can locate the
+   type-bound .div. once the type of (.grad. x) is known.
+
+   Calling gfc_extend_expr for this purpose would partially resolve the
+   return type's derived-type symbol (setting resolve_symbol_called before
+   resolve_typebound_procedures has run), which prevents the subsequent
+   outer gfc_extend_expr from properly resolving the type-bound operator
+   on the return type.  We avoid that by reading the return type directly
+   from the procedure's result variable without triggering resolution.  */
+
+static void
+infer_typebound_uop_type (gfc_expr *e)
+{
+  if (!e || e->expr_type != EXPR_OP || e->value.op.op != INTRINSIC_USER
+      || e->ts.type != BT_UNKNOWN)
+    return;
+
+  /* Find the operand and strip parentheses.  */
+  gfc_expr *operand = e->value.op.op1;
+  while (operand && operand->expr_type == EXPR_OP
+	 && operand->value.op.op == INTRINSIC_PARENTHESES)
+    operand = operand->value.op.op1;
+
+  if (!operand || operand->ts.type != BT_DERIVED || !operand->ts.u.derived)
+    return;
+
+  /* Look up the UDO binding in the derived type's namespace (and its
+     parent types, via the recursion in find_typebound_proc_uop).  This
+     does not call resolve_symbol, so it leaves resolve_symbol_called
+     untouched for all types involved.  */
+  bool ok = true;
+  gfc_symtree *tb_uop
+    = gfc_find_typebound_user_op (operand->ts.u.derived, &ok,
+				  e->value.op.uop->name, false, NULL);
+  if (!tb_uop || !tb_uop->n.tb)
+    return;
+
+  gfc_typebound_proc *tb = tb_uop->n.tb;
+  if (!tb->is_generic || !tb->u.generic)
+    return;
+
+  /* Take the first specific binding.  specific_st is set from module reading;
+     its n.tb is the gfc_typebound_proc for that specific binding (same as
+     what resolve_typebound_procedures later stores in g->specific).  Follow
+     the chain specific_st->n.tb->u.specific->n.sym to reach the actual
+     implementing function symbol, whose ts holds the return type.
+     This mirrors what build_compcall_for_operator does via
+     g->specific->u.specific->n.sym->ts after resolution.  */
+  gfc_tbp_generic *g = tb->u.generic;
+  if (!g->specific_st || !g->specific_st->n.tb)
+    return;
+
+  gfc_typebound_proc *specific_tb = g->specific_st->n.tb;
+  if (specific_tb->is_generic || !specific_tb->u.specific
+      || !specific_tb->u.specific->n.sym)
+    return;
+
+  gfc_symbol *proc = specific_tb->u.specific->n.sym;
+  if (proc->ts.type != BT_UNKNOWN)
+    e->ts = proc->ts;
+}
+
+/* Recursively propagate type information bottom-up through a nested UDO
+   expression tree so that when gfc_extend_expr is called on the outermost
+   operator during ASSOCIATE selector parsing, the inner operands already have
+   their types set and the type-bound lookup can succeed.  Uses
+   infer_typebound_uop_type rather than gfc_extend_expr to avoid triggering
+   resolve_symbol on the return types, which would prevent the outer
+   gfc_extend_expr from working correctly.  */
+
+static void
+extend_assoc_op (gfc_expr *e)
+{
+  if (!e || e->expr_type != EXPR_OP)
+    return;
+
+  /* Bottom-up: process children first.  */
+  extend_assoc_op (e->value.op.op1);
+  extend_assoc_op (e->value.op.op2);
+
+  /* Propagate the child's type upward through parentheses nodes.
+     gfc_extend_expr's matching_typebound_op checks ts.type BEFORE stripping
+     INTRINSIC_PARENTHESES wrappers, so an untyped parentheses node prevents
+     the outer operator from being found.  */
+  if (e->value.op.op == INTRINSIC_PARENTHESES
+      && e->ts.type == BT_UNKNOWN
+      && e->value.op.op1
+      && e->value.op.op1->ts.type != BT_UNKNOWN)
+    {
+      e->ts = e->value.op.op1->ts;
+      return;
+    }
+
+  /* Only handle unresolved user-defined operators.  */
+  if (e->value.op.op != INTRINSIC_USER || e->ts.type != BT_UNKNOWN)
+    return;
+
+  /* Try to infer the type of each operand if it is an unresolved constructor
+     call (EXPR_FUNCTION whose return type is still BT_UNKNOWN).  */
+  resolve_assoc_operand (e->value.op.op1);
+  resolve_assoc_operand (e->value.op.op2);
+
+  /* Infer this operator's return type from the type-bound procedure's result
+     variable, without calling gfc_resolve_symbol on the return type.  */
+  infer_typebound_uop_type (e);
+}
+
 match
 match_association_list (bool for_change_team = false)
 {
@@ -2142,12 +2291,37 @@ match_association_list (bool for_change_team = false)
 	    }
 	}
       else if (newAssoc->target->ts.type == BT_UNKNOWN
-	       && newAssoc->target->expr_type == EXPR_OP)
-	{
-	  /* This will work for sure if the operator is type bound to a use
-	     associated derived type.  */
-	  gfc_expr *tmp =gfc_copy_expr (newAssoc->target);
-	  if (gfc_extend_expr (tmp) == MATCH_YES)
+	       && newAssoc->target->expr_type == EXPR_OP
+	       && newAssoc->target->value.op.op == INTRINSIC_USER)
+	{
+	  /* If the selector is an unresolved type-bound user-defined operator
+	     expression, try to extend it now so the associate name gets a usable
+	     type.  For nested operators such as
+	       (.div. (.grad. x))
+	     first propagate types bottom-up through the inner operands
+	     (extend_assoc_op).  For a direct operator applied to a constructor
+	     call such as
+	       (.div. vector_t(init_fn, n=8))
+	     additionally resolve the direct operands as constructor calls
+	     (resolve_assoc_operand).  Then call gfc_extend_expr on the
+	     outermost operator.  Only handle INTRINSIC_USER here; arithmetic
+	     operators are left to the normal resolution pass.  */
+	  gfc_expr *tmp = gfc_copy_expr (newAssoc->target);
+	  extend_assoc_op (tmp->value.op.op1);
+	  extend_assoc_op (tmp->value.op.op2);
+	  resolve_assoc_operand (tmp->value.op.op1);
+	  resolve_assoc_operand (tmp->value.op.op2);
+	  /* Suppress errors from gfc_extend_expr: during parsing the full
+	     resolution has not run yet, so gfc_resolve_expr(COMPCALL) may
+	     fail even when the type-bound operator was found and the node
+	     was correctly converted to EXPR_COMPCALL.  Accept the conversion
+	     in that case and let the normal resolution pass finish it.  */
+	  gfc_push_suppress_errors ();
+	  match ext_m = gfc_extend_expr (tmp);
+	  gfc_pop_suppress_errors ();
+	  if (ext_m == MATCH_YES
+	      || (tmp->expr_type == EXPR_COMPCALL
+		  && tmp->ts.type != BT_UNKNOWN))
 	    gfc_replace_expr (newAssoc->target, tmp);
 	  else
 	    gfc_free_expr (tmp);
diff --git a/gcc/fortran/resolve.cc b/gcc/fortran/resolve.cc
index a31f395709d..df91671e171 100644
--- a/gcc/fortran/resolve.cc
+++ b/gcc/fortran/resolve.cc
@@ -17218,13 +17218,17 @@ resolve_typebound_procedures (gfc_symbol* derived)
   int op;
   gfc_symbol* super_type;
 
-  if (!derived->f2k_derived || !derived->f2k_derived->tb_sym_root)
-    return true;
-
+  /* Resolve the super-type first so that inherited bindings (including
+     user operators) are fully resolved before we look them up via
+     gfc_find_typebound_user_op.  This must happen even when 'derived'
+     has no direct type-bound bindings of its own.  */
   super_type = gfc_get_derived_super_type (derived);
   if (super_type)
     resolve_symbol (super_type);
 
+  if (!derived->f2k_derived || !derived->f2k_derived->tb_sym_root)
+    return true;
+
   resolve_bindings_derived = derived;
   resolve_bindings_result = true;
 
diff --git a/gcc/testsuite/gfortran.dg/associate_80.f90 b/gcc/testsuite/gfortran.dg/associate_80.f90
new file mode 100644
index 00000000000..14c405c8f22
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/associate_80.f90
@@ -0,0 +1,83 @@
+! { dg-do run }
+!
+! Test ASSOCIATE selectors that are type-bound user-defined operators (UDOs),
+! including inherited operators and nested expressions such as
+! .div. (.grad. x).  PR fortran/125515
+!
+module associate_80_m
+  implicit none
+
+  type :: scalar_t
+    real :: val
+  contains
+    generic :: operator(.grad.) => do_grad
+    procedure, private :: do_grad
+    generic :: operator(.sq.)   => do_sq
+    procedure, private :: do_sq
+    generic :: get => get_val
+    procedure :: get_val
+  end type
+
+  type, extends(scalar_t) :: vector_t
+  contains
+    generic :: operator(.div.) => do_div
+    procedure, private :: do_div
+  end type
+
+contains
+
+  ! .grad. on scalar_t -> vector_t
+  pure function do_grad (self) result (r)
+    class(scalar_t), intent(in) :: self
+    type(vector_t) :: r
+    r%val = self%val * 2.0
+  end function
+
+  ! .sq. on scalar_t -> scalar_t
+  pure function do_sq (self) result (r)
+    class(scalar_t), intent(in) :: self
+    type(scalar_t) :: r
+    r%val = self%val * self%val
+  end function
+
+  ! .div. on vector_t -> scalar_t
+  pure function do_div (self) result (r)
+    class(vector_t), intent(in) :: self
+    type(scalar_t) :: r
+    r%val = self%val / 2.0
+  end function
+
+  pure function get_val (self) result (r)
+    class(scalar_t), intent(in) :: self
+    real :: r
+    r = self%val
+  end function
+
+end module associate_80_m
+
+program associate_80
+  use associate_80_m
+  implicit none
+
+  type(scalar_t) :: s
+  s%val = 3.0
+
+  ! Case 1: direct type-bound UDO as ASSOCIATE selector
+  associate (g => .grad. s)
+    if (abs (g%val - 6.0) > 1.0e-6) stop 1
+    if (abs (g%get () - 6.0) > 1.0e-6) stop 2
+  end associate
+
+  ! Case 2: inherited UDO (.sq. defined on scalar_t, used via scalar_t)
+  associate (q => .sq. s)
+    if (abs (q%val - 9.0) > 1.0e-6) stop 3
+  end associate
+
+  ! Case 3: nested UDOs — .div. (.grad. s)
+  ! .grad. s gives vector_t with val=6, .div. gives scalar_t with val=3
+  associate (r => .div. (.grad. s))
+    if (abs (r%val - 3.0) > 1.0e-6) stop 4
+    if (abs (r%get () - 3.0) > 1.0e-6) stop 5
+  end associate
+
+end program associate_80
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.