Skip to content

[clang-tidy] Extend readability-else-after-return to remove else after calls to [[noreturn]] functions#185202

Open
berkaysahiin wants to merge 2 commits intollvm:mainfrom
berkaysahiin:fix-gh-184930
Open

[clang-tidy] Extend readability-else-after-return to remove else after calls to [[noreturn]] functions#185202
berkaysahiin wants to merge 2 commits intollvm:mainfrom
berkaysahiin:fix-gh-184930

Conversation

@berkaysahiin
Copy link
Contributor

Closes #184930

@llvmbot
Copy link
Member

llvmbot commented Mar 7, 2026

@llvm/pr-subscribers-clang-tidy

Author: Berkay Sahin (berkaysahiin)

Changes

Closes #184930


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

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp (+21-1)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+7-2)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp (+12)
diff --git a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
index 7e93d619e2a9f..4160f0fcf1dc1 100644
--- a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
@@ -39,6 +39,23 @@ class PPConditionalCollector : public PPCallbacks {
   const SourceManager &SM;
 };
 
+bool isNoReturnStmt(const Stmt &Stmt) {
+  const auto *Call = dyn_cast<CallExpr>(&Stmt);
+  if (!Call)
+    return false;
+
+  const FunctionDecl *Func = Call->getDirectCallee();
+  if (!Func)
+    return false;
+
+  return Func->isNoReturn();
+}
+
+AST_MATCHER(Stmt, isNoReturnStmt) {
+  const Stmt &S = Node;
+  return isNoReturnStmt(S);
+}
+
 AST_MATCHER_P(Stmt, stripLabelLikeStatements,
               ast_matchers::internal::Matcher<Stmt>, InnerMatcher) {
   const Stmt *S = Node.stripLabelLikeStatements();
@@ -174,7 +191,8 @@ void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM,
 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
   const auto InterruptsControlFlow = stmt(anyOf(
       returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
-      breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));
+      breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr),
+      stmt(isNoReturnStmt()).bind(InterruptingStr)));
 
   const auto IfWithInterruptingThenElse =
       ifStmt(unless(isConstexpr()), unless(isConsteval()),
@@ -237,6 +255,8 @@ static StringRef getControlFlowString(const Stmt &Stmt) {
     return "break";
   if (isa<CXXThrowExpr>(Stmt))
     return "throw";
+  if (isNoReturnStmt(Stmt))
+    return "noreturn";
   llvm_unreachable("Unknown control flow interrupter");
 }
 
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b0b4cd646c3bd..33437d2004079 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -295,8 +295,13 @@ Changes in existing checks
   when a member expression has a non-identifier name.
 
 - Improved :doc:`readability-else-after-return
-  <clang-tidy/checks/readability/else-after-return>` check by fixing missed
-  diagnostics when ``if`` statements appear in unbraced ``switch`` case labels.
+  <clang-tidy/checks/readability/else-after-return>` check:
+
+  - Fixed missed diagnostics when ``if`` statements appear in unbraced
+    ``switch`` case labels.
+
+  - Diagnose and remove redundant ``else`` branches after calls to
+    ``[[noreturn]]`` functions.
 
 - Improved :doc:`readability-enum-initial-value
   <clang-tidy/checks/readability/enum-initial-value>` check: the warning message
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
index e987687a764cd..f0788dbf3221b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
@@ -438,3 +438,15 @@ void testLabels(bool b) {
       f(0);
   }
 }
+
+[[noreturn]] void noReturn();
+
+void testNoReturn() {
+  if (true) {
+    noReturn();
+  } else { // comment-28
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'noreturn'
+    // CHECK-FIXES: {{^}}  } // comment-28
+    f(0);
+  }
+}

@llvmbot
Copy link
Member

llvmbot commented Mar 7, 2026

@llvm/pr-subscribers-clang-tools-extra

Author: Berkay Sahin (berkaysahiin)

Changes

Closes #184930


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

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp (+21-1)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+7-2)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp (+12)
diff --git a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
index 7e93d619e2a9f..4160f0fcf1dc1 100644
--- a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
@@ -39,6 +39,23 @@ class PPConditionalCollector : public PPCallbacks {
   const SourceManager &SM;
 };
 
+bool isNoReturnStmt(const Stmt &Stmt) {
+  const auto *Call = dyn_cast<CallExpr>(&Stmt);
+  if (!Call)
+    return false;
+
+  const FunctionDecl *Func = Call->getDirectCallee();
+  if (!Func)
+    return false;
+
+  return Func->isNoReturn();
+}
+
+AST_MATCHER(Stmt, isNoReturnStmt) {
+  const Stmt &S = Node;
+  return isNoReturnStmt(S);
+}
+
 AST_MATCHER_P(Stmt, stripLabelLikeStatements,
               ast_matchers::internal::Matcher<Stmt>, InnerMatcher) {
   const Stmt *S = Node.stripLabelLikeStatements();
@@ -174,7 +191,8 @@ void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM,
 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
   const auto InterruptsControlFlow = stmt(anyOf(
       returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
-      breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));
+      breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr),
+      stmt(isNoReturnStmt()).bind(InterruptingStr)));
 
   const auto IfWithInterruptingThenElse =
       ifStmt(unless(isConstexpr()), unless(isConsteval()),
@@ -237,6 +255,8 @@ static StringRef getControlFlowString(const Stmt &Stmt) {
     return "break";
   if (isa<CXXThrowExpr>(Stmt))
     return "throw";
+  if (isNoReturnStmt(Stmt))
+    return "noreturn";
   llvm_unreachable("Unknown control flow interrupter");
 }
 
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b0b4cd646c3bd..33437d2004079 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -295,8 +295,13 @@ Changes in existing checks
   when a member expression has a non-identifier name.
 
 - Improved :doc:`readability-else-after-return
-  <clang-tidy/checks/readability/else-after-return>` check by fixing missed
-  diagnostics when ``if`` statements appear in unbraced ``switch`` case labels.
+  <clang-tidy/checks/readability/else-after-return>` check:
+
+  - Fixed missed diagnostics when ``if`` statements appear in unbraced
+    ``switch`` case labels.
+
+  - Diagnose and remove redundant ``else`` branches after calls to
+    ``[[noreturn]]`` functions.
 
 - Improved :doc:`readability-enum-initial-value
   <clang-tidy/checks/readability/enum-initial-value>` check: the warning message
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
index e987687a764cd..f0788dbf3221b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/else-after-return.cpp
@@ -438,3 +438,15 @@ void testLabels(bool b) {
       f(0);
   }
 }
