[clang] [Clang] Skip function template bodies that have no dependent context (PR #226805)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 27 09:29:22 PDT 2026


https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/226805

Fixes #219354

For `template <class T, int N> &T::foo() { ... }`, `T::` doesn't name a class, so Sema diagnoses the declarator and never creates a declaration. The parser still parses the body, but in a fake function scope that leaves `CurContext` at the translation unit while `T` and `N` are still visible. `collapse(N)` is dependent, so OpenMP holds off on building the loop expressions, and `finishLinearClauses` then asserts because the context isn't dependent. The same happens when Sema rejects a template parameter list but still builds an invalid, non-dependent declaration, like `template <int N> void A<0>::f() {}`, out of line or as an in-class friend. OpenMP is just the first thing to trip over it; this goes back to at least clang 9.

Now the body is skipped whenever template parameters are in scope but there's no dependent context to parse it in, i.e. no declaration at all or only an invalid, non-dependent one. The tokens are consumed the same way delayed template parsing already does, in `ParseFunctionDefinition` and where in-class definitions are cached. A valid function template always has a dependent context, so correct code isn't affected, and the declaration error is still reported.


>From 81af66c4082a4f1df63896bf6b764032fe31861c Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Sun, 27 Sep 2026 21:57:08 +0530
Subject: [PATCH] [Clang] Skip function template bodies that have no dependent
 context

When Sema cannot form the declaration of a function template definition,
such as `template <class T, int N> &T::foo() {}` where `T::` names no
class, the parser still parses the body in a fake function scope that
leaves CurContext at the translation unit while the template parameters
stay in scope. `collapse(N)` is then dependent in a non-dependent
context and finishLinearClauses asserts. The same happens when a
template parameter list is rejected but an invalid, non-dependent
declaration is still built, e.g. `template <int N> void A<0>::f() {}`,
out of line or as an in-class friend definition.

Skip the body in those cases, consuming its tokens the way delayed
template parsing does, in ParseFunctionDefinition and when caching
in-class definitions. Valid function templates always have a dependent
context, so they are unaffected.

Fixes #219354
---
 clang/docs/ReleaseNotes.md                |  5 +++
 clang/lib/Parse/ParseCXXInlineMethods.cpp | 10 +++++
 clang/lib/Parse/Parser.cpp                | 11 ++++++
 clang/test/OpenMP/gh219354.cpp            | 47 +++++++++++++++++++++++
 4 files changed, 73 insertions(+)
 create mode 100644 clang/test/OpenMP/gh219354.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3c6acf353f93f6..1b4b0d9f3fbf50 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -707,6 +707,11 @@ features cannot lower the translation-unit ABI level;
 - Fixed merging of lambdas across modules in the case where neither lambda is
   imported from an AST file. (#GH214560)
 
+- Fixed an assertion when the body of an invalid function template definition,
+  such as `template <class T, int N> void T::f() {}`, used an OpenMP loop
+  directive. Such bodies are now skipped instead of being analyzed as
+  non-template code. (#GH219354)
+
 - Fixed a crash when a non-type template parameter of reference type is bound
   to a subobject and is used in a context that requires an implicit conversion.
   (#GH215900)
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index 35dbec8dfb2f7c..3ddc870b045d43 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -138,6 +138,16 @@ NamedDecl *Parser::ParseCXXInlineMethodDef(
     return FnD;
   }
 
+  // The template parameters are in scope but there is no dependent context.
+  if (auto *FD = dyn_cast_if_present<FunctionDecl>(FnD);
+      FD && TemplateInfo.Kind == ParsedTemplateKind::Template &&
+      FD->isInvalidDecl() && !FD->isDependentContext()) {
+    CachedTokens Toks;
+    LexTemplateFunctionForLateParsing(Toks);
+    Actions.ActOnSkippedFunctionBody(FnD);
+    return FnD;
+  }
+
   // In delayed template parsing mode, if we are within a class template
   // or if we are about to parse function member template then consume
   // the tokens and store them for parsing at the end of the translation unit.
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index c86ed6b2ea3f98..fa0496e19d18e3 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -1405,6 +1405,17 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
     return Actions.ActOnFinishFunctionBody(Res, nullptr, false);
   }
 
+  // The template parameters are in scope but there is no dependent context.
+  if (TemplateInfo.Kind == ParsedTemplateKind::Template &&
+      (!Res ||
+       (Res->isInvalidDecl() && !Res->getAsFunction()->isDependentContext()))) {
+    CachedTokens Toks;
+    LexTemplateFunctionForLateParsing(Toks);
+    BodyScope.Exit();
+    Actions.ActOnSkippedFunctionBody(Res);
+    return Actions.ActOnFinishFunctionBody(Res, nullptr, false);
+  }
+
   return ParseFunctionBody(Res, BodyScope);
 }
 
diff --git a/clang/test/OpenMP/gh219354.cpp b/clang/test/OpenMP/gh219354.cpp
new file mode 100644
index 00000000000000..78850d6196456e
--- /dev/null
+++ b/clang/test/OpenMP/gh219354.cpp
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -fopenmp -fsyntax-only -verify %s
+
+template <class T, int N> &T::foo() { // expected-error {{nested name specifier 'T' for declaration does not refer into a class, class template or class template partial specialization}}
+#pragma omp simd collapse(N)
+  ;
+}
+
+struct S {
+  template <int N> void bar();
+};
+
+namespace NS {
+template <int N> void S::bar() { // expected-error {{cannot define or redeclare 'bar' here because namespace 'NS' does not enclose namespace 'S'}}
+#pragma omp simd collapse(N)
+  ;
+}
+}
+
+template <int M> class D;
+template <int M> template <int N> void D<M>::baz() { // expected-error {{out-of-line definition of 'baz' from class 'D<M>' without definition}}
+#pragma omp simd collapse(N)
+  ;
+}
+
+template <int> struct E {
+  void spec();
+};
+template <int N> void E<0>::spec() { // expected-error {{template parameter list matching the non-templated nested type 'E<0>' should be empty ('template<>')}}
+#pragma omp simd collapse(N)
+  ;
+}
+
+template <int> struct G {
+  void fr();
+};
+struct H {
+  template <int N> friend void G<0>::fr() { // expected-error {{template parameter list matching the non-templated nested type 'G<0>' should be empty ('template<>')}}
+#pragma omp simd collapse(N)
+    ;
+  }
+};
+
+#pragma omp declare simd // expected-error {{function declaration is expected after 'declare simd' directive}}
+template <class T, int N> void T::qux() { // expected-error {{nested name specifier 'T' for declaration does not refer into a class, class template or class template partial specialization}}
+#pragma omp simd collapse(N)
+  ;
+}



More information about the cfe-commits mailing list