https://github.com/Sirraide updated 
https://github.com/llvm/llvm-project/pull/217110

>From a2a9a70d4e17f0d6b73a708b399036935b444860 Mon Sep 17 00:00:00 2001
From: Ambrose Leeb <[email protected]>
Date: Tue, 18 Aug 2026 21:48:14 +0200
Subject: [PATCH 1/4] [Clang] Fix a number of issues involving expansion
 statements

This fixes a number of problems around expansion statements, most
of which arise from the fact that we check if `CurContext` is a
`FunctionDecl` (which it isn't inside of an expansion statement)
and then complain that we're not inside a function (even though we
are).

I also added a helper to `DeclContext` to check if we're in a
function/block/ObjC method while ignoring any intervening expansion
statements, as well as few to cast a `DeclContext` to a `FunctionDecl`
(also while ignoring expansion statements).
---
 clang/docs/ReleaseNotes.md                    |   6 +
 clang/include/clang/AST/DeclBase.h            |  24 +++
 clang/include/clang/AST/DeclCXX.h             |   2 +-
 clang/lib/AST/ByteCode/Interp.h               |   4 +-
 clang/lib/AST/Decl.cpp                        |   4 +-
 clang/lib/AST/ExprConstant.cpp                |   4 +-
 clang/lib/Sema/SemaChecking.cpp               |   5 +-
 clang/lib/Sema/SemaCoroutine.cpp              |  16 +-
 clang/lib/Sema/SemaDecl.cpp                   |  11 +-
 clang/lib/Sema/SemaDeclCXX.cpp                |  30 ++-
 clang/lib/Sema/SemaExpr.cpp                   |   3 +-
 clang/lib/Sema/SemaType.cpp                   |   2 +-
 .../cxx2c-expansion-stmts-warnings.cpp        |  16 ++
 clang/test/SemaCXX/cxx2c-expansion-stmts.cpp  | 200 ++++++++++++++++++
 14 files changed, 296 insertions(+), 31 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index e4a6f72f8fec5..ea7b7ff9391d7 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -476,6 +476,12 @@ 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 a number issues arising from the fact that Clang considered the body of
+  an expansion statement to not be inside a function in some contexts. Several
+  constructs that were previously incorrectly rejected inside expansion 
statements
+  (e.g. `thread_local` variables, `va_start`, and 
`co_await`/`co_yield`/`co_return`)
+  are now accepted, and vice versa.
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/AST/DeclBase.h 
b/clang/include/clang/AST/DeclBase.h
index 9d233be282dbb..0e0c99cec389b 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2188,6 +2188,30 @@ class DeclContext {
     }
   }
 