+
+[[noreturn]] void noReturn();
+
+void testNoReturn() {
+  if (true) {
+    noReturn();
+  } else { // comment-28
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'noreturn'
+    // CHECK-FIXES: {{^}}  } // comment-28
+    f(0);
+  }
+}

@github-actions
Copy link

github-actions bot commented Mar 7, 2026

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

Copy link
Contributor

@localspook localspook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is getting a lot of love this release ^_^

// CHECK-FIXES: {{^}} } // comment-28
f(0);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test with a member function marked [[noreturn]]?

returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));
breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr),
stmt(isNoReturnStmt()).bind(InterruptingStr)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could just be written as:

Suggested change
stmt(isNoReturnStmt()).bind(InterruptingStr)));
callExpr(callee(functionDecl(isNoReturn()))).bind(InterruptingStr)));

with no need for the custom matchers.

Comment on lines +258 to +259
if (isNoReturnStmt(Stmt))
return "noreturn";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message

do not use 'else' after 'noreturn'

seems a bit opaque. I would suggest something like

do not use 'else' after calling a function that doesn't return

You'll have to remove the single quotes around %0 here:

static constexpr char WarningMessage[] = "do not use 'else' after '%0'";

and move them into getControlFlowString.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang-tidy] Extend readability-else-after-return to remove else after calls to [[noreturn]] functions

3 participants