https://github.com/ChenMiaoi created https://github.com/llvm/llvm-project/pull/236740
Clang maps C23 `thread_local` to `_Thread_local` to retain C semantics. Diagnostics using the internal specifier therefore report `_Thread_local` when the source spells the keyword `thread_local`. Track the C23 spelling in DeclSpec and use it for duplicate and invalid declaration diagnostics. >From 67ecf76ffc09366e739ee14668af346057b2db94 Mon Sep 17 00:00:00 2001 From: Chen Miao <[email protected]> Date: Thu, 29 Oct 2026 19:49:00 +0800 Subject: [PATCH] [clang][C23] Preserve `thread_local` spelling in diagnostics Clang maps C23 `thread_local` to `_Thread_local` to retain C semantics. Diagnostics using the internal specifier therefore report `_Thread_local` when the source spells the keyword `thread_local`. Track the C23 spelling in DeclSpec and use it for duplicate and invalid declaration diagnostics. --- clang/include/clang/Sema/DeclSpec.h | 9 +++++++-- clang/lib/Parse/ParseDecl.cpp | 5 +---- clang/lib/Sema/DeclSpec.cpp | 25 ++++++++++++++++++------- clang/lib/Sema/SemaDecl.cpp | 18 +++++++++--------- clang/lib/Sema/SemaDeclObjC.cpp | 4 ++-- clang/test/Sema/constexpr.c | 8 ++++---- clang/test/Sema/thread_local.c | 8 ++++++-- 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index 7b58bfcf53d98a..5bcf8f5f81c17f 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -342,6 +342,8 @@ class DeclSpec { LLVM_PREFERRED_TYPE(TSCS) unsigned ThreadStorageClassSpec : 2; LLVM_PREFERRED_TYPE(bool) + unsigned C23ThreadLocalSpelling : 1; + LLVM_PREFERRED_TYPE(bool) unsigned SCS_extern_in_linkage_spec : 1; // type-specifier @@ -470,7 +472,7 @@ class DeclSpec { DeclSpec(AttributeFactory &attrFactory) : StorageClassSpec(SCS_unspecified), - ThreadStorageClassSpec(TSCS_unspecified), + ThreadStorageClassSpec(TSCS_unspecified), C23ThreadLocalSpelling(false), SCS_extern_in_linkage_spec(false), TypeSpecWidth(static_cast<unsigned>(TypeSpecifierWidth::Unspecified)), TypeSpecComplex(TSC_unspecified), @@ -491,6 +493,7 @@ class DeclSpec { TSCS getThreadStorageClassSpec() const { return (TSCS)ThreadStorageClassSpec; } + const char *getThreadStorageClassSpecName() const; bool isExternInLinkageSpec() const { return SCS_extern_in_linkage_spec; } void setExternInLinkageSpec(bool Value) { SCS_extern_in_linkage_spec = Value; @@ -504,6 +507,7 @@ class DeclSpec { void ClearStorageClassSpecs() { StorageClassSpec = DeclSpec::SCS_unspecified; ThreadStorageClassSpec = DeclSpec::TSCS_unspecified; + C23ThreadLocalSpelling = false; SCS_extern_in_linkage_spec = false; StorageClassSpecLoc = SourceLocation(); ThreadStorageClassSpecLoc = SourceLocation(); @@ -733,7 +737,8 @@ class DeclSpec { const char *&PrevSpec, unsigned &DiagID, const PrintingPolicy &Policy); bool SetStorageClassSpecThread(TSCS TSC, SourceLocation Loc, - const char *&PrevSpec, unsigned &DiagID); + const char *&PrevSpec, unsigned &DiagID, + bool IsC23ThreadLocal = false); bool SetTypeSpecWidth(TypeSpecifierWidth W, SourceLocation Loc, const char *&PrevSpec, unsigned &DiagID, const PrintingPolicy &Policy); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index e452c312702491..4a453e72dcae40 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4217,13 +4217,10 @@ void Parser::ParseDeclarationSpecifiers( Diag(Tok, diag::warn_c23_compat_keyword) << Tok.getName(); // We map thread_local to _Thread_local in C23 mode so it retains the C // semantics rather than getting the C++ semantics. - // FIXME: diagnostics will show _Thread_local when the user wrote - // thread_local in source in C23 mode; we need some general way to - // identify which way the user spelled the keyword in source. isInvalid = DS.SetStorageClassSpecThread( getLangOpts().C23 ? DeclSpec::TSCS__Thread_local : DeclSpec::TSCS_thread_local, - Loc, PrevSpec, DiagID); + Loc, PrevSpec, DiagID, getLangOpts().C23); isStorageClass = true; break; case tok::kw__Thread_local: diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp index dfa7a79d1f92b2..3dc7e284ea8780 100644 --- a/clang/lib/Sema/DeclSpec.cpp +++ b/clang/lib/Sema/DeclSpec.cpp @@ -496,6 +496,11 @@ const char *DeclSpec::getSpecifierName(DeclSpec::TSCS S) { llvm_unreachable("Unknown typespec!"); } +const char *DeclSpec::getThreadStorageClassSpecName() const { + return C23ThreadLocalSpelling ? "thread_local" + : getSpecifierName(getThreadStorageClassSpec()); +} + const char *DeclSpec::getSpecifierName(TypeSpecifierWidth W) { switch (W) { case TypeSpecifierWidth::Unspecified: @@ -698,11 +703,16 @@ bool DeclSpec::SetStorageClassSpec(Sema &S, SCS SC, SourceLocation Loc, bool DeclSpec::SetStorageClassSpecThread(TSCS TSC, SourceLocation Loc, const char *&PrevSpec, - unsigned &DiagID) { - if (ThreadStorageClassSpec != TSCS_unspecified) - return BadSpecifier(TSC, (TSCS)ThreadStorageClassSpec, PrevSpec, DiagID); + unsigned &DiagID, + bool IsC23ThreadLocal) { + if (ThreadStorageClassSpec != TSCS_unspecified) { + BadSpecifier(TSC, (TSCS)ThreadStorageClassSpec, PrevSpec, DiagID); + PrevSpec = getThreadStorageClassSpecName(); + return true; + } ThreadStorageClassSpec = TSC; + C23ThreadLocalSpelling = IsC23ThreadLocal; ThreadStorageClassSpecLoc = Loc; return false; } @@ -1390,9 +1400,9 @@ void DeclSpec::CheckTypeSpec(Sema &S, const PrintingPolicy &Policy) { if (S.getSourceManager().isBeforeInTranslationUnit( getThreadStorageClassSpecLoc(), getStorageClassSpecLoc())) S.Diag(getStorageClassSpecLoc(), - diag::err_invalid_decl_spec_combination) - << DeclSpec::getSpecifierName(getThreadStorageClassSpec()) - << SourceRange(getThreadStorageClassSpecLoc()); + diag::err_invalid_decl_spec_combination) + << getThreadStorageClassSpecName() + << SourceRange(getThreadStorageClassSpecLoc()); else S.Diag(getThreadStorageClassSpecLoc(), diag::err_invalid_decl_spec_combination) @@ -1400,12 +1410,13 @@ void DeclSpec::CheckTypeSpec(Sema &S, const PrintingPolicy &Policy) { << SourceRange(getStorageClassSpecLoc()); // Discard the thread storage class specifier to recover. ThreadStorageClassSpec = TSCS_unspecified; + C23ThreadLocalSpelling = false; ThreadStorageClassSpecLoc = SourceLocation(); } if (S.getLangOpts().C23 && getConstexprSpecifier() == ConstexprSpecKind::Constexpr) { S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination) - << DeclSpec::getSpecifierName(getThreadStorageClassSpec()) + << getThreadStorageClassSpecName() << SourceRange(getThreadStorageClassSpecLoc()); } } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index f3d38a555a524a..446d9bab4ec448 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -5539,9 +5539,9 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS, << DeclSpec::getSpecifierName(SCS); } - if (DeclSpec::TSCS TSCS = DS.getThreadStorageClassSpec()) + if (DS.getThreadStorageClassSpec()) Diag(DS.getThreadStorageClassSpecLoc(), DiagID) - << DeclSpec::getSpecifierName(TSCS); + << DS.getThreadStorageClassSpecName(); if (DS.getTypeQualifiers()) { if (DS.getTypeQualifiers() & DeclSpec::TQ_const) Diag(DS.getConstSpecLoc(), DiagID) << "const"; @@ -8220,7 +8220,7 @@ NamedDecl *Sema::ActOnVariableDeclarator( !DC->isFunctionOrMethod())) Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(), diag::err_thread_non_global) - << DeclSpec::getSpecifierName(TSCS); + << D.getDeclSpec().getThreadStorageClassSpecName(); else if (!Context.getTargetInfo().isTLSSupported()) { if (getLangOpts().CUDA || getLangOpts().isTargetDevice()) { // Postpone error emission until we've collected attributes required to @@ -10297,10 +10297,10 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, DeclarationName Name = NameInfo.getName(); StorageClass SC = getFunctionStorageClass(*this, D); - if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec()) + if (D.getDeclSpec().getThreadStorageClassSpec()) Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(), diag::err_invalid_thread) - << DeclSpec::getSpecifierName(TSCS); + << D.getDeclSpec().getThreadStorageClassSpecName(); if (D.isFirstDeclarationOfMember()) adjustMemberFunctionCC( @@ -15975,9 +15975,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D, D.getMutableDeclSpec().ClearStorageClassSpecs(); } - if (DeclSpec::TSCS TSCS = DS.getThreadStorageClassSpec()) + if (DS.getThreadStorageClassSpec()) Diag(DS.getThreadStorageClassSpecLoc(), diag::err_invalid_thread) - << DeclSpec::getSpecifierName(TSCS); + << DS.getThreadStorageClassSpecName(); if (DS.isInlineSpecified()) Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function) << getLangOpts().CPlusPlus17; @@ -19601,10 +19601,10 @@ FieldDecl *Sema::HandleField(Scope *S, RecordDecl *Record, if (D.getDeclSpec().isInlineSpecified()) Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function) << getLangOpts().CPlusPlus17; - if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec()) + if (D.getDeclSpec().getThreadStorageClassSpec()) Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(), diag::err_invalid_thread) - << DeclSpec::getSpecifierName(TSCS); + << D.getDeclSpec().getThreadStorageClassSpecName(); // Check to see if this name was declared as a member previously NamedDecl *PrevDecl = nullptr; diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 4a6fc658575d9c..4e5855cb381257 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -5220,10 +5220,10 @@ Decl *SemaObjC::ActOnObjCExceptionDecl(Scope *S, Declarator &D) { if (DS.isInlineSpecified()) Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function) << getLangOpts().CPlusPlus17; - if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec()) + if (D.getDeclSpec().getThreadStorageClassSpec()) Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(), diag::err_invalid_thread) - << DeclSpec::getSpecifierName(TSCS); + << D.getDeclSpec().getThreadStorageClassSpecName(); D.getMutableDeclSpec().ClearStorageClassSpecs(); SemaRef.DiagnoseFunctionSpecifiers(D.getDeclSpec()); diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c index 3bb8cffe16ab21..21e15c84d30cd3 100644 --- a/clang/test/Sema/constexpr.c +++ b/clang/test/Sema/constexpr.c @@ -54,12 +54,12 @@ void f3(constexpr register int P1) { // expected-error {{function parameter cann constexpr register auto V10 = 0.0; } -constexpr thread_local int V11 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}} -constexpr static thread_local double V12 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}} -constexpr extern thread_local char V13; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}} +constexpr thread_local int V11 = 38; // expected-error {{cannot combine with previous 'thread_local' declaration specifier}} +constexpr static thread_local double V12 = 38; // expected-error {{cannot combine with previous 'thread_local' declaration specifier}} +constexpr extern thread_local char V13; // expected-error {{cannot combine with previous 'thread_local' declaration specifier}} // expected-error@-1 {{cannot combine with previous 'constexpr' declaration specifier}} // expected-error@-2 {{constexpr variable declaration must be a definition}} -constexpr thread_local short V14 = 38; // expected-error {{cannot combine with previous '_Thread_local' declaration specifier}} +constexpr thread_local short V14 = 38; // expected-error {{cannot combine with previous 'thread_local' declaration specifier}} // Check how constexpr works with qualifiers. constexpr _Atomic int V15 = 0; // expected-error {{constexpr variable cannot have type 'const _Atomic(int)'}} diff --git a/clang/test/Sema/thread_local.c b/clang/test/Sema/thread_local.c index b65f1119c73831..78d80740bceabe 100644 --- a/clang/test/Sema/thread_local.c +++ b/clang/test/Sema/thread_local.c @@ -4,8 +4,7 @@ // Ensure that thread_local and _Thread_local are synonyms in C23 and both // restrict local variables to be explicitly static or extern. void func(void) { - // FIXME: it would be nice if the diagnostic said 'thread_local' in this case. - thread_local int i = 12; // expected-error {{'_Thread_local' variables must have global storage}} + thread_local int i = 12; // expected-error {{'thread_local' variables must have global storage}} _Thread_local int j = 13; // expected-error {{'_Thread_local' variables must have global storage}} static thread_local int k = 14; @@ -18,3 +17,8 @@ void func(void) { // This would previously fail because the tls models were different. extern thread_local unsigned a; _Thread_local unsigned a = 0; + +thread_local _Thread_local int duplicate1; +// expected-warning@-1 {{duplicate 'thread_local' declaration specifier}} +_Thread_local thread_local int duplicate2; +// expected-warning@-1 {{duplicate '_Thread_local' declaration specifier}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