+  /// Test whether we're directly inside a function or method, but ignoring
+  /// any intervening expansion statements.
+  bool isInsideFunctionOrMethod() const {
+    return getEnclosingNonExpansionStatementContext()->isFunctionOrMethod();
+  }
+
+  /// Cast this to a FunctionDecl if it is one, ignoring any intervening
+  /// expansion statements. Returns nullptr if this is not a function.
+  FunctionDecl *getAsFunctionDecl() {
+    return dyn_cast<FunctionDecl>(getEnclosingNonExpansionStatementContext());
+  }
+
+  const FunctionDecl *getAsFunctionDecl() const {
+    return dyn_cast<FunctionDecl>(getEnclosingNonExpansionStatementContext());
+  }
+
+  FunctionDecl *castAsFunctionDecl() {
+    return cast<FunctionDecl>(getEnclosingNonExpansionStatementContext());
+  }
+
+  const FunctionDecl *castAsFunctionDecl() const {
+    return cast<FunctionDecl>(getEnclosingNonExpansionStatementContext());
+  }
+
   /// Test whether the context supports looking up names.
   bool isLookupContext() const {
     return !isFunctionOrMethod() && getDeclKind() != Decl::LinkageSpec &&
diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index a42884be71d68..13969828029df 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1574,7 +1574,7 @@ class CXXRecordDecl : public RecordDecl {
     if (const auto *RD = dyn_cast<CXXRecordDecl>(getDeclContext()))
       return RD->isLocalClass();
 
-    return dyn_cast<FunctionDecl>(getDeclContext());
+    return getDeclContext()->getAsFunctionDecl();
   }
 
   FunctionDecl *isLocalClass() {
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 054fba2c87c45..f45174e1c86f9 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2748,8 +2748,8 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC, uint32_t 
ElemSize) {
         return false;
       }
 
-      if (LHSAddrExpr->getLabel()->getDeclContext() !=
-          RHSAddrExpr->getLabel()->getDeclContext())
+      if (LHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl() !=
+          RHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl())
         return Invalid(S, OpPC);
 
       S.Stk.push<T>(LHSAddrExpr, RHSAddrExpr);
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 152c621bc1ef4..15a2fc40bd887 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1106,7 +1106,7 @@ bool NamedDecl::isPlaceholderVar(const LangOptions 
&LangOpts) const {
   if (isa<FieldDecl>(this))
     return true;
   if (const auto *IFD = dyn_cast<IndirectFieldDecl>(this)) {
-    if (!getDeclContext()->isFunctionOrMethod() &&
+    if (!getDeclContext()->isInsideFunctionOrMethod() &&
         !getDeclContext()->isRecord())
       return false;
     const VarDecl *VD = IFD->getVarDecl();
@@ -1121,7 +1121,7 @@ bool NamedDecl::isPlaceholderVar(const LangOptions 
&LangOpts) const {
     return VD->getStorageDuration() == StorageDuration::SD_Automatic;
   }
   if (const auto *BD = dyn_cast<BindingDecl>(this);
-      BD && getDeclContext()->isFunctionOrMethod()) {
+      BD && getDeclContext()->isInsideFunctionOrMethod()) {
     const VarDecl *VD = BD->getHoldingVar();
     return !VD || VD->getStorageDuration() == StorageDuration::SD_Automatic;
   }
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 480d5119a5363..67cffe0fc46b8 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -19007,8 +19007,8 @@ bool DataRecursiveIntBinOpEvaluator::
     if (!LHSAddrExpr || !RHSAddrExpr)
       return false;
     // Make sure both labels come from the same function.
-    if (LHSAddrExpr->getLabel()->getDeclContext() !=
-        RHSAddrExpr->getLabel()->getDeclContext())
+    if (LHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl() !=
+        RHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl())
       return false;
     Result = APValue(LHSAddrExpr, RHSAddrExpr);
     return true;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index f2f38c84dc5f8..17a04c391b169 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6180,7 +6180,8 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, 
Expr *Fn,
   // and get its parameter list.
   bool IsVariadic = false;
   ArrayRef<ParmVarDecl *> Params;
-  DeclContext *Caller = S.CurContext;
+  DeclContext *Caller =
+      S.CurContext->getEnclosingNonExpansionStatementContext();
   if (auto *Block = dyn_cast<BlockDecl>(Caller)) {
     IsVariadic = Block->isVariadic();
     Params = Block->parameters();
@@ -7828,7 +7829,7 @@ static bool CheckMissingFormatAttribute(
   if (S->getDiagnostics().isIgnored(diag::warn_missing_format_attribute, Loc))
     return false;
 
-  DeclContext *DC = S->CurContext;
+  DeclContext *DC = S->CurContext->getEnclosingNonExpansionStatementContext();
   if (!isa<ObjCMethodDecl>(DC) && !isa<FunctionDecl>(DC) && 
!isa<BlockDecl>(DC))
     return false;
   Decl *Caller = cast<Decl>(DC)->getCanonicalDecl();
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index 48ee5cc0b0836..7879f55f091ca 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -186,7 +186,7 @@ static bool isValidCoroutineContext(Sema &S, SourceLocation 
Loc,
   // appear in a default argument." But the diagnostic QoI here could be
   // improved to inform the user that default arguments specifically are not
   // allowed.
-  auto *FD = dyn_cast<FunctionDecl>(S.CurContext);
+  auto FD = S.CurContext->getAsFunctionDecl();
   if (!FD) {
     S.Diag(Loc, isa<ObjCMethodDecl>(S.CurContext)
                     ? diag::err_coroutine_objc_method
@@ -464,8 +464,7 @@ static ExprResult buildPromiseCall(Sema &S, VarDecl 
*Promise,
 }
 
 VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
-  assert(isa<FunctionDecl>(CurContext) && "not in a function scope");
-  auto *FD = cast<FunctionDecl>(CurContext);
+  auto *FD = CurContext->castAsFunctionDecl();
   bool IsThisDependentType = [&] {
     if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(FD))
       return MD->isImplicitObjectMemberFunction() &&
@@ -573,7 +572,7 @@ static FunctionScopeInfo *checkCoroutineContext(Sema &S, 
SourceLocation Loc,
   if (!isValidCoroutineContext(S, Loc, Keyword))
     return nullptr;
 
-  assert(isa<FunctionDecl>(S.CurContext) && "not in a function scope");
+  assert(S.CurContext->getAsFunctionDecl() && "not in a function scope");
 
   auto *ScopeInfo = S.getCurFunction();
   assert(ScopeInfo && "missing function scope for function");
@@ -620,7 +619,7 @@ static void checkNoThrow(Sema &S, const Stmt *E,
         //   potentially-throwing ([except.spec]).
         //
         // First time seeing an error, emit the error message.
-        S.Diag(cast<FunctionDecl>(S.CurContext)->getLocation(),
+        S.Diag(S.CurContext->castAsFunctionDecl()->getLocation(),
                diag::err_coroutine_promise_final_suspend_requires_nothrow);
       }
       ThrowingDecls.insert(D);
@@ -691,7 +690,7 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, 
SourceLocation KWLoc,
   // Ignore previous expr evaluation contexts.
   EnterExpressionEvaluationContextForFunction PotentiallyEvaluated(
       *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
-      dyn_cast_or_null<FunctionDecl>(CurContext));
+      CurContext->getAsFunctionDecl());
 
   if (!checkCoroutineContext(*this, KWLoc, Keyword))
     return false;
@@ -716,7 +715,7 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, 
SourceLocation KWLoc,
 
   ScopeInfo->setNeedsCoroutineSuspends(false);
 
-  auto *Fn = cast<FunctionDecl>(CurContext);
+  auto *Fn = CurContext->castAsFunctionDecl();
   SourceLocation Loc = Fn->getLocation();
   // Build the initial suspend point
   auto buildSuspends = [&](StringRef Name) mutable -> StmtResult {
@@ -1968,8 +1967,7 @@ static VarDecl *buildVarDecl(Sema &S, SourceLocation Loc, 
QualType Type,
 // Build statements that move coroutine function parameters to the coroutine
 // frame, and store them on the function scope info.
 bool Sema::buildCoroutineParameterMoves(SourceLocation Loc) {
-  assert(isa<FunctionDecl>(CurContext) && "not in a function scope");
-  auto *FD = cast<FunctionDecl>(CurContext);
+  auto *FD = CurContext->castAsFunctionDecl();
 
   auto *ScopeInfo = getCurFunction();
   if (!ScopeInfo->CoroutineParameterMoves.empty())
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d87710d3cf140..d8997c41e2ad8 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2054,7 +2054,7 @@ static bool ShouldDiagnoseUnusedDecl(const LangOptions 
&LangOpts,
 
   // Except for labels, we only care about unused decls that are local to
   // functions.
-  bool WithinFunction = D->getDeclContext()->isFunctionOrMethod();
+  bool WithinFunction = D->getDeclContext()->isInsideFunctionOrMethod();
   if (const auto *R = dyn_cast<CXXRecordDecl>(D->getDeclContext()))
     // For dependent types, the diagnostic is deferred.
     WithinFunction =
@@ -6410,6 +6410,7 @@ bool Sema::diagnoseQualifiedDeclaration(CXXScopeSpec &SS, 
DeclContext *DC,
   // declaration. For a template-id, we perform the checks in
   // CheckTemplateSpecializationScope.
   if (!Cur->Encloses(DC) && !(TemplateId || IsMemberSpecialization)) {
+    Cur = Cur->getEnclosingNonExpansionStatementContext();
     if (Cur->isRecord())
       Diag(Loc, diag::err_member_qualification)
         << Name << SS.getRange();
@@ -8115,7 +8116,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     if (!getLangOpts().CPlusPlus) {
       Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
           << 0;
-    } else if (CurContext->isFunctionOrMethod()) {
+    } else if (CurContext->isInsideFunctionOrMethod()) {
       // 'inline' is not allowed on block scope variable declaration.
       Diag(D.getDeclSpec().getInlineSpecLoc(),
            diag::err_inline_declaration_block_scope) << Name
@@ -8153,7 +8154,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     if (NewVD->hasLocalStorage() &&
         (SCSpec != DeclSpec::SCS_unspecified ||
          TSCS != DeclSpec::TSCS_thread_local ||
-         !DC->isFunctionOrMethod()))
+         !DC->isInsideFunctionOrMethod()))
       Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
            diag::err_thread_non_global)
         << DeclSpec::getSpecifierName(TSCS);
@@ -9592,7 +9593,7 @@ static StorageClass getFunctionStorageClass(Sema 
&SemaRef, Declarator &D) {
       return SC_None;
     return SC_Extern;
   case DeclSpec::SCS_static: {
-    if (SemaRef.CurContext->getRedeclContext()->isFunctionOrMethod()) {
+    if (SemaRef.CurContext->getRedeclContext()->isInsideFunctionOrMethod()) {
       // C99 6.7.1p5:
       //   The declaration of an identifier for a function that has
       //   block scope shall have no explicit storage-class specifier
@@ -10423,7 +10424,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, 
DeclContext *DC,
     //  The inline specifier shall not appear on a block scope function
     //  declaration.
     if (isInline && !NewFD->isInvalidDecl()) {
-      if (CurContext->isFunctionOrMethod()) {
+      if (CurContext->isInsideFunctionOrMethod()) {
         // 'inline' is not allowed on block scope function declaration.
         Diag(D.getDeclSpec().getInlineSpecLoc(),
              diag::err_inline_declaration_block_scope) << Name
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index dd95f9220bb9d..df3cdba48619b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -640,7 +640,9 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, 
FunctionDecl *Old,
           << (New->getTemplateSpecializationKind() 
==TSK_ExplicitSpecialization)
           << New->getDeclName()
           << NewParam->getDefaultArgRange();
-      } else if (New->getDeclContext()->isDependentContext()) {
+      } else if (New->getDeclContext()
+                     ->getEnclosingNonExpansionStatementContext()
+                     ->isDependentContext()) {
         // C++ [dcl.fct.default]p6 (DR217):
         //   Default arguments for a member function of a class template shall
         //   be specified on the initial declaration of the member function
@@ -2068,9 +2070,6 @@ static bool CheckConstexprDeclStmt(Sema &SemaRef, const 
FunctionDecl *Dcl,
       //   - using-enum-declaration
       continue;
 
-    case Decl::CXXExpansionStmt:
-      continue;
-
     case Decl::Typedef:
     case Decl::TypeAlias: {
       //   - typedef declarations and alias-declarations that do not define
@@ -2257,15 +2256,34 @@ CheckConstexprFunctionStmt(Sema &SemaRef, const 
FunctionDecl *Dcl, Stmt *S,
     //   - null statements,
     return true;
 
-  case Stmt::DeclStmtClass:
+  case Stmt::DeclStmtClass: {
+    auto *DS = cast<DeclStmt>(S);
+
+    // Expansion statement 'declarations' have substatements, so we need to
+    // handle them separately.
+    if (DS->isSingleDecl()) {
+      if (auto *ESD = dyn_cast<CXXExpansionStmtDecl>(DS->getSingleDecl())) {
+        // Don't check unexpanded expansion statements.
+        if (!ESD->getInstantiations())
+          return true;
+        for (auto *BodyIt : ESD->getInstantiations()->getInstantiations()) {
+          if (!CheckConstexprFunctionStmt(SemaRef, Dcl, BodyIt, ReturnStmts,
+                                          Cxx1yLoc, Cxx2aLoc, Cxx2bLoc, Kind))
+            return false;
+        }
+        return true;
+      }
+    }
+
     //   - static_assert-declarations
     //   - using-declarations,
     //   - using-directives,
     //   - typedef declarations and alias-declarations that do not define
     //     classes or enumerations,
-    if (!CheckConstexprDeclStmt(SemaRef, Dcl, cast<DeclStmt>(S), Cxx1yLoc, 
Kind))
+    if (!CheckConstexprDeclStmt(SemaRef, Dcl, DS, Cxx1yLoc, Kind))
       return false;
     return true;
+  }
 
   case Stmt::ReturnStmtClass:
     //   - and exactly one return statement;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index f25829ae676dc..3333993a4566e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19335,7 +19335,8 @@ void Sema::MarkCaptureUsedInEnclosingContext(ValueDecl 
*Capture,
 static void diagnoseUncapturableValueReferenceOrBinding(Sema &S,
                                                         SourceLocation loc,
                                                         ValueDecl *var) {
-  DeclContext *VarDC = var->getDeclContext();
+  DeclContext *VarDC =
+      var->getDeclContext()->getEnclosingNonExpansionStatementContext();
 
   //  If the parameter still belongs to the translation unit, then
   //  we're actually just using one parameter in the declaration of
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index f9033ecb48581..0c5becf5379f7 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -3551,7 +3551,7 @@ static void warnAboutAmbiguousFunction(Sema &S, 
Declarator &D,
   // doesn't have a storage class (such as 'extern') specified.
   if (!D.isFunctionDeclarator() ||
       D.getFunctionDefinitionKind() != FunctionDefinitionKind::Declaration ||
-      !S.CurContext->isFunctionOrMethod() ||
+      !S.CurContext->isInsideFunctionOrMethod() ||
       D.getDeclSpec().getStorageClassSpec() != DeclSpec::SCS_unspecified)
     return;
 
diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp 
b/clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp
new file mode 100644
index 0000000000000..6ef468f2cd1fb
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify=expected,old-interp
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify=expected,new-interp 
-fexperimental-new-constant-interpreter
+
+// Test that checks for warnings that should be emitted in expansion 
statements,
+// but which are suppressed if we saw an error (which is why they're in a 
separate
+// file).
+
+#pragma GCC diagnostic warning "-Wunused-variable"
+#pragma GCC diagnostic warning "-Wunused-local-typedefs"
+void unused() {
+  template for (int init_stmt; int expansion_var : {0}) { // expected-warning 
{{unused variable 'init_stmt'}} expected-warning {{unused variable 
'expansion_var'}}
+    int unused_var; // expected-warning {{unused variable 'unused_var'}}
+    using unused_type = int; // expected-warning {{unused type alias 
'unused_type'}}
+    typedef int unused_typedef; // expected-warning {{unused typedef 
'unused_typedef'}}
+  }
+}
diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp 
b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
index dd450a8f1b76c..44189ff31dae8 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
@@ -1607,3 +1607,203 @@ T tf() {
 
 template long tf<long>();
 }
+
+// Boilerplate needed for tests involving coroutines
+namespace std {
+template <class... Args>
+struct void_t_imp {
+  using type = void;
+};
+template <class... Args>
+using void_t = typename void_t_imp<Args...>::type;
+
+template <class T, class = void>
+struct traits_sfinae_base {};
+
+template <class T>
+struct traits_sfinae_base<T, void_t<typename T::promise_type>> {
+  using promise_type = typename T::promise_type;
+};
+
+template <class Ret, class... Args>
+struct coroutine_traits : public traits_sfinae_base<Ret> {};
+
+template <class PromiseType = void>
+struct coroutine_handle {
+  static coroutine_handle from_address(void *) noexcept;
+  static coroutine_handle from_promise(PromiseType &promise);
+};
+template <>
+struct coroutine_handle<void> {
+  template <class PromiseType>
+  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+  static coroutine_handle from_address(void *) noexcept;
+  template <class PromiseType>
+  static coroutine_handle from_promise(PromiseType &promise);
+};
+
+struct suspend_always {
+  bool await_ready() noexcept { return false; }
+  template <typename F>
+  void await_suspend(F) noexcept;
+  void await_resume() noexcept {}
+};
+
+struct suspend_never {
+  bool await_ready() noexcept { return true; }
+  template <typename F>
+  void await_suspend(F) noexcept;
+  void await_resume() noexcept {}
+};
+} // namespace std
+
+struct task {
+  struct promise_type {
+    task get_return_object() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
+    std::suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    std::suspend_never yield_value(int) { return {}; }
+    void unhandled_exception() {}
+  };
+};
+
+namespace decl_context_issues {
+void local_class() {
+  template for (int x : {0}) {
+    struct Local {
+      template <class T> // expected-error {{templates cannot be declared 
inside of a local class}}
+      void member(T) {}
+    };
+
+    template for (int y : {1}) {
+      struct Nested {
+        template <class T> // expected-error {{templates cannot be declared 
inside of a local class}}
+        void member(T) {}
+      };
+    }
+  }
+
+  template for (int x : {}) {
+    struct DiscardedLocal {
+      template <class T> // expected-error {{templates cannot be declared 
inside of a local class}}
+      void member(T) {}
+    };
+  }
+}
+
+void thread_local_var() {
+  template for (int x : {0}) {
+    thread_local int v1;
+    __thread int v2; // expected-error {{'__thread' variables must have global 
storage}}
+    static __thread int v3;
+  }
+}
+
+task coro() {
+  template for (int x : {0}) {
+    co_await std::suspend_never{};
+    co_yield 1;
+    co_return;
+  }
+  co_return;
+}
+
+task coro_discarded() {
+  template for (int x : {}) {
+    co_await std::suspend_never{}; // expected-note {{function is a coroutine 
due to use of 'co_await' here}}
+  }
+
+  // This is a coroutine even though the co_await above is discarded.
+  return task(); // expected-error {{return statement not allowed in 
coroutine; did you mean 'co_return'?}}
+}
+
+void inline_static() {
+  template for (int x : {0}) {
+    inline int y = x; // expected-error {{inline declaration of 'y' not 
allowed in block scope}}
+    inline void f1(); // expected-error {{inline declaration of 'f1' not 
allowed in block scope}}
+    static void f2(); // expected-error {{function declared in block scope 
cannot have 'static' storage class}}
+  }
+}
+
+struct VexingParse {};
+void vexing_parse() {
+  template for (int x : {}) {
+    VexingParse v(); // expected-warning {{empty parentheses interpreted as a 
function declaration}} expected-note {{remove parentheses to declare a 
variable}}
+  }
+}
+
+void builtin_va_start(int x, ...) {
+  __builtin_va_list ap;
+  template for (int y : {1}) {
+    __builtin_va_start(ap, x);
+  }
+
+  (void) ^ (int x, ...) {
+    __builtin_va_list ap;
+    template for (int y : {1}) {
+      __builtin_va_start(ap, x);
+    }
+  };
+}
+
+constexpr void local_label_constexpr() {
+  template for (int x : {0}) {
+    __label__ local; // expected-error {{statement not allowed in constexpr 
function}}
+    local:
+  }
+}
+
+#pragma GCC diagnostic warning "-Wformat"
+#pragma GCC diagnostic warning "-Wmissing-format-attribute"
+
+__attribute__((format(printf, 1, 0)))
+int vfprintf(const char *, __builtin_va_list);
+
+void call_vfprintf(const char *format, __builtin_va_list arguments) { // 
expected-note {{'call_vfprintf' declared here}}
+  template for (int x : {0}) {
+    vfprintf(format, arguments); // expected-warning {{diagnostic behavior may 
be improved by adding the 'format(printf, 1, 0)' attribute to the declaration 
of 'call_vfprintf'}}
+  }
+}
+
+void format_warn(__builtin_va_list arguments) {
+  call_vfprintf("%", arguments); // expected-warning {{incomplete format 
specifier}}
+}
+
+void local_class_enclosing_var() {
+  template for (int index : {0}) { // expected-note {{in instantiation of}}
+    int value = index; // expected-note {{declared here}}
+    struct Local { // expected-note {{in instantiation of}}
+      int read() {
+        return value; // expected-error {{reference to local variable 'value' 
declared in enclosing function 
'decl_context_issues::local_class_enclosing_var'}}
+      }
+    };
+  }
+}
+
+namespace nested {
+void function();
+}
+
+void function_redecl() {
+  template for (int x : {0}) {
+    void nested::function(); // expected-error {{definition or redeclaration 
of 'function' not allowed inside a function}}
+  }
+}
+
+void default_arg_in_redecl() {
+  template for (int x : {0}) {
+    void f(int);
+    void f(int value = 0);
+    f();
+  }
+
+  template for (constexpr int x : {1, 2, 3}) {
+    void f(int[x]);
+    void f(int[x] = nullptr);
+    f();
+  }
+}
+
+
+} // namespace decl_context_issues

