Skip to content

Conversation

@akuhlens
Copy link
Contributor

@akuhlens akuhlens commented Jul 8, 2025

Adds the flag -Wfatal-errors which truncates the error messages at 1 error.

clang-format

simplify semantics

remove line numbers from test

fix error in semantics

add newline to end of tests

fix another error in semantics
@llvmbot llvmbot added flang:driver flang Flang issues not falling into any other category flang:semantics flang:parser labels Jul 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-flang-parser

@llvm/pr-subscribers-flang-driver

Author: Andre Kuhlenschmidt (akuhlens)

Changes

Adds the flag -Wfatal-errors which truncates the error messages at 1 error.


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

9 Files Affected:

  • (modified) flang/include/flang/Frontend/CompilerInvocation.h (+6-2)
  • (modified) flang/include/flang/Parser/message.h (+2-1)
  • (modified) flang/include/flang/Semantics/semantics.h (+9)
  • (modified) flang/lib/Frontend/CompilerInvocation.cpp (+5-1)
  • (modified) flang/lib/Frontend/FrontendAction.cpp (+8-6)
  • (modified) flang/lib/Parser/message.cpp (+11-2)
  • (modified) flang/lib/Semantics/semantics.cpp (+3-3)
  • (added) flang/test/Driver/fatal-errors-parsing.f90 (+16)
  • (added) flang/test/Driver/fatal-errors-semantics.f90 (+40)
