Skip to content

Conversation

@klausler
Copy link
Contributor

When a generic procedure reference does not match any of its specific procedures, run through them and emit the errors for each attempted match, so that the user has more information to resolve the problem by adjusting the actual arguments.

@llvmbot llvmbot added flang Flang issues not falling into any other category flang:semantics labels Oct 23, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 23, 2025

@llvm/pr-subscribers-flang-semantics

Author: Peter Klausler (klausler)

Changes

When a generic procedure reference does not match any of its specific procedures, run through them and emit the errors for each attempted match, so that the user has more information to resolve the problem by adjusting the actual arguments.


Full diff: https://github.com/llvm/llvm-project/pull/164738.diff

7 Files Affected:

  • (modified) flang/include/flang/Evaluate/call.h (+1)
  • (modified) flang/include/flang/Semantics/expression.h (+2-2)
  • (modified) flang/lib/Evaluate/formatting.cpp (+7)
  • (modified) flang/lib/Semantics/check-call.cpp (+3-4)
  • (modified) flang/lib/Semantics/check-call.h (+7-4)
  • (modified) flang/lib/Semantics/expression.cpp (+39-13)
  • (added) flang/test/Semantics/generic-error.f90 (+21)
diff --git a/flang/include/flang/Evaluate/call.h b/flang/include/flang/Evaluate/call.h
index 2a5929b873d74..fea09d6440226 100644
--- a/flang/include/flang/Evaluate/call.h
+++ b/flang/include/flang/Evaluate/call.h
@@ -112,6 +112,7 @@ class ActualArgument {
   int Rank() const;
   bool operator==(const ActualArgument &) const;
   llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
+  std::string AsFortran() const;
 
   std::optional<parser::CharBlock> keyword() const { return keyword_; }
   ActualArgument &set_keyword(parser::CharBlock x) {
diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index 95c97f264a667..3648a988254c3 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -370,8 +370,8 @@ class ExpressionAnalyzer {
   std::pair<const Symbol *, bool /* failure due ambiguity */> ResolveGeneric(
       const Symbol &, const ActualArguments &, const AdjustActuals &,
       bool isSubroutine, bool mightBeStructureConstructor = false);
-  void EmitGenericResolutionError(
-      const Symbol &, bool dueToNullActuals, bool isSubroutine);
+  void EmitGenericResolutionError(const Symbol &, bool dueToNullActuals,
+      bool isSubroutine, ActualArguments &);
   const Symbol &AccessSpecific(
       const Symbol &originalGeneric, const Symbol &specific);
   std::optional<CalleeAndArguments> GetCalleeAndArguments(const parser::Name &,
diff --git a/flang/lib/Evaluate/formatting.cpp b/flang/lib/Evaluate/formatting.cpp
index ec5dc0baaa5cb..5632015857ab3 100644
--- a/flang/lib/Evaluate/formatting.cpp
+++ b/flang/lib/Evaluate/formatting.cpp
@@ -252,6 +252,13 @@ llvm::raw_ostream &ActualArgument::AsFortran(llvm::raw_ostream &o) const {
   return o;
 }
 
+std::string ActualArgument::AsFortran() const {
+  std::string result;
+  llvm::raw_string_ostream sstream(result);
+  AsFortran(sstream);
+  return result;
+}
+
 llvm::raw_ostream &SpecificIntrinsic::AsFortran(llvm::raw_ostream &o) const {
   return o << name;
 }
diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp
index e4d2a0d220c12..c51d40b9e5039 100644
--- a/flang/lib/Semantics/check-call.cpp
+++ b/flang/lib/Semantics/check-call.cpp
@@ -2241,10 +2241,9 @@ static void CheckSpecificIntrinsic(const characteristics::Procedure &proc,
   }
 }
 
-static parser::Messages CheckExplicitInterface(
-    const characteristics::Procedure &proc, evaluate::ActualArguments &actuals,
-    SemanticsContext &context, const Scope *scope,
-    const evaluate::SpecificIntrinsic *intrinsic,
+parser::Messages CheckExplicitInterface(const characteristics::Procedure &proc,
+    evaluate::ActualArguments &actuals, SemanticsContext &context,
+    const Scope *scope, const evaluate::SpecificIntrinsic *intrinsic,
     bool allowActualArgumentConversions, bool extentErrors,
     bool ignoreImplicitVsExplicit) {
   evaluate::FoldingContext &foldingContext{context.foldingContext()};
diff --git a/flang/lib/Semantics/check-call.h b/flang/lib/Semantics/check-call.h
index 46bc61a601bd3..a69b792b646e6 100644
--- a/flang/lib/Semantics/check-call.h
+++ b/flang/lib/Semantics/check-call.h
@@ -12,11 +12,8 @@
 #define FORTRAN_SEMANTICS_CHECK_CALL_H_
 
 #include "flang/Evaluate/call.h"
+#include "flang/Parser/message.h"
 
-namespace Fortran::parser {
-class Messages;
-class ContextualMessages;
-} // namespace Fortran::parser
 namespace Fortran::evaluate::characteristics {
 struct Procedure;
 }
@@ -47,6 +44,12 @@ bool CheckArgumentIsConstantExprInRange(
     const evaluate::ActualArguments &actuals, int index, int lowerBound,
     int upperBound, parser::ContextualMessages &messages);
 
+parser::Messages CheckExplicitInterface(
+    const evaluate::characteristics::Procedure &, evaluate::ActualArguments &,
+    SemanticsContext &, const Scope *, const evaluate::SpecificIntrinsic *,
+    bool allowActualArgumentConversions, bool extentErrors,
+    bool ignoreImplicitVsExplicit);
+
 // Checks actual arguments for the purpose of resolving a generic interface.
 bool CheckInterfaceForGeneric(const evaluate::characteristics::Procedure &,
     evaluate::ActualArguments &, SemanticsContext &,
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 4aeb9a44088e2..d20d52d6df8eb 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -2556,7 +2556,8 @@ auto ExpressionAnalyzer::AnalyzeProcedureComponentRef(
               ResolveGeneric(generic, arguments, adjustment, isSubroutine)};
           sym = pair.first;
           if (!sym) {
-            EmitGenericResolutionError(generic, pair.second, isSubroutine);
+            EmitGenericResolutionError(
+                generic, pair.second, isSubroutine, arguments);
             return std::nullopt;
           }
           // re-resolve the name to the specific binding
@@ -3098,16 +3099,39 @@ const Symbol &ExpressionAnalyzer::AccessSpecific(
   }
 }
 
-void ExpressionAnalyzer::EmitGenericResolutionError(
-    const Symbol &symbol, bool dueToAmbiguity, bool isSubroutine) {
-  Say(dueToAmbiguity
-          ? "The actual arguments to the generic procedure '%s' matched multiple specific procedures, perhaps due to use of NULL() without MOLD= or an actual procedure with an implicit interface"_err_en_US
-          : semantics::IsGenericDefinedOp(symbol)
-          ? "No specific procedure of generic operator '%s' matches the actual arguments"_err_en_US
-          : isSubroutine
-          ? "No specific subroutine of generic '%s' matches the actual arguments"_err_en_US
-          : "No specific function of generic '%s' matches the actual arguments"_err_en_US,
-      symbol.name());
+void ExpressionAnalyzer::EmitGenericResolutionError(const Symbol &symbol,
+    bool dueToAmbiguity, bool isSubroutine, ActualArguments &arguments) {
+  if (auto *msg{Say(dueToAmbiguity
+              ? "The actual arguments to the generic procedure '%s' matched multiple specific procedures, perhaps due to use of NULL() without MOLD= or an actual procedure with an implicit interface"_err_en_US
+              : semantics::IsGenericDefinedOp(symbol)
+              ? "No specific procedure of generic operator '%s' matches the actual arguments"_err_en_US
+              : isSubroutine
+              ? "No specific subroutine of generic '%s' matches the actual arguments"_err_en_US
+              : "No specific function of generic '%s' matches the actual arguments"_err_en_US,
+          symbol.name())}) {
+    if (const auto *generic{
+            symbol.GetUltimate().detailsIf<semantics::GenericDetails>()}) {
+      parser::ContextualMessages &messages{GetContextualMessages()};
+      semantics::Scope &scope{context_.FindScope(messages.at())};
+      for (const Symbol &specific : generic->specificProcs()) {
+        if (auto procChars{characteristics::Procedure::Characterize(
+                specific, GetFoldingContext())}) {
+          if (auto reasons{semantics::CheckExplicitInterface(*procChars,
+                  arguments, context_, &scope, /*intrinsic=*/nullptr,
+                  /*allocActualARgumentConversions=*/false,
+                  /*extentErrors=*/false,
+                  /*ignoreImplicitVsExplicit=*/false)};
+              !reasons.empty()) {
+            reasons.AttachTo(
+                msg->Attach(specific.name(),
+                    "Specific procedure '%s' does not match the actual arguments"_en_US,
+                    specific.name()),
+                parser::Severity::None);
+          }
+        }
+      }
+    }
+  }
 }
 
 auto ExpressionAnalyzer::GetCalleeAndArguments(
@@ -3182,7 +3206,8 @@ auto ExpressionAnalyzer::GetCalleeAndArguments(const parser::Name &name,
           std::move(specificCall->arguments)};
     } else {
       if (isGenericInterface) {
-        EmitGenericResolutionError(*symbol, dueToAmbiguity, isSubroutine);
+        EmitGenericResolutionError(
+            *symbol, dueToAmbiguity, isSubroutine, arguments);
       }
       return std::nullopt;
     }
@@ -5115,7 +5140,8 @@ const Symbol *ArgumentAnalyzer::FindBoundOp(parser::CharBlock oprName,
       if (isAmbiguous) {
         *isAmbiguous = pair.second;
       }
-      context_.EmitGenericResolutionError(*generic, pair.second, isSubroutine);
+      context_.EmitGenericResolutionError(
+          *generic, pair.second, isSubroutine, actuals_);
     }
   }
   return nullptr;
diff --git a/flang/test/Semantics/generic-error.f90 b/flang/test/Semantics/generic-error.f90
new file mode 100644
index 0000000000000..25c0410a938c5
--- /dev/null
+++ b/flang/test/Semantics/generic-error.f90
@@ -0,0 +1,21 @@
+! RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s
+module m
+  interface generic
+    procedure :: sub1, sub2
+  end interface
+ contains
+  subroutine sub1(x)
+  end
+  subroutine sub2(j)
+  end
+end
+
+program test
+  use m
+!CHECK: error: No specific subroutine of generic 'generic' matches the actual arguments
+!CHECK: Specific procedure 'sub1' does not match the actual arguments
+!CHECK: Actual argument type 'REAL(8)' is not compatible with dummy argument type 'REAL(4)'
+!CHECK: Specific procedure 'sub2' does not match the actual arguments
+!CHECK: Actual argument type 'REAL(8)' is not compatible with dummy argument type 'INTEGER(4)'
+  call generic(1.d0)
+end

@github-actions
Copy link

github-actions bot commented Oct 24, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@klausler klausler force-pushed the generic-error branch 2 times, most recently from 60f33b7 to 1bf4a65 Compare October 24, 2025 14:39
When a generic procedure reference does not match any of its
specific procedures, run through them and emit the errors for each
attempted match, so that the user has more information to
resolve the problem by adjusting the actual arguments.
@klausler klausler merged commit e34d603 into llvm:main Oct 24, 2025
10 checks passed
@klausler klausler deleted the generic-error branch October 24, 2025 19:15
dvbuka pushed a commit to dvbuka/llvm-project that referenced this pull request Oct 27, 2025
When a generic procedure reference does not match any of its specific
procedures, run through them and emit the errors for each attempted
match, so that the user has more information to resolve the problem by
adjusting the actual arguments.
Lukacma pushed a commit to Lukacma/llvm-project that referenced this pull request Oct 29, 2025
When a generic procedure reference does not match any of its specific
procedures, run through them and emit the errors for each attempted
match, so that the user has more information to resolve the problem by
adjusting the actual arguments.
aokblast pushed a commit to aokblast/llvm-project that referenced this pull request Oct 30, 2025
When a generic procedure reference does not match any of its specific
procedures, run through them and emit the errors for each attempted
match, so that the user has more information to resolve the problem by
adjusting the actual arguments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:semantics flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants