diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -133,6 +133,8 @@ ^^^^^^^^^^^^^^^^^^^^^ - Compiler flags ``-std=c++2c`` and ``-std=gnu++2c`` have been added for experimental C++2c implementation work. - Implemented `P2738R1: constexpr cast from void* `_. +- Implemented `P2169R4: A nice placeholder with no name `_. This allows using `_` + as a variable name multiple times in the same scope and is supported in all C++ language modes as an extension. Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -452,6 +452,8 @@ return hasCachedLinkage(); } + bool isPlaceholderVar(const LangOptions &LangOpts) const; + /// Looks through UsingDecls and ObjCCompatibleAliasDecls for /// the underlying named decl. NamedDecl *getUnderlyingDecl() { diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6589,6 +6589,16 @@ InGroup>, DefaultError; // Expressions. +def err_using_placeholder_variable : Error< + "ambiguous reference to placeholder '_', which is defined multiple times">; +def note_reference_placeholder : Note< + "placeholder declared here">; +def ext_placeholder_var_definition : ExtWarn< + "placeholder variables are a C++2c extension">, InGroup; +def warn_cxx23_placeholder_var_definition : Warning< + "placeholder variables are incompatible with C++ standards before C++2c">, + DefaultIgnore, InGroup; + def ext_sizeof_alignof_function_type : Extension< "invalid application of '%0' to a function type">, InGroup; def ext_sizeof_alignof_void_type : Extension< diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -494,6 +494,9 @@ /// If the identifier is an "uglified" reserved name, return a cleaned form. /// e.g. _Foo => Foo. Otherwise, just returns the name. StringRef deuglifiedName() const; + bool isPlaceholder() const { + return getLength() == 1 && getNameStart()[0] == '_'; + } /// Provide less than operator for lexicographical sorting. bool operator<(const IdentifierInfo &RHS) const { diff --git a/clang/include/clang/Sema/Lookup.h b/clang/include/clang/Sema/Lookup.h --- a/clang/include/clang/Sema/Lookup.h +++ b/clang/include/clang/Sema/Lookup.h @@ -117,6 +117,17 @@ /// @endcode AmbiguousReference, + /// Name lookup results in an ambiguity because multiple placeholder + /// variables were found in the same scope. + /// @code + /// void f() { + /// int _ = 0; + /// int _ = 0; + /// return _; // ambiguous use of placeholder variable + /// } + /// @endcode + AmbiguousReferenceToPlaceholderVariable, + /// Name lookup results in an ambiguity because an entity with a /// tag name was hidden by an entity with an ordinary name from /// a different context. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2939,6 +2939,7 @@ NamedDecl * ActOnDecompositionDeclarator(Scope *S, Declarator &D, MultiTemplateParamsArg TemplateParamLists); + void DiagPlaceholderVariableDefinition(SourceLocation Loc); // Returns true if the variable declaration is a redeclaration bool CheckVariableDeclaration(VarDecl *NewVD, LookupResult &Previous); void CheckVariableDeclarationType(VarDecl *NewVD); diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -1098,6 +1098,35 @@ return L == getCachedLinkage(); } +bool NamedDecl::isPlaceholderVar(const LangOptions &LangOpts) const { + // [C++2c] [basic.scope.scope]/p5 + // A declaration is name-independent if its name is _ and it declares + // - a variable with automatic storage duration, + // - a structured binding not inhabiting a namespace scope, + // - the variable introduced by an init-capture + // - or a non-static data member. + + if (!LangOpts.CPlusPlus || !getIdentifier() || + !getIdentifier()->isPlaceholder()) + return false; + if (isa(this)) + return true; + // and it declares a variable with automatic storage duration + if (const VarDecl *VD = dyn_cast(this)) { + if (isa(VD)) + return false; + if (VD->isInitCapture()) + return true; + return VD->getStorageDuration() == StorageDuration::SD_Automatic; + } + if (const BindingDecl *BD = dyn_cast(this); + BD && getDeclContext()->isFunctionOrMethod()) { + VarDecl *VD = BD->getHoldingVar(); + return !VD || VD->getStorageDuration() == StorageDuration::SD_Automatic; + } + return false; +} + ReservedIdentifierStatus NamedDecl::isReserved(const LangOptions &LangOpts) const { const IdentifierInfo *II = getIdentifier(); diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -709,6 +709,7 @@ if (LangOpts.CPlusPlus11) Builder.defineMacro("__cpp_static_call_operator", "202207L"); Builder.defineMacro("__cpp_named_character_escapes", "202207L"); + Builder.defineMacro("__cpp_placeholder_variables", "202306L"); if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1976,7 +1976,7 @@ UnusedFileScopedDecls.push_back(D); } -static bool ShouldDiagnoseUnusedDecl(const NamedDecl *D) { +static bool ShouldDiagnoseUnusedDecl(const Sema &SemaRef, const NamedDecl *D) { if (D->isInvalidDecl()) return false; @@ -1984,15 +1984,24 @@ // For a decomposition declaration, warn if none of the bindings are // referenced, instead of if the variable itself is referenced (which // it is, by the bindings' expressions). - for (auto *BD : DD->bindings()) + bool IsAllPlaceholders = true; + for (auto *BD : DD->bindings()) { if (BD->isReferenced()) return false; + IsAllPlaceholders = + IsAllPlaceholders && BD->isPlaceholderVar(SemaRef.getLangOpts()); + } + if (IsAllPlaceholders) + return false; } else if (!D->getDeclName()) { return false; } else if (D->isReferenced() || D->isUsed()) { return false; } + if (D->isPlaceholderVar(SemaRef.getLangOpts())) + return false; + if (D->hasAttr() || D->hasAttr() || D->hasAttr()) return false; @@ -2131,7 +2140,7 @@ /// DiagnoseUnusedDecl - Emit warnings about declarations that are not used /// unless they are marked attr(unused). void Sema::DiagnoseUnusedDecl(const NamedDecl *D, DiagReceiverTy DiagReceiver) { - if (!ShouldDiagnoseUnusedDecl(D)) + if (!ShouldDiagnoseUnusedDecl(*this, D)) return; if (auto *TD = dyn_cast(D)) { @@ -2160,8 +2169,11 @@ DiagReceiverTy DiagReceiver) { // If it's not referenced, it can't be set. If it has the Cleanup attribute, // it's not really unused. - if (!VD->isReferenced() || !VD->getDeclName() || VD->hasAttr() || - VD->hasAttr()) + if (!VD->isReferenced() || !VD->getDeclName() || VD->hasAttr()) + return; + + // In C++, `_` variables behave as if they were maybe_unused + if (VD->hasAttr() || VD->isPlaceholderVar(getLangOpts())) return; const auto *Ty = VD->getType().getTypePtr()->getBaseElementTypeUnsafe(); @@ -7448,6 +7460,7 @@ DeclarationName Name = GetNameForDeclarator(D).getName(); IdentifierInfo *II = Name.getAsIdentifierInfo(); + bool IsPlaceholderVariable = false; if (D.isDecompositionDeclarator()) { // Take the name of the first declarator as our name for diagnostic @@ -7466,6 +7479,18 @@ DeclSpec::SCS SCSpec = D.getDeclSpec().getStorageClassSpec(); StorageClass SC = StorageClassSpecToVarDeclStorageClass(D.getDeclSpec()); + if (LangOpts.CPlusPlus && (DC->isClosure() || DC->isFunctionOrMethod()) && + SC != SC_Static && SC != SC_Extern && II && II->isPlaceholder()) { + IsPlaceholderVariable = true; + if (!Previous.empty()) { + NamedDecl *PrevDecl = *Previous.begin(); + bool SameDC = PrevDecl->getDeclContext()->getRedeclContext()->Equals( + DC->getRedeclContext()); + if (SameDC && isDeclInScope(PrevDecl, CurContext, S, false)) + DiagPlaceholderVariableDefinition(D.getIdentifierLoc()); + } + } + // dllimport globals without explicit storage class are treated as extern. We // have to change the storage class this early to get the right DeclContext. if (SC == SC_None && !DC->isRecord() && @@ -8034,7 +8059,7 @@ NewVD->setInvalidDecl(); } - if (!IsVariableTemplateSpecialization) + if (!IsVariableTemplateSpecialization && !IsPlaceholderVariable) D.setRedeclaration(CheckVariableDeclaration(NewVD, Previous)); // CheckVariableDeclaration will set NewVD as invalid if something is in @@ -8072,7 +8097,7 @@ } // Diagnose shadowed variables iff this isn't a redeclaration. - if (ShadowedDecl && !D.isRedeclaration()) + if (!IsPlaceholderVariable && ShadowedDecl && !D.isRedeclaration()) CheckShadow(NewVD, ShadowedDecl, Previous); ProcessPragmaWeak(S, NewVD); @@ -8306,6 +8331,10 @@ } } + // Never warn about shadowing a placeholder variable. + if (ShadowedDecl->isPlaceholderVar(getLangOpts())) + return; + // Only warn about certain kinds of shadowing for class members. if (NewDC && NewDC->isRecord()) { // In particular, don't warn about shadowing non-class members. @@ -14769,17 +14798,17 @@ LookupResult R(*this, II, D.getIdentifierLoc(), LookupOrdinaryName, ForVisibleRedeclaration); LookupName(R, S); - if (R.isSingleResult()) { - NamedDecl *PrevDecl = R.getFoundDecl(); - if (PrevDecl->isTemplateParameter()) { + if (!R.empty()) { + NamedDecl *PrevDecl = *R.begin(); + if (R.isSingleResult() && PrevDecl->isTemplateParameter()) { // Maybe we will complain about the shadowed template parameter. DiagnoseTemplateParameterShadow(D.getIdentifierLoc(), PrevDecl); // Just pretend that we didn't see the previous declaration. PrevDecl = nullptr; - } else if (S->isDeclScope(PrevDecl)) { + } + if (PrevDecl && S->isDeclScope(PrevDecl)) { Diag(D.getIdentifierLoc(), diag::err_param_redefinition) << II; Diag(PrevDecl->getLocation(), diag::note_previous_declaration); - // Recover by removing the name II = nullptr; D.SetIdentifier(nullptr, D.getIdentifierLoc()); @@ -14848,7 +14877,8 @@ for (const ParmVarDecl *Parameter : Parameters) { if (!Parameter->isReferenced() && Parameter->getDeclName() && - !Parameter->hasAttr()) { + !Parameter->hasAttr() && + !Parameter->getIdentifier()->isPlaceholder()) { Diag(Parameter->getLocation(), diag::warn_unused_parameter) << Parameter->getDeclName(); } @@ -18211,9 +18241,13 @@ NewFD->setInvalidDecl(); if (PrevDecl && !isa(PrevDecl)) { - Diag(Loc, diag::err_duplicate_member) << II; - Diag(PrevDecl->getLocation(), diag::note_previous_declaration); - NewFD->setInvalidDecl(); + if (isa(PrevDecl) && PrevDecl->isPlaceholderVar(getLangOpts())) { + DiagPlaceholderVariableDefinition(Loc); + } else { + Diag(Loc, diag::err_duplicate_member) << II; + Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + NewFD->setInvalidDecl(); + } } if (!InvalidDecl && getLangOpts().CPlusPlus) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -722,6 +722,12 @@ return Invalid; } +void Sema::DiagPlaceholderVariableDefinition(SourceLocation Loc) { + Diag(Loc, getLangOpts().CPlusPlus26 + ? diag::ext_placeholder_var_definition + : diag::warn_cxx23_placeholder_var_definition); +} + NamedDecl * Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D, MultiTemplateParamsArg TemplateParamLists) { @@ -877,6 +883,9 @@ for (auto &B : D.getDecompositionDeclarator().bindings()) { // Check for name conflicts. DeclarationNameInfo NameInfo(B.Name, B.NameLoc); + IdentifierInfo *VarName = B.Name; + assert(VarName && "Cannot have an unnamed binding declaration"); + LookupResult Previous(*this, NameInfo, LookupOrdinaryName, ForVisibleRedeclaration); LookupName(Previous, S, @@ -890,7 +899,7 @@ Previous.clear(); } - auto *BD = BindingDecl::Create(Context, DC, B.NameLoc, B.Name); + auto *BD = BindingDecl::Create(Context, DC, B.NameLoc, VarName); // Find the shadowed declaration before filtering for scope. NamedDecl *ShadowedDecl = D.getCXXScopeSpec().isEmpty() @@ -902,10 +911,24 @@ FilterLookupForScope(Previous, DC, S, ConsiderLinkage, /*AllowInlineNamespace*/false); + bool IsPlaceholder = DS.getStorageClassSpec() != DeclSpec::SCS_static && + DC->isFunctionOrMethod() && VarName->isPlaceholder(); if (!Previous.empty()) { - auto *Old = Previous.getRepresentativeDecl(); - Diag(B.NameLoc, diag::err_redefinition) << B.Name; - Diag(Old->getLocation(), diag::note_previous_definition); + if (IsPlaceholder) { + bool sameDC = (Previous.end() - 1) + ->getDeclContext() + ->getRedeclContext() + ->Equals(DC->getRedeclContext()); + if (sameDC && + isDeclInScope(*(Previous.end() - 1), CurContext, S, false)) { + Previous.clear(); + DiagPlaceholderVariableDefinition(B.NameLoc); + } + } else { + auto *Old = Previous.getRepresentativeDecl(); + Diag(B.NameLoc, diag::err_redefinition) << B.Name; + Diag(Old->getLocation(), diag::note_previous_definition); + } } else if (ShadowedDecl && !D.isRedeclaration()) { CheckShadow(BD, ShadowedDecl, Previous); } diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1168,11 +1168,15 @@ << C->Id << It->second->getBeginLoc() << FixItHint::CreateRemoval( SourceRange(getLocForEndOfToken(PrevCaptureLoc), C->Loc)); - } else + Var->setInvalidDecl(); + } else if (Var && Var->isPlaceholderVar(getLangOpts())) { + DiagPlaceholderVariableDefinition(C->Loc); + } else { // Previous capture captured something different (one or both was // an init-capture): no fixit. Diag(C->Loc, diag::err_capture_more_than_once) << C->Id; - continue; + continue; + } } // Ignore invalid decls; they'll just confuse the code later. @@ -1870,6 +1874,12 @@ if (From.isVLATypeCapture()) return false; + // FIXME: maybe we should warn on these if we can find a sensible diagnostic + // message + if (From.isInitCapture() && + From.getVariable()->isPlaceholderVar(getLangOpts())) + return false; + auto diag = Diag(From.getLocation(), diag::warn_unused_lambda_capture); if (From.isThisCapture()) diag << "'this'"; diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -508,6 +508,7 @@ llvm::SmallDenseMap UniqueTypes; bool Ambiguous = false; + bool ReferenceToPlaceHolderVariable = false; bool HasTag = false, HasFunction = false; bool HasFunctionTemplate = false, HasUnresolved = false; const NamedDecl *HasNonFunction = nullptr; @@ -546,7 +547,7 @@ // For non-type declarations, check for a prior lookup result naming this // canonical declaration. - if (!ExistingI) { + if (!D->isPlaceholderVar(getSema().getLangOpts()) && !ExistingI) { auto UniqueResult = Unique.insert(std::make_pair(D, I)); if (!UniqueResult.second) { // We've seen this entity before. @@ -590,7 +591,11 @@ Decls[I] = Decls[--N]; continue; } - + if (D->isPlaceholderVar(getSema().getLangOpts()) && + getContextForScopeMatching(D) == + getContextForScopeMatching(Decls[I])) { + ReferenceToPlaceHolderVariable = true; + } Ambiguous = true; } HasNonFunction = D; @@ -630,7 +635,9 @@ if (HasNonFunction && (HasFunction || HasUnresolved)) Ambiguous = true; - if (Ambiguous) + if (Ambiguous && ReferenceToPlaceHolderVariable) + setAmbiguous(LookupResult::AmbiguousReferenceToPlaceholderVariable); + else if (Ambiguous) setAmbiguous(LookupResult::AmbiguousReference); else if (HasUnresolved) ResultKind = LookupResult::FoundUnresolvedValue; @@ -2856,6 +2863,13 @@ break; } + case LookupResult::AmbiguousReferenceToPlaceholderVariable: { + Diag(NameLoc, diag::err_using_placeholder_variable) << Name << LookupRange; + for (auto *D : Result) + Diag(D->getLocation(), diag::note_reference_placeholder) << D; + break; + } + case LookupResult::AmbiguousReference: { Diag(NameLoc, diag::err_ambiguous_reference) << Name << LookupRange; diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -34,6 +34,10 @@ // --- C++26 features --- +#if check(placeholder_variables, 202306, 202306, 202306, 202306, 202306, 202306, 202306) +#error "wrong value for __cpp_placeholder_variables" +#endif + // --- C++23 features --- #if check(implicit_move, 0, 0, 0, 0, 0, 202011, 202011) diff --git a/clang/test/Lexer/unicode.c b/clang/test/Lexer/unicode.c --- a/clang/test/Lexer/unicode.c +++ b/clang/test/Lexer/unicode.c @@ -27,7 +27,7 @@ CHECK : The preprocessor should not complain about Unicode characters like ©. #endif - int _; +int a; extern int X\UAAAAAAAA; // expected-error {{not allowed in an identifier}} int Y = '\UAAAAAAAA'; // expected-error {{invalid universal character}} @@ -41,8 +41,8 @@ extern int \u1B4C; // BALINESE LETTER ARCHAIC JNYA - Added in Unicode 14 extern int \U00016AA2; // TANGSA LETTER GA - Added in Unicode 14 extern int \U0001E4D0; // 𞓐 NAG MUNDARI LETTER O - Added in Unicode 15 -extern int _\N{TANGSA LETTER GA}; -extern int _\N{TANGSALETTERGA}; // expected-error {{'TANGSALETTERGA' is not a valid Unicode character name}} \ +extern int a\N{TANGSA LETTER GA}; +extern int a\N{TANGSALETTERGA}; // expected-error {{'TANGSALETTERGA' is not a valid Unicode character name}} \ // expected-error {{expected ';' after top level declarator}} \ // expected-note {{characters names in Unicode escape sequences are sensitive to case and whitespace}} diff --git a/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/cxx2c-placeholder-vars.cpp @@ -0,0 +1,103 @@ +// RUN: %clang -cc1 -fsyntax-only -verify -std=c++2c -Wunused-parameter -Wunused %s + +void static_var() { + static int _; // expected-note {{previous definition is here}} \ + // expected-note {{candidate}} + static int _; // expected-error {{redefinition of '_'}} + int _; // expected-warning {{placeholder variables are a C++2c extension}} \ + // expected-note {{candidate}} + _++; // expected-error{{reference to '_' is ambiguous}} +} + +void static_var_2() { + int _; // expected-note {{previous definition is here}} + static int _; // expected-error {{redefinition of '_'}} +} + +void bindings() { + int arr[4] = {0, 1, 2, 3}; + auto [_, _, _, _] = arr; // expected-warning 3{{placeholder variables are a C++2c extension}} \ + // expected-note 4{{placeholder declared here}} + _ == 42; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + { + auto [_, a, b, c] = arr; // expected-warning {{unused variable '[_, a, b, c]'}} + } + { + auto [_, _, b, c] = arr; // expected-warning {{unused variable '[_, _, b, c]'}} \ + // expected-warning {{placeholder variables are a C++2c extension}} + } + { + auto [_, _, _, _] = arr; // expected-warning 3{{placeholder variables are a C++2c extension}} + } +} + +void lambda() { + (void)[_ = 0, _ = 1] { // expected-warning {{placeholder variables are a C++2c extension}} \ + // expected-note 4{{placeholder declared here}} + (void)_++; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + }; +} + +namespace global_var { + int _; // expected-note {{previous definition is here}} + int _; // expected-error {{redefinition of '_'}} +} + +namespace global_fun { +void _(); +void _(); + +void _() {} // expected-note {{previous definition is here}} +void _() {} // expected-error {{redefinition of '_'}} +void _(int){}; +} + +void extern_test() { + extern int _; + extern int _; // expected-note {{candidate}} + int _; //expected-note {{candidate}} + _++; // expected-error {{reference to '_' is ambiguous}} +} + + +struct Members { + int _; // expected-note {{placeholder declared here}} + int _; // expected-warning{{placeholder variables are a C++2c extension}} \ + // expected-note {{placeholder declared here}} + void f() { + _++; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + } +}; + +namespace using_ { +int _; // expected-note {{target of using declaration}} +void f() { + int _; // expected-note {{conflicting declaration}} + _ = 0; + using using_::_; // expected-error {{target of using declaration conflicts with declaration already in scope}} +} +} + + +void call(int); +void test_param(int _) {} +void test_params(int _, int _); // expected-error {{redefinition of parameter '_'}} \ + // expected-note {{previous declaration is here}} + +template // expected-error {{declaration of '_' shadows template parameter}} \ + // expected-note {{template parameter is declared here}} +auto i = 0; + +template +concept C = requires(T _, T _) { // expected-error {{redefinition of parameter '_'}} \ + // expected-note {{previous declaration is here}} + T{}; +}; + +struct S { + int a; +}; + +void f(S a, S _) { // expected-warning {{unused parameter 'a'}} + +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -145,7 +145,7 @@ Placeholder variables with no name P2169R4 - No + Clang 17