diff --git a/flang/include/flang/Frontend/CompilerInvocation.h b/flang/include/flang/Frontend/CompilerInvocation.h
index 06978029435b7..d294955af780e 100644
--- a/flang/include/flang/Frontend/CompilerInvocation.h
+++ b/flang/include/flang/Frontend/CompilerInvocation.h
@@ -102,8 +102,6 @@ class CompilerInvocation : public CompilerInvocationBase {
   bool debugModuleDir = false;
   bool hermeticModuleFileOutput = false;
 
-  bool warnAsErr = false;
-
   // Executable name
   const char *argv0;
 
@@ -116,6 +114,9 @@ class CompilerInvocation : public CompilerInvocationBase {
   // Fortran Dialect options
   Fortran::common::IntrinsicTypeDefaultKinds defaultKinds;
 
+  // Fortran Error options
+  size_t maxErrors = 0;
+  bool warnAsErr = false;
   // Fortran Warning options
   bool enableConformanceChecks = false;
   bool enableUsageChecks = false;
@@ -189,6 +190,8 @@ class CompilerInvocation : public CompilerInvocationBase {
   const bool &getHermeticModuleFileOutput() const {
     return hermeticModuleFileOutput;
   }
+  size_t &getMaxErrors() { return maxErrors; }
+  const size_t &getMaxErrors() const { return maxErrors; }
 
   bool &getWarnAsErr() { return warnAsErr; }
   const bool &getWarnAsErr() const { return warnAsErr; }
@@ -261,6 +264,7 @@ class CompilerInvocation : public CompilerInvocationBase {
     hermeticModuleFileOutput = flag;
   }
 
+  void setMaxErrors(size_t maxErrors) { this->maxErrors = maxErrors; }
   void setWarnAsErr(bool flag) { warnAsErr = flag; }
 
   void setUseAnalyzedObjectsForUnparse(bool flag) {
diff --git a/flang/include/flang/Parser/message.h b/flang/include/flang/Parser/message.h
index 7371a46ad36ce..723180f63955c 100644
--- a/flang/include/flang/Parser/message.h
+++ b/flang/include/flang/Parser/message.h
@@ -354,7 +354,8 @@ class Messages {
   void ResolveProvenances(const AllCookedSources &);
   void Emit(llvm::raw_ostream &, const AllCookedSources &,
       bool echoSourceLines = true,
-      const common::LanguageFeatureControl *hintFlags = nullptr) const;
+      const common::LanguageFeatureControl *hintFlags = nullptr,
+      size_t maxErrorsToEmit = 0) const;
   void AttachTo(Message &, std::optional<Severity> = std::nullopt);
   bool AnyFatalError() const;
 
diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 730513dbe3232..3100aca353d81 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -154,6 +154,10 @@ class SemanticsContext {
     warnOnNonstandardUsage_ = x;
     return *this;
   }
+  SemanticsContext &set_maxErrors(size_t x) {
+    maxErrors_ = x;
+    return *this;
+  }
   SemanticsContext &set_warningsAreErrors(bool x) {
     warningsAreErrors_ = x;
     return *this;
@@ -167,6 +171,8 @@ class SemanticsContext {
   const DeclTypeSpec &MakeNumericType(TypeCategory, int kind = 0);
   const DeclTypeSpec &MakeLogicalType(int kind = 0);
 
+  size_t maxErrors() const { return maxErrors_; }
+
   bool AnyFatalError() const;
 
   // Test or set the Error flag on a Symbol
@@ -213,6 +219,8 @@ class SemanticsContext {
     return Warn(warning, *location_, std::forward<A>(args)...);
   }
 
+  void EmitMessages(llvm::raw_ostream &);
+
   const Scope &FindScope(parser::CharBlock) const;
   Scope &FindScope(parser::CharBlock);
   void UpdateScopeIndex(Scope &, parser::CharBlock);
@@ -322,6 +330,7 @@ class SemanticsContext {
   Scope *currentHermeticModuleFileScope_{nullptr};
   ScopeIndex scopeIndex_;
   parser::Messages messages_;
+  size_t maxErrors_{0};
   evaluate::FoldingContext foldingContext_;
   ConstructStack constructStack_;
   struct IndexVarInfo {
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index e5c6b1d5c61ad..a92afeec170a2 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1014,7 +1014,10 @@ static bool parseDiagArgs(CompilerInvocation &res, llvm::opt::ArgList &args,
     for (const auto &wArg : wArgs) {
       if (wArg == "error") {
         res.setWarnAsErr(true);
-        // -W(no-)<feature>
+        // -Wfatal-errors
+      } else if (wArg == "fatal-errors") {
+        res.setMaxErrors(1);
+        // -W[no-]<feature>
       } else if (!features.EnableWarning(wArg)) {
         const unsigned diagID = diags.getCustomDiagID(
             clang::DiagnosticsEngine::Error, "Unknown diagnostic option: -W%0");
@@ -1775,6 +1778,7 @@ CompilerInvocation::getSemanticsCtx(
   semanticsContext->set_moduleDirectory(getModuleDir())
       .set_searchDirectories(fortranOptions.searchDirectories)
       .set_intrinsicModuleDirectories(fortranOptions.intrinsicModuleDirectories)
+      .set_maxErrors(getMaxErrors())
       .set_warningsAreErrors(getWarnAsErr())
       .set_moduleFileSuffix(getModuleFileSuffix())
       .set_underscoring(getCodeGenOpts().Underscoring);
diff --git a/flang/lib/Frontend/FrontendAction.cpp b/flang/lib/Frontend/FrontendAction.cpp
index 5d788fe5619a2..2429e07e5b8c4 100644
--- a/flang/lib/Frontend/FrontendAction.cpp
+++ b/flang/lib/Frontend/FrontendAction.cpp
@@ -173,6 +173,7 @@ bool FrontendAction::runParse(bool emitMessages) {
     // combining them with messages from semantics.
     const common::LanguageFeatureControl &features{
         ci.getInvocation().getFortranOpts().features};
+    // Default maxErrors here because none are fatal.
     ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources(),
                                     /*echoSourceLine=*/true, &features);
   }
@@ -228,15 +229,16 @@ template <unsigned N>
 bool FrontendAction::reportFatalErrors(const char (&message)[N]) {
   const common::LanguageFeatureControl &features{
       instance->getInvocation().getFortranOpts().features};
+  const size_t maxErrors{instance->getInvocation().getMaxErrors()};
   if (!instance->getParsing().messages().empty() &&
       (instance->getInvocation().getWarnAsErr() ||
        instance->getParsing().messages().AnyFatalError())) {
     const unsigned diagID = instance->getDiagnostics().getCustomDiagID(
         clang::DiagnosticsEngine::Error, message);
     instance->getDiagnostics().Report(diagID) << getCurrentFileOrBufferName();
-    instance->getParsing().messages().Emit(llvm::errs(),
-                                           instance->getAllCookedSources(),
-                                           /*echoSourceLines=*/true, &features);
+    instance->getParsing().messages().Emit(
+        llvm::errs(), instance->getAllCookedSources(),
+        /*echoSourceLines=*/true, &features, maxErrors);
     return true;
   }
   if (instance->getParsing().parseTree().has_value() &&
@@ -245,9 +247,9 @@ bool FrontendAction::reportFatalErrors(const char (&message)[N]) {
     const unsigned diagID = instance->getDiagnostics().getCustomDiagID(
         clang::DiagnosticsEngine::Error, message);
     instance->getDiagnostics().Report(diagID) << getCurrentFileOrBufferName();
-    instance->getParsing().messages().Emit(llvm::errs(),
-                                           instance->getAllCookedSources(),
-                                           /*echoSourceLine=*/true, &features);
+    instance->getParsing().messages().Emit(
+        llvm::errs(), instance->getAllCookedSources(),
+        /*echoSourceLine=*/true, &features, maxErrors);
     instance->getParsing().EmitMessage(
         llvm::errs(), instance->getParsing().finalRestingPlace(),
         "parser FAIL (final position)", "error: ", llvm::raw_ostream::RED);
diff --git a/flang/lib/Parser/message.cpp b/flang/lib/Parser/message.cpp
index f2237da3857a2..dd329458ea6e6 100644
--- a/flang/lib/Parser/message.cpp
+++ b/flang/lib/Parser/message.cpp
@@ -452,8 +452,8 @@ void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
 }
 
 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
-    bool echoSourceLines,
-    const common::LanguageFeatureControl *hintFlagPtr) const {
+    bool echoSourceLines, const common::LanguageFeatureControl *hintFlagPtr,
+    size_t maxErrorsToEmit) const {
   std::vector<const Message *> sorted;
   for (const auto &msg : messages_) {
     sorted.push_back(&msg);
@@ -461,6 +461,7 @@ void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
   std::stable_sort(sorted.begin(), sorted.end(),
       [](const Message *x, const Message *y) { return x->SortBefore(*y); });
   const Message *lastMsg{nullptr};
+  size_t errorsEmitted{0};
   for (const Message *msg : sorted) {
     if (lastMsg && *msg == *lastMsg) {
       // Don't emit two identical messages for the same location
@@ -468,6 +469,14 @@ void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     }
     msg->Emit(o, allCooked, echoSourceLines, hintFlagPtr);
     lastMsg = msg;
+    if (msg->IsFatal()) {
+      ++errorsEmitted;
+    }
+    // If maxErrorsToEmit is 0, emit all errors, otherwise break after
+    // maxErrorsToEmit.
+    if (maxErrorsToEmit > 0 && errorsEmitted >= maxErrorsToEmit) {
+      break;
+    }
   }
 }
 
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index ed41c6fb16892..ab78605d01f4c 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -655,10 +655,10 @@ bool Semantics::Perform() {
 void Semantics::EmitMessages(llvm::raw_ostream &os) {
   // Resolve the CharBlock locations of the Messages to ProvenanceRanges
   // so messages from parsing and semantics are intermixed in source order.
-  const common::LanguageFeatureControl &features{context_.languageFeatures()};
   context_.messages().ResolveProvenances(context_.allCookedSources());
-  context_.messages().Emit(
-      os, context_.allCookedSources(), /*echoSourceLine=*/true, &features);
+  context_.messages().Emit(os, context_.allCookedSources(),
+      /*echoSourceLine=*/true, &context_.languageFeatures(),
+      /*maxErrorsToEmit=*/context_.maxErrors());
 }
 
 void SemanticsContext::DumpSymbols(llvm::raw_ostream &os) {
diff --git a/flang/test/Driver/fatal-errors-parsing.f90 b/flang/test/Driver/fatal-errors-parsing.f90
new file mode 100644
index 0000000000000..185a6e08481d7
--- /dev/null
+++ b/flang/test/Driver/fatal-errors-parsing.f90
@@ -0,0 +1,16 @@
+!RUN: not %flang_fc1 -fsyntax-only -Wfatal-errors %s 2>&1 | FileCheck %s --check-prefix=CHECK1
+!RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+program p
+ contains
+   ! CHECK1: fatal-errors-parsing.f90:{{.*}} error:
+   ! CHECK2: fatal-errors-parsing.f90:{{.*}} error:
+continue
+end
+   
+subroutine s
+contains
+   ! CHECK1-NOT: error:
+   ! CHECK2: fatal-errors-parsing.f90:{{.*}} error:
+continue
+end
diff --git a/flang/test/Driver/fatal-errors-semantics.f90 b/flang/test/Driver/fatal-errors-semantics.f90
new file mode 100644
index 0000000000000..54740dd6deec0
--- /dev/null
+++ b/flang/test/Driver/fatal-errors-semantics.f90
@@ -0,0 +1,40 @@
+! RUN: not %flang_fc1 %s 2>&1 | FileCheck %s --check-prefix=CHECK1
+! RUN: not %flang_fc1 -Wfatal-errors %s 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+module m
+    contains
+     subroutine s0(p)
+       real, pointer, intent(in) :: p
+     end
+     subroutine s1(p)
+       real, pointer, intent(in) :: p(:)
+     end
+     subroutine sa(p)
+       real, pointer, intent(in) :: p(..)
+     end
+     subroutine sao(p)
+       real, intent(in), optional, pointer :: p(..)
+     end
+     subroutine soa(a)
+       real, intent(in), optional, allocatable :: a(..)
+     end
+     subroutine test
+       real, pointer :: a0, a1(:)
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2: fatal-errors-semantics.f90:{{.*}} error:
+       call s0(null(a1))
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call s1(null(a0))
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call sa(null())
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call sao(null())
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call soa(null())
+     end
+   end
+   
\ No newline at end of file

@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-flang-semantics

Author: Andre Kuhlenschmidt (akuhlens)

Changes

Adds the flag -Wfatal-errors which truncates the error messages at 1 error.


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

9 Files Affected:

  • (modified) flang/include/flang/Frontend/CompilerInvocation.h (+6-2)
  • (modified) flang/include/flang/Parser/message.h (+2-1)
  • (modified) flang/include/flang/Semantics/semantics.h (+9)
  • (modified) flang/lib/Frontend/CompilerInvocation.cpp (+5-1)
  • (modified) flang/lib/Frontend/FrontendAction.cpp (+8-6)
  • (modified) flang/lib/Parser/message.cpp (+11-2)
  • (modified) flang/lib/Semantics/semantics.cpp (+3-3)
  • (added) flang/test/Driver/fatal-errors-parsing.f90 (+16)
  • (added) flang/test/Driver/fatal-errors-semantics.f90 (+40)
diff --git a/flang/include/flang/Frontend/CompilerInvocation.h b/flang/include/flang/Frontend/CompilerInvocation.h
index 06978029435b7..d294955af780e 100644
--- a/flang/include/flang/Frontend/CompilerInvocation.h
+++ b/flang/include/flang/Frontend/CompilerInvocation.h
@@ -102,8 +102,6 @@ class CompilerInvocation : public CompilerInvocationBase {
   bool debugModuleDir = false;
   bool hermeticModuleFileOutput = false;
 
-  bool warnAsErr = false;
-
   // Executable name
   const char *argv0;
 
@@ -116,6 +114,9 @@ class CompilerInvocation : public CompilerInvocationBase {
   // Fortran Dialect options
   Fortran::common::IntrinsicTypeDefaultKinds defaultKinds;
 
+  // Fortran Error options
+  size_t maxErrors = 0;
+  bool warnAsErr = false;
   // Fortran Warning options
   bool enableConformanceChecks = false;
   bool enableUsageChecks = false;
@@ -189,6 +190,8 @@ class CompilerInvocation : public CompilerInvocationBase {
   const bool &getHermeticModuleFileOutput() const {
     return hermeticModuleFileOutput;
   }
+  size_t &getMaxErrors() { return maxErrors; }
+  const size_t &getMaxErrors() const { return maxErrors; }
 
   bool &getWarnAsErr() { return warnAsErr; }
   const bool &getWarnAsErr() const { return warnAsErr; }
@@ -261,6 +264,7 @@ class CompilerInvocation : public CompilerInvocationBase {
     hermeticModuleFileOutput = flag;
   }
 
+  void setMaxErrors(size_t maxErrors) { this->maxErrors = maxErrors; }
   void setWarnAsErr(bool flag) { warnAsErr = flag; }
 
   void setUseAnalyzedObjectsForUnparse(bool flag) {
diff --git a/flang/include/flang/Parser/message.h b/flang/include/flang/Parser/message.h
index 7371a46ad36ce..723180f63955c 100644
--- a/flang/include/flang/Parser/message.h
+++ b/flang/include/flang/Parser/message.h
@@ -354,7 +354,8 @@ class Messages {
   void ResolveProvenances(const AllCookedSources &);
   void Emit(llvm::raw_ostream &, const AllCookedSources &,
       bool echoSourceLines = true,
-      const common::LanguageFeatureControl *hintFlags = nullptr) const;
+      const common::LanguageFeatureControl *hintFlags = nullptr,
+      size_t maxErrorsToEmit = 0) const;
   void AttachTo(Message &, std::optional<Severity> = std::nullopt);
   bool AnyFatalError() const;
 
diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 730513dbe3232..3100aca353d81 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -154,6 +154,10 @@ class SemanticsContext {
     warnOnNonstandardUsage_ = x;
     return *this;
   }
+  SemanticsContext &set_maxErrors(size_t x) {
+    maxErrors_ = x;
+    return *this;
+  }
   SemanticsContext &set_warningsAreErrors(bool x) {
     warningsAreErrors_ = x;
     return *this;
@@ -167,6 +171,8 @@ class SemanticsContext {
   const DeclTypeSpec &MakeNumericType(TypeCategory, int kind = 0);
   const DeclTypeSpec &MakeLogicalType(int kind = 0);
 
+  size_t maxErrors() const { return maxErrors_; }
+
   bool AnyFatalError() const;
 
   // Test or set the Error flag on a Symbol
@@ -213,6 +219,8 @@ class SemanticsContext {
     return Warn(warning, *location_, std::forward<A>(args)...);
   }
 
+  void EmitMessages(llvm::raw_ostream &);
+
   const Scope &FindScope(parser::CharBlock) const;
   Scope &FindScope(parser::CharBlock);
   void UpdateScopeIndex(Scope &, parser::CharBlock);
@@ -322,6 +330,7 @@ class SemanticsContext {
   Scope *currentHermeticModuleFileScope_{nullptr};
   ScopeIndex scopeIndex_;
   parser::Messages messages_;
+  size_t maxErrors_{0};
   evaluate::FoldingContext foldingContext_;
   ConstructStack constructStack_;
   struct IndexVarInfo {
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index e5c6b1d5c61ad..a92afeec170a2 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -1014,7 +1014,10 @@ static bool parseDiagArgs(CompilerInvocation &res, llvm::opt::ArgList &args,
     for (const auto &wArg : wArgs) {
       if (wArg == "error") {
         res.setWarnAsErr(true);
-        // -W(no-)<feature>
+        // -Wfatal-errors
+      } else if (wArg == "fatal-errors") {
+        res.setMaxErrors(1);
+        // -W[no-]<feature>
       } else if (!features.EnableWarning(wArg)) {
         const unsigned diagID = diags.getCustomDiagID(
             clang::DiagnosticsEngine::Error, "Unknown diagnostic option: -W%0");
@@ -1775,6 +1778,7 @@ CompilerInvocation::getSemanticsCtx(
   semanticsContext->set_moduleDirectory(getModuleDir())
       .set_searchDirectories(fortranOptions.searchDirectories)
       .set_intrinsicModuleDirectories(fortranOptions.intrinsicModuleDirectories)
+      .set_maxErrors(getMaxErrors())
       .set_warningsAreErrors(getWarnAsErr())
       .set_moduleFileSuffix(getModuleFileSuffix())
       .set_underscoring(getCodeGenOpts().Underscoring);
diff --git a/flang/lib/Frontend/FrontendAction.cpp b/flang/lib/Frontend/FrontendAction.cpp
index 5d788fe5619a2..2429e07e5b8c4 100644
--- a/flang/lib/Frontend/FrontendAction.cpp
+++ b/flang/lib/Frontend/FrontendAction.cpp
@@ -173,6 +173,7 @@ bool FrontendAction::runParse(bool emitMessages) {
     // combining them with messages from semantics.
     const common::LanguageFeatureControl &features{
         ci.getInvocation().getFortranOpts().features};
+    // Default maxErrors here because none are fatal.
     ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources(),
                                     /*echoSourceLine=*/true, &features);
   }
@@ -228,15 +229,16 @@ template <unsigned N>
 bool FrontendAction::reportFatalErrors(const char (&message)[N]) {
   const common::LanguageFeatureControl &features{
       instance->getInvocation().getFortranOpts().features};
+  const size_t maxErrors{instance->getInvocation().getMaxErrors()};
   if (!instance->getParsing().messages().empty() &&
       (instance->getInvocation().getWarnAsErr() ||
        instance->getParsing().messages().AnyFatalError())) {
     const unsigned diagID = instance->getDiagnostics().getCustomDiagID(
         clang::DiagnosticsEngine::Error, message);
     instance->getDiagnostics().Report(diagID) << getCurrentFileOrBufferName();
-    instance->getParsing().messages().Emit(llvm::errs(),
-                                           instance->getAllCookedSources(),
-                                           /*echoSourceLines=*/true, &features);
+    instance->getParsing().messages().Emit(
+        llvm::errs(), instance->getAllCookedSources(),
+        /*echoSourceLines=*/true, &features, maxErrors);
     return true;
   }
   if (instance->getParsing().parseTree().has_value() &&
@@ -245,9 +247,9 @@ bool FrontendAction::reportFatalErrors(const char (&message)[N]) {
     const unsigned diagID = instance->getDiagnostics().getCustomDiagID(
         clang::DiagnosticsEngine::Error, message);
     instance->getDiagnostics().Report(diagID) << getCurrentFileOrBufferName();
-    instance->getParsing().messages().Emit(llvm::errs(),
-                                           instance->getAllCookedSources(),
-                                           /*echoSourceLine=*/true, &features);
+    instance->getParsing().messages().Emit(
+        llvm::errs(), instance->getAllCookedSources(),
+        /*echoSourceLine=*/true, &features, maxErrors);
     instance->getParsing().EmitMessage(
         llvm::errs(), instance->getParsing().finalRestingPlace(),
         "parser FAIL (final position)", "error: ", llvm::raw_ostream::RED);
diff --git a/flang/lib/Parser/message.cpp b/flang/lib/Parser/message.cpp
index f2237da3857a2..dd329458ea6e6 100644
--- a/flang/lib/Parser/message.cpp
+++ b/flang/lib/Parser/message.cpp
@@ -452,8 +452,8 @@ void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
 }
 
 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
-    bool echoSourceLines,
-    const common::LanguageFeatureControl *hintFlagPtr) const {
+    bool echoSourceLines, const common::LanguageFeatureControl *hintFlagPtr,
+    size_t maxErrorsToEmit) const {
   std::vector<const Message *> sorted;
   for (const auto &msg : messages_) {
     sorted.push_back(&msg);
@@ -461,6 +461,7 @@ void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
   std::stable_sort(sorted.begin(), sorted.end(),
       [](const Message *x, const Message *y) { return x->SortBefore(*y); });
   const Message *lastMsg{nullptr};
+  size_t errorsEmitted{0};
   for (const Message *msg : sorted) {
     if (lastMsg && *msg == *lastMsg) {
       // Don't emit two identical messages for the same location
@@ -468,6 +469,14 @@ void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
     }
     msg->Emit(o, allCooked, echoSourceLines, hintFlagPtr);
     lastMsg = msg;
+    if (msg->IsFatal()) {
+      ++errorsEmitted;
+    }
+    // If maxErrorsToEmit is 0, emit all errors, otherwise break after
+    // maxErrorsToEmit.
+    if (maxErrorsToEmit > 0 && errorsEmitted >= maxErrorsToEmit) {
+      break;
+    }
   }
 }
 
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index ed41c6fb16892..ab78605d01f4c 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -655,10 +655,10 @@ bool Semantics::Perform() {
 void Semantics::EmitMessages(llvm::raw_ostream &os) {
   // Resolve the CharBlock locations of the Messages to ProvenanceRanges
   // so messages from parsing and semantics are intermixed in source order.
-  const common::LanguageFeatureControl &features{context_.languageFeatures()};
   context_.messages().ResolveProvenances(context_.allCookedSources());
-  context_.messages().Emit(
-      os, context_.allCookedSources(), /*echoSourceLine=*/true, &features);
+  context_.messages().Emit(os, context_.allCookedSources(),
+      /*echoSourceLine=*/true, &context_.languageFeatures(),
+      /*maxErrorsToEmit=*/context_.maxErrors());
 }
 
 void SemanticsContext::DumpSymbols(llvm::raw_ostream &os) {
diff --git a/flang/test/Driver/fatal-errors-parsing.f90 b/flang/test/Driver/fatal-errors-parsing.f90
new file mode 100644
index 0000000000000..185a6e08481d7
--- /dev/null
+++ b/flang/test/Driver/fatal-errors-parsing.f90
@@ -0,0 +1,16 @@
+!RUN: not %flang_fc1 -fsyntax-only -Wfatal-errors %s 2>&1 | FileCheck %s --check-prefix=CHECK1
+!RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+program p
+ contains
+   ! CHECK1: fatal-errors-parsing.f90:{{.*}} error:
+   ! CHECK2: fatal-errors-parsing.f90:{{.*}} error:
+continue
+end
+   
+subroutine s
+contains
+   ! CHECK1-NOT: error:
+   ! CHECK2: fatal-errors-parsing.f90:{{.*}} error:
+continue
+end
diff --git a/flang/test/Driver/fatal-errors-semantics.f90 b/flang/test/Driver/fatal-errors-semantics.f90
new file mode 100644
index 0000000000000..54740dd6deec0
--- /dev/null
+++ b/flang/test/Driver/fatal-errors-semantics.f90
@@ -0,0 +1,40 @@
+! RUN: not %flang_fc1 %s 2>&1 | FileCheck %s --check-prefix=CHECK1
+! RUN: not %flang_fc1 -Wfatal-errors %s 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+module m
+    contains
+     subroutine s0(p)
+       real, pointer, intent(in) :: p
+     end
+     subroutine s1(p)
+       real, pointer, intent(in) :: p(:)
+     end
+     subroutine sa(p)
+       real, pointer, intent(in) :: p(..)
+     end
+     subroutine sao(p)
+       real, intent(in), optional, pointer :: p(..)
+     end
+     subroutine soa(a)
+       real, intent(in), optional, allocatable :: a(..)
+     end
+     subroutine test
+       real, pointer :: a0, a1(:)
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2: fatal-errors-semantics.f90:{{.*}} error:
+       call s0(null(a1))
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call s1(null(a0))
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call sa(null())
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call sao(null())
+       !CHECK1: fatal-errors-semantics.f90:{{.*}} error:
+       !CHECK2-NOT: error:
+       call soa(null())
+     end
+   end
+   
\ No newline at end of file

@akuhlens akuhlens requested a review from klausler July 8, 2025 23:37
@akuhlens akuhlens merged commit fc9dd58 into llvm:main Jul 9, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:driver flang:parser 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