https://github.com/Sirraide created https://github.com/llvm/llvm-project/pull/223005
This reverts 7c44c505cd7688a8994779f1751e0f9b4b95ab4c and relands #217110. There were two tests that needed updating: one because a warning that had previously been erroneously suppressed (because we thought we weren’t in a function) now works properly, and another because it crashes due to an unrelated bug. I’ve filed #223003 for the latter and moved the crash into a separate XFAIL test so it can be reenabled when the bug is fixed. The plan was to backport this to the 23 release branch, so still no release note. >From a181f3219ea411729a5cac13064b75169e81ca02 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb <[email protected]> Date: Fri, 4 Sep 2026 20:52:15 +0200 Subject: [PATCH 1/3] Reapply "[Clang] Fix a number of issues involving expansion statements" (#220375) This reverts commit 7c44c505cd7688a8994779f1751e0f9b4b95ab4c. --- clang/include/clang/AST/DeclBase.h | 26 +++ clang/include/clang/AST/DeclCXX.h | 2 +- clang/lib/AST/Decl.cpp | 4 +- clang/lib/Sema/SemaChecking.cpp | 5 +- clang/lib/Sema/SemaCoroutine.cpp | 16 +- clang/lib/Sema/SemaDecl.cpp | 3 +- clang/lib/Sema/SemaDeclCXX.cpp | 30 ++- clang/lib/Sema/SemaExpr.cpp | 3 +- .../Parser/cxx2c-expansion-statements.cpp | 4 +- .../cxx2c-expansion-stmts-warnings.cpp | 16 ++ clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 200 ++++++++++++++++++ ...xpansion-statements-local-extern-decls.cpp | 12 +- 12 files changed, 288 insertions(+), 33 deletions(-) create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index 9d233be282dbb8..a067d87e92a84a 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,6 +2192,28 @@ class DeclContext { } } + /// Cast this to a FunctionDecl if it is one, ignoring any intervening + /// expansion statements. Returns nullptr if this is not a function. + /// + /// In particular, this will return nullptr if the *nearest* enclosing + /// DeclContext that is not an expansion statement is something other + /// than a function (e.g. a CXXRecordDecl, even if it is a local class). + FunctionDecl *getEnclosingFunction() { + return dyn_cast<FunctionDecl>(getEnclosingNonExpansionStatementContext()); + } + + const FunctionDecl *getEnclosingFunction() const { + return dyn_cast<FunctionDecl>(getEnclosingNonExpansionStatementContext()); + } + + FunctionDecl *castEnclosingFunction() { + return cast<FunctionDecl>(getEnclosingNonExpansionStatementContext()); + } + + const FunctionDecl *castEnclosingFunction() 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 afe46fae1bceb1..ff2223070dc157 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1578,7 +1578,7 @@ class CXXRecordDecl : public RecordDecl { if (const auto *RD = dyn_cast<CXXRecordDecl>(getDeclContext())) return RD->isLocalClass(); - return dyn_cast<FunctionDecl>(getDeclContext()); + return getDeclContext()->getEnclosingFunction(); } FunctionDecl *isLocalClass() { diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index a620e9f211ca66..825651e33f592c 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -1567,9 +1567,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/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index f0a1a529841b2c..c3ae8e75eda7d3 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -6184,7 +6184,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(); @@ -7839,7 +7840,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 d22260b887415b..d68a9ec2d8f35c 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->getEnclosingFunction(); if (!FD) { S.Diag(Loc, isa<ObjCMethodDecl>(S.CurContext) ? diag::err_coroutine_objc_method @@ -470,8 +470,7 @@ static void markCoroutineParametersReferenced(FunctionDecl &FD) { } VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) { - assert(isa<FunctionDecl>(CurContext) && "not in a function scope"); - auto *FD = cast<FunctionDecl>(CurContext); + auto *FD = CurContext->castEnclosingFunction(); bool IsThisDependentType = [&] { if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(FD)) return MD->isImplicitObjectMemberFunction() && @@ -583,7 +582,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->getEnclosingFunction() && "not in a function scope"); auto *ScopeInfo = S.getCurFunction(); assert(ScopeInfo && "missing function scope for function"); @@ -630,7 +629,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->castEnclosingFunction()->getLocation(), diag::err_coroutine_promise_final_suspend_requires_nothrow); } ThrowingDecls.insert(D); @@ -701,7 +700,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->getEnclosingFunction()); if (!checkCoroutineContext(*this, KWLoc, Keyword)) return false; @@ -726,7 +725,7 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc, ScopeInfo->setNeedsCoroutineSuspends(false); - auto *Fn = cast<FunctionDecl>(CurContext); + auto *Fn = CurContext->castEnclosingFunction(); SourceLocation Loc = Fn->getLocation(); // Build the initial suspend point auto buildSuspends = [&](StringRef Name) mutable -> StmtResult { @@ -2008,8 +2007,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->castEnclosingFunction(); auto *ScopeInfo = getCurFunction(); if (!ScopeInfo->CoroutineParameterMoves.empty()) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index ccdc33d6fa39b0..cacf96fd269742 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -6417,6 +6417,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(); @@ -7545,7 +7546,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 diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4457ec58902d04..9e0fcaf8dc26ab 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 @@ -2074,9 +2076,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 @@ -2263,15 +2262,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 c93efeb928c563..93e4038d8a4c45 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -19408,7 +19408,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/test/Parser/cxx2c-expansion-statements.cpp b/clang/test/Parser/cxx2c-expansion-statements.cpp index 736f9fead383c0..3a28b3268fd7a1 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/cxx2c-expansion-stmts-warnings.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts-warnings.cpp new file mode 100644 index 00000000000000..6ef468f2cd1fb8 --- /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 dd450a8f1b76c5..44189ff31dae8e 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 diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp index 6e16090b960ec7..0ec3de29f2cd3c 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}} } } } >From 7c56beaff7300ec8ad7f815a7546e1c59f5171ff Mon Sep 17 00:00:00 2001 From: Ambrose Leeb <[email protected]> Date: Fri, 11 Sep 2026 20:08:23 +0200 Subject: [PATCH 2/3] update tests --- .../SemaCXX/cxx2c-expansion-stmts-control-flow.cpp | 2 +- .../expansion-statements-local-extern-decls.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp index 1001abae5f6efb..3a8bcb30751068 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp @@ -120,7 +120,7 @@ void GH210575(int i) { switch (i) { template for (auto x : {1, 2}) { switch (i) { - bar baz(); // expected-error {{unknown type name 'bar'}} + bar baz(); // expected-error {{unknown type name 'bar'}} expected-warning {{empty parentheses interpreted as a function declaration}} expected-note {{replace parentheses with an initializer to declare a variable}} } } } diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp index 0ec3de29f2cd3c..cbe24df4ba887c 100644 --- a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp +++ b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp @@ -80,6 +80,8 @@ void foo7() { } } +// FIXME: This test currently asserts due to a bug not related to expansion statements: https://github.com/llvm/llvm-project/issues/223003. +/* int foo8_decl; // #foo8_decl void foo8() { template for (constexpr auto x : {true, false}) { // #foo8_instantiation @@ -87,9 +89,11 @@ void foo8() { extern int foo8_decl; } else { extern thread_local int foo8_decl; // #mismatched_foo8_decl - // expected-error@#mismatched_foo8_decl {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}} - // expected-note@#foo8_instantiation {{in instantiation of expansion statement requested here}} - // expected-note@#foo8_decl {{previous definition is here}} + // FIXME: Remove 'DISABLE-' once the bug mentioned above is fixed and this test case is reenabled. + // DISABLE-expected-error@#mismatched_foo8_decl {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}} + // DISABLE-expected-note@#foo8_instantiation {{in instantiation of expansion statement requested here}} + // DISABLE-expected-note@#foo8_decl {{previous definition is here}} } } } +*/ >From 7db4ba98ab0458a9fb38097fca1871bee752b761 Mon Sep 17 00:00:00 2001 From: Ambrose Leeb <[email protected]> Date: Fri, 11 Sep 2026 20:12:32 +0200 Subject: [PATCH 3/3] make the crash an XFAIL test --- ...xpansion-statements-local-extern-decls.cpp | 18 ------------------ ...n-statements-local-extern-if-constexpr.cpp | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp index cbe24df4ba887c..a99db79b1b72fa 100644 --- a/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp +++ b/clang/test/SemaCXX/expansion-statements-local-extern-decls.cpp @@ -79,21 +79,3 @@ void foo7() { } } } - -// FIXME: This test currently asserts due to a bug not related to expansion statements: https://github.com/llvm/llvm-project/issues/223003. -/* -int foo8_decl; // #foo8_decl -void foo8() { - template for (constexpr auto x : {true, false}) { // #foo8_instantiation - if constexpr (x) { - extern int foo8_decl; - } else { - extern thread_local int foo8_decl; // #mismatched_foo8_decl - // FIXME: Remove 'DISABLE-' once the bug mentioned above is fixed and this test case is reenabled. - // DISABLE-expected-error@#mismatched_foo8_decl {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}} - // DISABLE-expected-note@#foo8_instantiation {{in instantiation of expansion statement requested here}} - // DISABLE-expected-note@#foo8_decl {{previous definition is here}} - } - } -} -*/ diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp b/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp new file mode 100644 index 00000000000000..cd83856a36c3b6 --- /dev/null +++ b/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 %s -I %S/Inputs -std=c++2c -fsyntax-only -verify +// XFAIL: * + +// FIXME: This test currently asserts due to a bug not related to expansion statements: +// https://github.com/llvm/llvm-project/issues/223003. Reenable it once that bug is fixed. + +int foo8_decl; // #foo8_decl +void foo8() { + template for (constexpr auto x : {true, false}) { // #foo8_instantiation + if constexpr (x) { + extern int foo8_decl; + } else { + extern thread_local int foo8_decl; // #mismatched_foo8_decl + // expected-error@#mismatched_foo8_decl {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}} + // expected-note@#foo8_instantiation {{in instantiation of expansion statement requested here}} + // expected-note@#foo8_decl {{previous definition is here}} + } + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
