diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -132,6 +132,8 @@ C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ - Compiler flags ``-std=c++2c`` and ``-std=gnu++2c`` have been added for experimental C++2c implementation work. +- Implemented `P2169R4: A nice placeholder with no name `_. This allows to use `_` + 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,10 @@ return hasCachedLinkage(); } + bool isPlaceholderVar() const { return Decl::IsPlaceholder; } + + void setIsPlaceholderVar(bool Set) { Decl::IsPlaceholder = Set; } + /// Looks through UsingDecls and ObjCCompatibleAliasDecls for /// the underlying named decl. NamedDecl *getUnderlyingDecl() { diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -337,6 +337,10 @@ /// Otherwise, it is the linkage + 1. mutable unsigned CacheValidAndLinkage : 3; + // Whether this declaration denotes a placeholder that can be + // redefined in the same scope. + unsigned IsPlaceholder : 1; + /// Allocate memory for a deserialized declaration. /// /// This routine must be used to allocate memory for any declaration that is @@ -385,7 +389,7 @@ Implicit(false), Used(false), Referenced(false), TopLevelDeclInObjCContainer(false), Access(AS_none), FromASTFile(0), IdentifierNamespace(getIdentifierNamespaceForKind(DK)), - CacheValidAndLinkage(0) { + CacheValidAndLinkage(0), IsPlaceholder(false) { if (StatisticsEnabled) add(DK); } @@ -394,7 +398,7 @@ Used(false), Referenced(false), TopLevelDeclInObjCContainer(false), Access(AS_none), FromASTFile(0), IdentifierNamespace(getIdentifierNamespaceForKind(DK)), - CacheValidAndLinkage(0) { + CacheValidAndLinkage(0), IsPlaceholder(false) { if (StatisticsEnabled) add(DK); } 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 @@ -6585,6 +6585,19 @@ InGroup>, DefaultError; // Expressions. +def err_using_placeholder_variable : Error< + "referring to placeholder '_' is not allowed">; +def note_reference_placeholder : Note< + "placeholder declared here">; +def warn_placeholder_variable_has_no_side_effect : Warning< + "placeholder variable has no side effect">, InGroup; + +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 @@ -462,6 +462,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 NotePlaceholderVariableDefinition(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 @@ -1826,9 +1826,9 @@ if (isa(this)) return false; - // For parameters, pick the newer one. This is either an error or (in - // Objective-C) permitted as an extension. - if (isa(this)) + // For parameters, pick the newer one in + // Objective-C as an extension. + if (isa(this) && getLangOpts().ObjC) return true; // Inline namespaces can give us two declarations with the same 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 @@ -708,6 +708,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 @@ -1992,6 +1992,9 @@ return false; } + if (D->isPlaceholderVar()) + return false; + if (D->hasAttr() || D->hasAttr() || D->hasAttr()) return false; @@ -2018,6 +2021,8 @@ // Types of valid local variables should be complete, so this should succeed. if (const VarDecl *VD = dyn_cast(D)) { + if (D->isPlaceholderVar()) + return !VD->hasInit(); const Expr *Init = VD->getInit(); if (const auto *Cleanups = dyn_cast_or_null(Init)) @@ -2144,7 +2149,9 @@ GenerateFixForUnusedDecl(D, Context, Hint); unsigned DiagID; - if (isa(D) && cast(D)->isExceptionVariable()) + if (D->isPlaceholderVar()) { + DiagID = diag::warn_placeholder_variable_has_no_side_effect; + } else if (isa(D) && cast(D)->isExceptionVariable()) DiagID = diag::warn_unused_exception_param; else if (isa(D)) DiagID = diag::warn_unused_label; @@ -2163,6 +2170,9 @@ VD->hasAttr()) return; + if (VD->isPlaceholderVar()) + return; + const auto *Ty = VD->getType().getTypePtr()->getBaseElementTypeUnsafe(); if (Ty->isReferenceType() || Ty->isDependentType()) @@ -7435,6 +7445,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 @@ -7453,6 +7464,19 @@ 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()) { + auto Decl = *Previous.begin(); + const bool sameDC = Decl->getDeclContext()->getRedeclContext()->Equals( + DC->getRedeclContext()); + if (sameDC && isDeclInScope(Decl, CurContext, S, false)) { + NotePlaceholderVariableDefinition(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() && @@ -7723,6 +7747,7 @@ // Set the lexical context. If the declarator has a C++ scope specifier, the // lexical context will be different from the semantic context. NewVD->setLexicalDeclContext(CurContext); + NewVD->setIsPlaceholderVar(IsPlaceholderVariable); if (NewTemplate) NewTemplate->setLexicalDeclContext(CurContext); @@ -8021,7 +8046,7 @@ NewVD->setInvalidDecl(); } - if (!IsVariableTemplateSpecialization) + if (!IsVariableTemplateSpecialization && !IsPlaceholderVariable) D.setRedeclaration(CheckVariableDeclaration(NewVD, Previous)); // CheckVariableDeclaration will set NewVD as invalid if something is in @@ -8059,7 +8084,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); @@ -8293,6 +8318,10 @@ } } + // Never warn about shadowing placeholder variable + if (ShadowedDecl->isPlaceholderVar()) + 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. @@ -14753,17 +14782,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()); @@ -14832,7 +14861,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(); } @@ -18184,10 +18214,15 @@ if (InvalidDecl) NewFD->setInvalidDecl(); + NewFD->setIsPlaceholderVar(LangOpts.CPlusPlus && II && II->isPlaceholder()); 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()) { + NotePlaceholderVariableDefinition(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::NotePlaceholderVariableDefinition(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,7 @@ for (auto &B : D.getDecompositionDeclarator().bindings()) { // Check for name conflicts. DeclarationNameInfo NameInfo(B.Name, B.NameLoc); + IdentifierInfo *VarName = B.Name; LookupResult Previous(*this, NameInfo, LookupOrdinaryName, ForVisibleRedeclaration); LookupName(Previous, S, @@ -890,7 +897,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 +909,25 @@ FilterLookupForScope(Previous, DC, S, ConsiderLinkage, /*AllowInlineNamespace*/false); + bool IsPlaceholder = DS.getStorageClassSpec() != DeclSpec::SCS_static && + DC->isFunctionOrMethod() && VarName->isPlaceholder(); + BD->setIsPlaceholderVar(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) { + const bool sameDC = (Previous.end() - 1) + ->getDeclContext() + ->getRedeclContext() + ->Equals(DC->getRedeclContext()); + if (sameDC && + isDeclInScope(*(Previous.end() - 1), CurContext, S, false)) { + Previous.clear(); + NotePlaceholderVariableDefinition(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 @@ -1148,6 +1148,10 @@ continue; } + if (Var && Var->isInitCapture() && C->Id->isPlaceholder()) { + Var->setIsPlaceholderVar(true); + } + // C++11 [expr.prim.lambda]p10: // [...] each such lookup shall find a variable with automatic storage // duration declared in the reaching scope of the local lambda expression. @@ -1168,11 +1172,15 @@ << C->Id << It->second->getBeginLoc() << FixItHint::CreateRemoval( SourceRange(getLocForEndOfToken(PrevCaptureLoc), C->Loc)); - } else + Var->setInvalidDecl(); + } else if (Var && Var->isPlaceholderVar()) { + NotePlaceholderVariableDefinition(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,7 +1878,13 @@ if (From.isVLATypeCapture()) return false; - auto diag = Diag(From.getLocation(), diag::warn_unused_lambda_capture); + bool IsPlaceholder = + From.isInitCapture() && From.getVariable()->isPlaceholderVar(); + + auto diag = + Diag(From.getLocation(), + IsPlaceholder ? diag::warn_placeholder_variable_has_no_side_effect + : diag::warn_unused_lambda_capture); if (From.isThisCapture()) diag << "'this'"; else 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() && !ExistingI) { auto UniqueResult = Unique.insert(std::make_pair(D, I)); if (!UniqueResult.second) { // We've seen this entity before. @@ -590,7 +591,10 @@ Decls[I] = Decls[--N]; continue; } - + if (D->isPlaceholderVar() && getContextForScopeMatching(D) == + getContextForScopeMatching(Decls[I])) { + ReferenceToPlaceHolderVariable = true; + } Ambiguous = true; } HasNonFunction = D; @@ -630,7 +634,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 +2862,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 @@ -54,6 +54,10 @@ #error "wrong value for __cpp_named_character_escapes" #endif +#if check(placeholder_variables, 202306, 202306, 202306, 202306, 202306, 202306) +#error "wrong value for __cpp_named_character_escapes" +#endif + #if check(explicit_this_parameter, 0, 0, 0, 0, 0, 0) #error "wrong value for __cpp_explicit_this_parameter" #endif 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,98 @@ +// 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 {{referring to placeholder '_' is not allowed}} + { + auto [_, a, b, c] = arr; + auto [_, _, _, _] = arr; // expected-warning 4{{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}} \\ + // expected-warning 2{{placeholder variable has no side effect}} + (void)_++; // expected-error {{referring to placeholder '_' is not allowed}} + }; +} + +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 {{referring to placeholder '_' is not allowed}} + } +}; + +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