>From acbaeb261741b99490462ae61531847c8deebba7 Mon Sep 17 00:00:00 2001
From: Ambrose Leeb <[email protected]>
Date: Tue, 18 Aug 2026 21:54:53 +0200
Subject: [PATCH 2/4] Undo change that isn't actually needed anymore

---
 clang/lib/AST/ByteCode/Interp.h | 4 ++--
 clang/lib/AST/ExprConstant.cpp  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index f45174e1c86f9..054fba2c87c45 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2748,8 +2748,8 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC, uint32_t 
ElemSize) {
         return false;
       }
 
-      if (LHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl() !=
-          RHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl())
+      if (LHSAddrExpr->getLabel()->getDeclContext() !=
+          RHSAddrExpr->getLabel()->getDeclContext())
         return Invalid(S, OpPC);
 
       S.Stk.push<T>(LHSAddrExpr, RHSAddrExpr);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 67cffe0fc46b8..480d5119a5363 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -19007,8 +19007,8 @@ bool DataRecursiveIntBinOpEvaluator::
     if (!LHSAddrExpr || !RHSAddrExpr)
       return false;
     // Make sure both labels come from the same function.
-    if (LHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl() !=
-        RHSAddrExpr->getLabel()->getDeclContext()->getAsFunctionDecl())
+    if (LHSAddrExpr->getLabel()->getDeclContext() !=
+        RHSAddrExpr->getLabel()->getDeclContext())
       return false;
     Result = APValue(LHSAddrExpr, RHSAddrExpr);
     return true;

>From 53069c0d8ecfb25d9f12347cae82b5f48a07ed84 Mon Sep 17 00:00:00 2001
From: Ambrose Leeb <[email protected]>
Date: Tue, 18 Aug 2026 22:08:26 +0200
Subject: [PATCH 3/4] clang-format

---
 clang/lib/Sema/SemaDecl.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 60adb58d48f1c..cd591133042cd 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8154,10 +8154,9 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     //   explicitly.
     // Core issue: 'static' is not implied if the variable is declared
     //   'extern'.
-    if (NewVD->hasLocalStorage() &&
-        (SCSpec != DeclSpec::SCS_unspecified ||
-         TSCS != DeclSpec::TSCS_thread_local ||
-         !DC->isInsideFunctionOrMethod()))
+    if (NewVD->hasLocalStorage() && (SCSpec != DeclSpec::SCS_unspecified ||
+                                     TSCS != DeclSpec::TSCS_thread_local ||
+                                     !DC->isInsideFunctionOrMethod()))
       Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
            diag::err_thread_non_global)
         << DeclSpec::getSpecifierName(TSCS);

>From 8e1000608b607f6c281b26865bfcd49b598b16e8 Mon Sep 17 00:00:00 2001
From: Ambrose Leeb <[email protected]>
Date: Thu, 20 Aug 2026 21:51:34 +0200
Subject: [PATCH 4/4] Add CXXExpansionStmtDecl to isFunctionOrMethod()

