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
