[flang-commits] [flang] [flang][Semantics] Check defined-operator purity at the use site (PR #229976)

via flang-commits flang-commits at lists.llvm.org
Thu Oct 15 04:41:56 PDT 2026


https://github.com/shivaramaarao updated https://github.com/llvm/llvm-project/pull/229976

>From e762604bfd812d8ea82269578dd4baed7b41ec5c Mon Sep 17 00:00:00 2001
From: Shivarama Rao <shivarama.rao at amd.com>
Date: Thu, 8 Oct 2026 08:25:38 +0530
Subject: [PATCH] [flang] Check defined-operator purity at the use site

AnalyzeDefinedOp passed the generic's declaration to MakeFunctionRef, so C1595 ran in the module instead
of in the pure subprogram that used the operator. This is fixed by Using the call site instead of
current source location.

Added the semantics tests that tests these feature.

Fixes #222888

Assisted by Cursor to get the root cause and updating the tests
---
 flang/include/flang/Semantics/expression.h |   4 +-
 flang/lib/Semantics/expression.cpp         |  13 +-
 flang/test/Semantics/pure01.f90            | 204 ++++++++++++++++++++-
 3 files changed, 214 insertions(+), 7 deletions(-)

diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index 59cd424c4dae97..a38ffdb79a97e3 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -352,8 +352,8 @@ class ExpressionAnalyzer {
   bool CheckDataRef(const DataRef &); // ditto
   std::optional<Expr<SubscriptInteger>> GetSubstringBound(
       const std::optional<parser::ScalarIntExpr> &);
-  MaybeExpr AnalyzeDefinedOp(
-      const parser::Name &, ActualArguments &&, const Symbol *&);
+  MaybeExpr AnalyzeDefinedOp(const parser::Name &, ActualArguments &&,
+      const Symbol *&, parser::CharBlock callSite);
   MaybeExpr FixMisparsedSubstring(const parser::Designator &);
 
   struct CalleeAndArguments {
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 91e98d5c8018c0..0a5127c0187bf6 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -4368,12 +4368,17 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::Concat &x) {
 // If the actuals match one of the specific procedures, return a function ref.
 // Otherwise report the error in messages.
 MaybeExpr ExpressionAnalyzer::AnalyzeDefinedOp(const parser::Name &name,
-    ActualArguments &&actuals, const Symbol *&symbol) {
+    ActualArguments &&actuals, const Symbol *&symbol,
+    parser::CharBlock callSite) {
+  // Reference checks, such as F2023 C1595 (which requires that a procedure
+  // referenced within a pure subprogram be pure), must be performed at the
+  // point of use. Therefore, callSite is used instead of the current source
+  // location.
   if (auto callee{GetCalleeAndArguments(name, std::move(actuals))}) {
     auto &proc{std::get<evaluate::ProcedureDesignator>(callee->u)};
     symbol = proc.GetSymbol();
     return MakeFunctionRef(
-        name.source, std::move(proc), std::move(callee->arguments));
+        callSite, std::move(proc), std::move(callee->arguments));
   } else {
     return std::nullopt;
   }
@@ -5422,7 +5427,7 @@ bool ArgumentAnalyzer::HasDeviceDefinedIntrinsicOpOverride(
       parser::Name name{generic->name(), generic};
       const Symbol *resultSymbol{nullptr};
       if (context_.AnalyzeDefinedOp(
-              name, ActualArguments{actuals_}, resultSymbol)) {
+              name, ActualArguments{actuals_}, resultSymbol, source_)) {
         return true;
       }
     }
@@ -5487,7 +5492,7 @@ MaybeExpr ArgumentAnalyzer::TryDefinedOp(const char *opr,
       parser::Name name{generic->name(), generic};
       const Symbol *resultSymbol{nullptr};
       MaybeExpr possibleResult{context_.AnalyzeDefinedOp(
-          name, ActualArguments{actuals_}, resultSymbol)};
+          name, ActualArguments{actuals_}, resultSymbol, source_)};
       FoundOne(std::move(possibleResult), *generic, resultSymbol);
     }
     for (std::size_t passIndex{0}; passIndex < actuals_.size(); ++passIndex) {
diff --git a/flang/test/Semantics/pure01.f90 b/flang/test/Semantics/pure01.f90
index e0911efd77c603..06ed2b0f0a8732 100644
--- a/flang/test/Semantics/pure01.f90
+++ b/flang/test/Semantics/pure01.f90
@@ -12,8 +12,210 @@ impure integer function func(x)
     class(t), intent(in) :: x
     func = 0
   end
-  pure integer function test
+  pure integer function pure_id(n)
+    integer, intent(in) :: n
+    pure_id = n
+  end
+  pure integer function test()
+    ! A direct call to a pure procedure is allowed.
+    test = pure_id(1)
     !ERROR: Procedure 'func' referenced in pure subprogram 'test' must be pure too
     test = .not. t()
   end
 end
+
+! F2023 C1595: a procedure referenced from a pure subprogram must be pure,
+! including references through defined operators (binary, unary, named,
+! and relational). The operator's generic is declared where its interface
+! block appears; the purity check must be anchored at the use site instead,
+! so each reference is diagnosed independently and the message points at
+! the call, not at the (possibly distant, possibly single) declaration.
+
+module pure_op_mod
+  implicit none
+  type, public :: vec_type
+    real :: x = 0.0, y = 0.0
+  end type vec_type
+
+  interface operator(+)
+    procedure impure_add ! impure
+  end interface operator(+)
+
+  interface operator(*)
+    procedure impure_scale ! impure
+  end interface operator(*)
+
+  interface operator(-)
+    procedure pure_sub ! binary, pure
+    procedure impure_neg ! unary, impure
+  end interface operator(-)
+
+  interface operator(==)
+    procedure impure_eq ! impure
+  end interface operator(==)
+
+  interface operator(.dot.)
+    procedure impure_dot ! impure
+  end interface operator(.dot.)
+
+  ! One generic, two specifics with different purity: only the specific
+  ! that overload resolution actually picks may be flagged.
+  interface operator(.scale.)
+    procedure pure_scale_int ! pure, (vec_type, integer)
+    procedure impure_scale_real ! impure, (vec_type, real)
+  end interface operator(.scale.)
+
+contains
+  function impure_add(a, b) result(r)
+    type(vec_type), intent(in) :: a, b
+    type(vec_type) :: r
+    r%x = a%x + b%x
+    r%y = a%y + b%y
+  end function impure_add
+
+  function impure_scale(s, v) result(r)
+    real, intent(in) :: s
+    type(vec_type), intent(in) :: v
+    type(vec_type) :: r
+    r%x = s * v%x
+    r%y = s * v%y
+  end function impure_scale
+
+  pure function pure_sub(a, b) result(r)
+    type(vec_type), intent(in) :: a, b
+    type(vec_type) :: r
+    r%x = a%x - b%x
+    r%y = a%y - b%y
+  end function pure_sub
+
+  function impure_neg(a) result(r)
+    type(vec_type), intent(in) :: a
+    type(vec_type) :: r
+    r%x = -a%x
+    r%y = -a%y
+  end function impure_neg
+
+  function impure_eq(a, b) result(same)
+    type(vec_type), intent(in) :: a, b
+    logical :: same
+    same = a%x == b%x .and. a%y == b%y
+  end function impure_eq
+
+  function impure_dot(a, b) result(d)
+    type(vec_type), intent(in) :: a, b
+    real :: d
+    d = a%x * b%x + a%y * b%y
+  end function impure_dot
+
+  pure function pure_scale_int(v, n) result(r)
+    type(vec_type), intent(in) :: v
+    integer, intent(in) :: n
+    type(vec_type) :: r
+    r%x = v%x * n
+    r%y = v%y * n
+  end function pure_scale_int
+
+  function impure_scale_real(v, s) result(r)
+    type(vec_type), intent(in) :: v
+    real, intent(in) :: s
+    type(vec_type) :: r
+    r%x = v%x * s
+    r%y = v%y * s
+  end function impure_scale_real
+
+  ! Pure, called from the ELEMENTAL and SIMPLE cases below so that the
+  ! only impurity they can trip over is the impure_add defined operator.
+  pure function pure_half(a) result(r)
+    type(vec_type), intent(in) :: a
+    type(vec_type) :: r
+    r%x = a%x * 0.5
+    r%y = a%y * 0.5
+  end function pure_half
+
+  ! ELEMENTAL is implicitly PURE unless declared IMPURE (F2023 15.7).
+  elemental function elemental_caller(p, q) result(r)
+    type(vec_type), intent(in) :: p, q
+    type(vec_type) :: r
+    ! A direct call to a pure procedure is allowed.
+    r = pure_half(p)
+    !ERROR: Procedure 'impure_add' referenced in pure subprogram 'elemental_caller' must be pure too
+    r = pure_half(p + q)
+  end function elemental_caller
+
+  ! SIMPLE implies PURE (F2023 15.8), and is stricter.
+  simple function simple_caller(p, q) result(r)
+    type(vec_type), intent(in) :: p, q
+    type(vec_type) :: r
+    ! A direct call to a pure procedure is allowed.
+    r = pure_half(p)
+    !ERROR: Procedure 'impure_add' referenced in pure subprogram 'simple_caller' must be pure too
+    r = pure_half(p + q)
+  end function simple_caller
+end module pure_op_mod
+
+function local_impure_add(a, b) result(z)
+  use pure_op_mod, only: vec_type
+  type(vec_type), intent(in) :: a, b
+  type(vec_type) :: z
+  z%x = a%x + b%x
+  z%y = a%y + b%y
+end function local_impure_add
+
+program main
+  use pure_op_mod
+  implicit none
+  type(vec_type) :: a, b, m
+  real :: d
+  logical :: l
+  a = vec_type(1.0, 2.0)
+  b = vec_type(3.0, 4.0)
+  ! Impure defined operators are fine outside a pure subprogram.
+  m = 0.5 * (a + b)
+  m = a - b
+  m = -a
+  l = a == b
+  d = a .dot. b
+  m = a .scale. 2
+  m = a .scale. 2.0
+  m = pure_sub(a, b)
+  m = pure_scale_int(a, 2)
+  m = pure_half(a)
+  m = midpoint(a, b)
+contains
+  pure function midpoint(p, q) result(r)
+    type(vec_type), intent(in) :: p, q
+    type(vec_type) :: r
+    real :: d
+    logical :: eqv
+    interface operator(.local.)
+      function local_impure_add(a, b) result(z)
+        import :: vec_type
+        type(vec_type), intent(in) :: a, b
+        type(vec_type) :: z
+      end function local_impure_add
+    end interface operator(.local.)
+    ! Direct calls to pure procedures are allowed.
+    r = pure_sub(p, q)
+    r = pure_scale_int(p, 2)
+    r = pure_half(p)
+    ! A pure binary defined operator may be referenced.
+    r = p - q
+    ! Overload resolution picks the pure specific here: no error.
+    r = p .scale. 2
+    !ERROR: Procedure 'impure_add' referenced in pure subprogram 'midpoint' must be pure too
+    !ERROR: Procedure 'impure_scale' referenced in pure subprogram 'midpoint' must be pure too
+    r = 0.5 * (p + q)
+    !ERROR: Procedure 'impure_neg' referenced in pure subprogram 'midpoint' must be pure too
+    r = -p
+    !ERROR: Procedure 'impure_eq' referenced in pure subprogram 'midpoint' must be pure too
+    eqv = p == q
+    !ERROR: Procedure 'impure_dot' referenced in pure subprogram 'midpoint' must be pure too
+    d = p .dot. q
+    !ERROR: Procedure 'impure_scale_real' referenced in pure subprogram 'midpoint' must be pure too
+    r = p .scale. 2.0
+    !ERROR: Procedure 'local_impure_add' referenced in pure subprogram 'midpoint' must be pure too
+    r = p .local. q
+    !ERROR: Procedure 'local_impure_add' referenced in pure subprogram 'midpoint' must be pure too
+    r = q .local. p
+  end function midpoint
+end program main



More information about the flang-commits mailing list