---
 clang/docs/ReleaseNotes.md                      |  6 ------
 clang/include/clang/AST/DeclBase.h              | 10 ++++------
 clang/lib/AST/Decl.cpp                          |  8 +++-----
 clang/lib/Sema/SemaDecl.cpp                     | 17 +++++++++--------
 clang/lib/Sema/SemaType.cpp                     |  2 +-
 .../test/Parser/cxx2c-expansion-statements.cpp  |  4 ++--
 .../expansion-statements-local-extern-decls.cpp | 12 ++++--------
 7 files changed, 23 insertions(+), 36 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9357c15ec2076..943e75080f12f 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -497,12 +497,6 @@ features cannot lower the translation-unit ABI level;
   to a subobject and is used in a context that requires an implicit conversion.
   (#GH215900)
 
-- Fixed a number issues arising from the fact that Clang considered the body of
-  an expansion statement to not be inside a function in some contexts. Several
-  constructs that were previously incorrectly rejected inside expansion 
statements
-  (e.g. `thread_local` variables, `va_start`, and 
`co_await`/`co_yield`/`co_return`)
-  are now accepted, and vice versa.
-
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/AST/DeclBase.h 
b/clang/include/clang/AST/DeclBase.h
index 0e0c99cec389b..a8e86dccbc03c 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2175,12 +2175,16 @@ class DeclContext {
     }
   }
 
+  /// Returns true if this DeclContext is a function, Objective-C method,
+  /// or block, or a DeclContext that can only occur in or is conceptually
+  /// treated like a function.
   bool isFunctionOrMethod() const {
     switch (getDeclKind()) {
     case Decl::Block:
     case Decl::Captured:
     case Decl::ObjCMethod:
     case Decl::TopLevelStmt:
+    case Decl::CXXExpansionStmt:
       return true;
     default:
       return getDeclKind() >= Decl::firstFunction &&
@@ -2188,12 +2192,6 @@ class DeclContext {
     }
   }
 
-  /// Test whether we're directly inside a function or method, but ignoring
-  /// any intervening expansion statements.
-  bool isInsideFunctionOrMethod() const {
-    return getEnclosingNonExpansionStatementContext()->isFunctionOrMethod();
-  }
-
   /// Cast this to a FunctionDecl if it is one, ignoring any intervening
   /// expansion statements. Returns nullptr if this is not a function.
   FunctionDecl *getAsFunctionDecl() {
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index dabb061cbe904..37a6e97d0450d 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1106,7 +1106,7 @@ bool NamedDecl::isPlaceholderVar(const LangOptions 
&LangOpts) const {
   if (isa<FieldDecl>(this))
     return true;
   if (const auto *IFD = dyn_cast<IndirectFieldDecl>(this)) {
-    if (!getDeclContext()->isInsideFunctionOrMethod() &&
+    if (!getDeclContext()->isFunctionOrMethod() &&
         !getDeclContext()->isRecord())
       return false;
     const VarDecl *VD = IFD->getVarDecl();
@@ -1121,7 +1121,7 @@ bool NamedDecl::isPlaceholderVar(const LangOptions 
&LangOpts) const {
     return VD->getStorageDuration() == StorageDuration::SD_Automatic;
   }
   if (const auto *BD = dyn_cast<BindingDecl>(this);
-      BD && getDeclContext()->isInsideFunctionOrMethod()) {
+      BD && getDeclContext()->isFunctionOrMethod()) {
     const VarDecl *VD = BD->getHoldingVar();
     return !VD || VD->getStorageDuration() == StorageDuration::SD_Automatic;
   }
@@ -1566,9 +1566,7 @@ LinkageInfo LinkageComputer::computeLVForDecl(const 
NamedDecl *D,
   //   one such matching entity, the program is ill-formed. Otherwise,
   //   if no matching entity is found, the block scope entity receives
   //   external linkage.
-  if (D->getDeclContext()
-          ->getEnclosingNonExpansionStatementContext()
-          ->isFunctionOrMethod())
+  if (D->getDeclContext()->isFunctionOrMethod())
     return getLVForLocalDecl(D, computation);
 
   // C++ [basic.link]p6:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index cd591133042cd..3643a7e875987 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2054,7 +2054,7 @@ static bool ShouldDiagnoseUnusedDecl(const LangOptions 
&LangOpts,
 
   // Except for labels, we only care about unused decls that are local to
   // functions.
-  bool WithinFunction = D->getDeclContext()->isInsideFunctionOrMethod();
+  bool WithinFunction = D->getDeclContext()->isFunctionOrMethod();
   if (const auto *R = dyn_cast<CXXRecordDecl>(D->getDeclContext()))
     // For dependent types, the diagnostic is deferred.
     WithinFunction =
@@ -7540,7 +7540,7 @@ static bool hasParsedAttr(Scope *S, const Declarator &PD,
 }
 
 bool Sema::adjustContextForLocalExternDecl(DeclContext *&DC) {
-  if (!DC->getEnclosingNonExpansionStatementContext()->isFunctionOrMethod())
+  if (!DC->isFunctionOrMethod())
     return false;
 
   // If this is a local extern function or variable declared within a function
@@ -8119,7 +8119,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     if (!getLangOpts().CPlusPlus) {
       Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
           << 0;
-    } else if (CurContext->isInsideFunctionOrMethod()) {
+    } else if (CurContext->isFunctionOrMethod()) {
       // 'inline' is not allowed on block scope variable declaration.
       Diag(D.getDeclSpec().getInlineSpecLoc(),
            diag::err_inline_declaration_block_scope) << Name
@@ -8154,9 +8154,10 @@ NamedDecl *Sema::ActOnVariableDeclarator(
     //   explicitly.
     // Core issue: 'static' is not implied if the variable is declared
     //   'extern'.
-    if (NewVD->hasLocalStorage() && (SCSpec != DeclSpec::SCS_unspecified ||
-                                     TSCS != DeclSpec::TSCS_thread_local ||
-                                     !DC->isInsideFunctionOrMethod()))
+    if (NewVD->hasLocalStorage() &&
+        (SCSpec != DeclSpec::SCS_unspecified ||
+         TSCS != DeclSpec::TSCS_thread_local ||
+         !DC->isFunctionOrMethod()))
       Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
            diag::err_thread_non_global)
         << DeclSpec::getSpecifierName(TSCS);
@@ -9595,7 +9596,7 @@ static StorageClass getFunctionStorageClass(Sema 
&SemaRef, Declarator &D) {
       return SC_None;
     return SC_Extern;
   case DeclSpec::SCS_static: {
-    if (SemaRef.CurContext->getRedeclContext()->isInsideFunctionOrMethod()) {
+    if (SemaRef.CurContext->getRedeclContext()->isFunctionOrMethod()) {
       // C99 6.7.1p5:
       //   The declaration of an identifier for a function that has
       //   block scope shall have no explicit storage-class specifier
@@ -10426,7 +10427,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, 
DeclContext *DC,
     //  The inline specifier shall not appear on a block scope function
     //  declaration.
     if (isInline && !NewFD->isInvalidDecl()) {
-      if (CurContext->isInsideFunctionOrMethod()) {
+      if (CurContext->isFunctionOrMethod()) {
         // 'inline' is not allowed on block scope function declaration.
         Diag(D.getDeclSpec().getInlineSpecLoc(),
              diag::err_inline_declaration_block_scope) << Name
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d8cd048e0f6cb..42ef93b98aa0a 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -3550,7 +3550,7 @@ static void warnAboutAmbiguousFunction(Sema &S, 
Declarator &D,
   // doesn't have a storage class (such as 'extern') specified.
   if (!D.isFunctionDeclarator() ||
       D.getFunctionDefinitionKind() != FunctionDefinitionKind::Declaration ||
-      !S.CurContext->isInsideFunctionOrMethod() ||
+      !S.CurContext->isFunctionOrMethod() ||
       D.getDeclSpec().getStorageClassSpec() != DeclSpec::SCS_unspecified)
     return;
 
diff --git a/clang/test/Parser/cxx2c-expansion-statements.cpp 
b/clang/test/Parser/cxx2c-expansion-statements.cpp
index 736f9fead383c..3a28b3268fd7a 100644
--- a/clang/test/Parser/cxx2c-expansion-statements.cpp
+++ b/clang/test/Parser/cxx2c-expansion-statements.cpp
@@ -23,7 +23,7 @@ void bad() {
   template for (__private_extern__ auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'extern'}}
   template for (extern static auto y : {1, 2}); // expected-error {{cannot 
combine with previous 'extern' declaration specifier}} expected-error 
{{expansion variable 'y' may not be declared 'extern'}}
   template for (static auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'static'}}
-  template for (thread_local auto y : {1, 2}); // expected-error 
{{'thread_local' variables must have global storage}}
+  template for (thread_local auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'thread_local'}}
   template for (static thread_local auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'thread_local'}}
   template for (__thread auto y : {1, 2}); // expected-error {{'__thread' 
variables must have global storage}}
   template for (static __thread auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'static'}}
@@ -32,7 +32,7 @@ void bad() {
   template for (int x; extern auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'extern'}}
   template for (int x; extern static auto y : {1, 2}); // expected-error 
{{cannot combine with previous 'extern' declaration specifier}} expected-error 
{{expansion variable 'y' may not be declared 'extern'}}
   template for (int x; static auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'static'}}
-  template for (int x; thread_local auto y : {1, 2}); // expected-error 
{{'thread_local' variables must have global storage}}
+  template for (int x; thread_local auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'thread_local'}}
   template for (int x; static thread_local auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'thread_local'}}
   template for (int x; __thread auto y : {1, 2}); // expected-error 
{{'__thread' variables must have global storage}}
   template for (int x; static __thread auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'static'}}
diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp 
b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp
index c00a9c400c24d..2dfb18ec9aece 100644
--- a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp
+++ b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp
@@ -3,21 +3,19 @@
 int wibble(); // #wibble_decl
 
 void foo1() {
-  template for (auto x : {1}) { // #foo1_instantiation
+  template for (auto x : {1}) {
     void wibble();
     // expected-error@-1 {{functions that differ only in their return type 
cannot be overloaded}}
     // expected-note@#wibble_decl {{previous declaration is here}}
-    // expected-note@#foo1_instantiation {{in instantiation of expansion 
statement requested here}}
   }
 }
 
 void foo2() {
-  template for (auto x : {1}) { // #foo2_instantiation
+  template for (auto x : {1}) {
     template for (auto x : {1}) {
       void wibble();
       // expected-error@-1 {{functions that differ only in their return type 
cannot be overloaded}}
       // expected-note@#wibble_decl {{previous declaration is here}}
-      // expected-note@#foo2_instantiation {{in instantiation of expansion 
statement requested here}}
     }
   }
 }
@@ -25,21 +23,19 @@ void foo2() {
 int woffle; // #woffle_decl
 
 void foo3() {
-  template for (auto x : {1}) { // #foo3_instantiation
+  template for (auto x : {1}) {
     extern double woffle;
     // expected-error@-1 {{redeclaration of 'woffle' with a different type: 
'double' vs 'int'}}
     // expected-note@#woffle_decl {{previous definition is here}}
-    // expected-note@#foo3_instantiation {{in instantiation of expansion 
statement requested here}}
   }
 }
 
 void foo4() {
-  template for (auto x : {1}) { // #foo4_instantiation
+  template for (auto x : {1}) {
     template for (auto x : {1}) {
       extern double woffle;
       // expected-error@-1 {{redeclaration of 'woffle' with a different type: 
'double' vs 'int'}}
       // expected-note@#woffle_decl {{previous definition is here}}
-      // expected-note@#foo4_instantiation {{in instantiation of expansion 
statement requested here}}
     }
   }
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to