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,21 @@ ^^^^^^^^^^^^^^^^^^^^^ - 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. + An extension warning is produced when multiple variables are introduced by `_` in the same scope. + Unused warnings are no longer produced for variables named `_`. + + .. code-block:: cpp + struct S { + int _, _; // Was invalid, now OK + }; + void func() { + int _, _; // Was invalid, now OK + } + void other() { + int _; // Previously diagnosed under -Wunused, no longer diagnosed + } 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,10 @@ NamedDecl * ActOnDecompositionDeclarator(Scope *S, Declarator &D, MultiTemplateParamsArg TemplateParamLists); + void DiagPlaceholderVariableDefinition(SourceLocation Loc); + bool DiagRedefinedPlaceholderFieldDecl(SourceLocation Loc, + RecordDecl *ClassDecl, + const IdentifierInfo *Name); // Returns true if the variable declaration is a redeclaration bool CheckVariableDeclaration(VarDecl *NewVD, LookupResult &Previous); void CheckVariableDeclarationType(VarDecl *NewVD); @@ -3295,6 +3299,12 @@ RecordDecl *Record, const PrintingPolicy &Policy); + /// Called once it is known whether + /// a tag declaration is an anonymous union or struct. + void ActOnDefinedDeclarationSpecifier(Decl *D); + + void DiagPlaceholderFieldDeclDefinitions(RecordDecl *Record); + Decl *BuildMicrosoftCAnonymousStruct(Scope *S, DeclSpec &DS, RecordDecl *Record); @@ -6133,6 +6143,9 @@ CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; + ValueDecl *tryLookupUnambiguousFieldDecl(RecordDecl *ClassDecl, + const IdentifierInfo *MemberOrBase); + private: // A cache representing if we've fully checked the various comparison category // types stored in ASTContext. The bit-index corresponds to the integer value 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,42 @@ 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; + if (auto *IFD = dyn_cast(this)) { + if (!getDeclContext()->isFunctionOrMethod() && + !getDeclContext()->isRecord()) + return false; + VarDecl *VD = IFD->getVarDecl(); + return !VD || VD->getStorageDuration() == SD_Automatic; + } + // and it declares a variable with automatic storage duration + if (const auto *VD = dyn_cast(this)) { + if (isa(VD)) + return false; + if (VD->isInitCapture()) + return true; + return VD->getStorageDuration() == StorageDuration::SD_Automatic; + } + if (const auto *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/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1939,6 +1939,7 @@ RecordDecl *AnonRecord = nullptr; Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec( getCurScope(), AS_none, DS, ParsedAttributesView::none(), AnonRecord); + Actions.ActOnDefinedDeclarationSpecifier(TheDecl); DS.complete(TheDecl); if (AnonRecord) { Decl* decls[] = {AnonRecord, TheDecl}; @@ -1947,6 +1948,9 @@ return Actions.ConvertDeclToDeclGroup(TheDecl); } + if (DS.hasTagDefinition()) + Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl()); + if (DeclSpecStart) DS.SetRangeStart(*DeclSpecStart); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2845,6 +2845,7 @@ RecordDecl *AnonRecord = nullptr; Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec( getCurScope(), AS, DS, DeclAttrs, TemplateParams, false, AnonRecord); + Actions.ActOnDefinedDeclarationSpecifier(TheDecl); DS.complete(TheDecl); if (AnonRecord) { Decl *decls[] = {AnonRecord, TheDecl}; @@ -2853,6 +2854,9 @@ return Actions.ConvertDeclToDeclGroup(TheDecl); } + if (DS.hasTagDefinition()) + Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl()); + ParsingDeclarator DeclaratorInfo(*this, DS, DeclAttrs, DeclaratorContext::Member); if (TemplateInfo.TemplateParams) diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp --- a/clang/lib/Parse/ParseTemplate.cpp +++ b/clang/lib/Parse/ParseTemplate.cpp @@ -248,12 +248,16 @@ : MultiTemplateParamsArg(), TemplateInfo.Kind == ParsedTemplateInfo::ExplicitInstantiation, AnonRecord); + Actions.ActOnDefinedDeclarationSpecifier(Decl); assert(!AnonRecord && "Anonymous unions/structs should not be valid with template"); DS.complete(Decl); return Decl; } + if (DS.hasTagDefinition()) + Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl()); + // Move the attributes from the prefix into the DS. if (TemplateInfo.Kind == ParsedTemplateInfo::ExplicitInstantiation) ProhibitAttributes(prefixAttrs); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -1157,6 +1157,7 @@ Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec( getCurScope(), AS_none, DS, ParsedAttributesView::none(), AnonRecord); DS.complete(TheDecl); + Actions.ActOnDefinedDeclarationSpecifier(TheDecl); if (AnonRecord) { Decl* decls[] = {AnonRecord, TheDecl}; return Actions.BuildDeclaratorGroup(decls); @@ -1164,6 +1165,9 @@ return Actions.ConvertDeclToDeclGroup(TheDecl); } + if (DS.hasTagDefinition()) + Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl()); + // ObjC2 allows prefix attributes on class interfaces and protocols. // FIXME: This still needs better diagnostics. We should only accept // attributes here, no types, etc. 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,8 @@ UnusedFileScopedDecls.push_back(D); } -static bool ShouldDiagnoseUnusedDecl(const NamedDecl *D) { +static bool ShouldDiagnoseUnusedDecl(const LangOptions &LangOpts, + const NamedDecl *D) { if (D->isInvalidDecl()) return false; @@ -1984,15 +1985,23 @@ // 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(LangOpts); + } + if (IsAllPlaceholders) + return false; } else if (!D->getDeclName()) { return false; } else if (D->isReferenced() || D->isUsed()) { return false; } + if (D->isPlaceholderVar(LangOpts)) + 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(getLangOpts(), 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(); @@ -5337,13 +5349,14 @@ /// check if there's an existing declaration that can't be overloaded. /// /// \return true if this is a forbidden redeclaration -static bool CheckAnonMemberRedeclaration(Sema &SemaRef, - Scope *S, +static bool CheckAnonMemberRedeclaration(Sema &SemaRef, Scope *S, DeclContext *Owner, DeclarationName Name, - SourceLocation NameLoc, - bool IsUnion) { - LookupResult R(SemaRef, Name, NameLoc, Sema::LookupMemberName, + SourceLocation NameLoc, bool IsUnion, + StorageClass SC) { + LookupResult R(SemaRef, Name, NameLoc, + Owner->isRecord() ? Sema::LookupMemberName + : Sema::LookupOrdinaryName, Sema::ForVisibleRedeclaration); if (!SemaRef.LookupName(R, S)) return false; @@ -5354,6 +5367,14 @@ if (!SemaRef.isDeclInScope(PrevDecl, Owner, S)) return false; + if (SC == StorageClass::SC_None && + PrevDecl->isPlaceholderVar(SemaRef.getLangOpts()) && + (Owner->isFunctionOrMethod() || Owner->isRecord())) { + if (!Owner->isRecord()) + SemaRef.DiagPlaceholderVariableDefinition(NameLoc); + return false; + } + SemaRef.Diag(NameLoc, diag::err_anonymous_record_member_redecl) << IsUnion << Name; SemaRef.Diag(PrevDecl->getLocation(), diag::note_previous_declaration); @@ -5361,6 +5382,36 @@ return true; } +void Sema::ActOnDefinedDeclarationSpecifier(Decl *D) { + if (auto *RD = dyn_cast_if_present(D)) + DiagPlaceholderFieldDeclDefinitions(RD); +} + +/// Emit diagnostic warnings for placeholder members. +/// We can only do that after the class is fully constructed, +/// as anonymous union/structs can insert placeholders +/// in their parent scope (which might be a Record). +void Sema::DiagPlaceholderFieldDeclDefinitions(RecordDecl *Record) { + if (!getLangOpts().CPlusPlus) + return; + + // This function can be parsed before we have validated the + // structure as an anonymous struct + if (Record->isAnonymousStructOrUnion()) + return; + + const NamedDecl *First = 0; + for (const Decl *D : Record->decls()) { + const NamedDecl *ND = dyn_cast(D); + if (!ND || !ND->isPlaceholderVar(getLangOpts())) + continue; + if (!First) + First = ND; + else + DiagPlaceholderVariableDefinition(ND->getLocation()); + } +} + /// InjectAnonymousStructOrUnionMembers - Inject the members of the /// anonymous struct or union AnonRecord into the owning context Owner /// and scope S. This routine will be invoked just after we realize @@ -5380,6 +5431,7 @@ static bool InjectAnonymousStructOrUnionMembers(Sema &SemaRef, Scope *S, DeclContext *Owner, RecordDecl *AnonRecord, AccessSpecifier AS, + StorageClass SC, SmallVectorImpl &Chaining) { bool Invalid = false; @@ -5389,8 +5441,8 @@ cast(D)->getDeclName()) { ValueDecl *VD = cast(D); if (CheckAnonMemberRedeclaration(SemaRef, S, Owner, VD->getDeclName(), - VD->getLocation(), - AnonRecord->isUnion())) { + VD->getLocation(), AnonRecord->isUnion(), + SC)) { // C++ [class.union]p2: // The names of the members of an anonymous union shall be // distinct from the names of any other entity in the @@ -5690,6 +5742,7 @@ // Mock up a declarator. Declarator Dc(DS, ParsedAttributesView::none(), DeclaratorContext::Member); + StorageClass SC = StorageClassSpecToVarDeclStorageClass(DS); TypeSourceInfo *TInfo = GetTypeForDeclarator(Dc, S); assert(TInfo && "couldn't build declarator info for anonymous struct/union"); @@ -5708,7 +5761,6 @@ FieldCollector->Add(cast(Anon)); } else { DeclSpec::SCS SCSpec = DS.getStorageClassSpec(); - StorageClass SC = StorageClassSpecToVarDeclStorageClass(DS); if (SCSpec == DeclSpec::SCS_mutable) { // mutable can only appear on non-static class members, so it's always // an error here @@ -5744,7 +5796,8 @@ SmallVector Chain; Chain.push_back(Anon); - if (InjectAnonymousStructOrUnionMembers(*this, S, Owner, Record, AS, Chain)) + if (InjectAnonymousStructOrUnionMembers(*this, S, Owner, Record, AS, SC, + Chain)) Invalid = true; if (VarDecl *NewVD = dyn_cast(Anon)) { @@ -5813,8 +5866,9 @@ RecordDecl *RecordDef = Record->getDefinition(); if (RequireCompleteSizedType(Anon->getLocation(), RecTy, diag::err_field_incomplete_or_sizeless) || - InjectAnonymousStructOrUnionMembers(*this, S, CurContext, RecordDef, - AS_none, Chain)) { + InjectAnonymousStructOrUnionMembers( + *this, S, CurContext, RecordDef, AS_none, + StorageClassSpecToVarDeclStorageClass(DS), Chain)) { Anon->setInvalidDecl(); ParentDecl->setInvalidDecl(); } @@ -7448,6 +7502,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 +7521,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 +8101,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 +8139,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 +8373,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 +14840,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 +14919,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(); } @@ -18210,7 +18282,8 @@ if (InvalidDecl) NewFD->setInvalidDecl(); - if (PrevDecl && !isa(PrevDecl)) { + if (PrevDecl && !isa(PrevDecl) && + !PrevDecl->isPlaceholderVar(getLangOpts())) { Diag(Loc, diag::err_duplicate_member) << II; Diag(PrevDecl->getLocation(), diag::note_previous_declaration); NewFD->setInvalidDecl(); 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::warn_cxx23_placeholder_var_definition + : diag::ext_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); } @@ -4300,16 +4323,57 @@ } +bool Sema::DiagRedefinedPlaceholderFieldDecl(SourceLocation Loc, + RecordDecl *ClassDecl, + const IdentifierInfo *Name) { + DeclContextLookupResult Result = ClassDecl->lookup(Name); + DeclContextLookupResult::iterator Found = + llvm::find_if(Result, [this](const NamedDecl *Elem) { + return (isa(Elem) || isa(Elem)) && + Elem->isPlaceholderVar(getLangOpts()); + }); + // We did not find a placeholder variable + if (Found == Result.end()) + return false; + Diag(Loc, diag::err_using_placeholder_variable) << Name; + for (DeclContextLookupResult::iterator It = Found; It != Result.end(); It++) { + const NamedDecl *ND = *It; + if (ND->getDeclContext() != ND->getDeclContext()) + break; + if ((isa(ND) || isa(ND)) && + ND->isPlaceholderVar(getLangOpts())) + Diag(ND->getLocation(), diag::note_reference_placeholder) << ND; + } + return true; +} + +ValueDecl * +Sema::tryLookupUnambiguousFieldDecl(RecordDecl *ClassDecl, + const IdentifierInfo *MemberOrBase) { + ValueDecl *ND = nullptr; + for (auto *D : ClassDecl->lookup(MemberOrBase)) { + if (isa(D) || isa(D)) { + bool IsPlaceholder = D->isPlaceholderVar(getLangOpts()); + if (ND) { + if (IsPlaceholder && D->getDeclContext() == ND->getDeclContext()) + return nullptr; + break; + } + if (!IsPlaceholder) + return cast(D); + ND = cast(D); + } + } + return ND; +} + ValueDecl *Sema::tryLookupCtorInitMemberDecl(CXXRecordDecl *ClassDecl, CXXScopeSpec &SS, ParsedType TemplateTypeTy, IdentifierInfo *MemberOrBase) { if (SS.getScopeRep() || TemplateTypeTy) return nullptr; - for (auto *D : ClassDecl->lookup(MemberOrBase)) - if (isa(D) || isa(D)) - return cast(D); - return nullptr; + return tryLookupUnambiguousFieldDecl(ClassDecl, MemberOrBase); } /// Handle a C++ member initializer. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -16659,10 +16659,15 @@ MemberDecl = IndirectMemberDecl->getAnonField(); } - if (!MemberDecl) - return ExprError(Diag(BuiltinLoc, diag::err_no_member) - << OC.U.IdentInfo << RD << SourceRange(OC.LocStart, - OC.LocEnd)); + if (!MemberDecl) { + // Lookup could be ambiguous when looking up a placeholder variable + // __builtin_offsetof(S, _). + // In that case we would already have emitted a diagnostic + if (!R.isAmbiguous()) + Diag(BuiltinLoc, diag::err_no_member) + << OC.U.IdentInfo << RD << SourceRange(OC.LocStart, OC.LocEnd); + return ExprError(); + } // C99 7.17p3: // (If the specified member is a bit-field, the behavior is undefined.) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -2603,21 +2603,17 @@ FieldDecl *KnownField = D->getFieldDecl(); if (!KnownField) { const IdentifierInfo *FieldName = D->getFieldName(); - DeclContext::lookup_result Lookup = RT->getDecl()->lookup(FieldName); - for (NamedDecl *ND : Lookup) { - if (auto *FD = dyn_cast(ND)) { - KnownField = FD; - break; - } - if (auto *IFD = dyn_cast(ND)) { - // In verify mode, don't modify the original. - if (VerifyOnly) - DIE = CloneDesignatedInitExpr(SemaRef, DIE); - ExpandAnonymousFieldDesignator(SemaRef, DIE, DesigIdx, IFD); - D = DIE->getDesignator(DesigIdx); - KnownField = cast(*IFD->chain_begin()); - break; - } + ValueDecl *VD = + SemaRef.tryLookupUnambiguousFieldDecl(RT->getDecl(), FieldName); + if (auto *FD = dyn_cast_if_present(VD)) { + KnownField = FD; + } else if (auto *IFD = dyn_cast_if_present(VD)) { + // In verify mode, don't modify the original. + if (VerifyOnly) + DIE = CloneDesignatedInitExpr(SemaRef, DIE); + ExpandAnonymousFieldDesignator(SemaRef, DIE, DesigIdx, IFD); + D = DIE->getDesignator(DesigIdx); + KnownField = cast(*IFD->chain_begin()); } if (!KnownField) { if (VerifyOnly) { @@ -2625,10 +2621,17 @@ return true; // No typo correction when just trying this out. } + // We found a placeholder variable + if (SemaRef.DiagRedefinedPlaceholderFieldDecl( + DIE->getBeginLoc(), RT->getDecl(), FieldName)) { + ++Index; + return true; + } // Name lookup found something, but it wasn't a field. - if (!Lookup.empty()) { + if (DeclContextLookupResult Lookup = RT->getDecl()->lookup(FieldName); + !Lookup.empty()) { SemaRef.Diag(D->getFieldLoc(), diag::err_field_designator_nonfield) - << FieldName; + << FieldName; SemaRef.Diag(Lookup.front()->getLocation(), diag::note_field_designator_found); ++Index; 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,18 @@ break; } + case LookupResult::AmbiguousReferenceToPlaceholderVariable: { + Diag(NameLoc, diag::err_using_placeholder_variable) << Name << LookupRange; + DeclContext *DC = nullptr; + for (auto *D : Result) { + Diag(D->getLocation(), diag::note_reference_placeholder) << D; + if (DC != nullptr && DC != D->getDeclContext()) + break; + DC = D->getDeclContext(); + } + 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/anonymous-union-export.cpp b/clang/test/SemaCXX/anonymous-union-export.cpp --- a/clang/test/SemaCXX/anonymous-union-export.cpp +++ b/clang/test/SemaCXX/anonymous-union-export.cpp @@ -3,5 +3,5 @@ export module M; export { // expected-note 2{{export block begins here}} union { bool a; }; // expected-error {{anonymous unions at namespace or global scope must be declared 'static'}} expected-error {{declaration of 'a' with internal linkage cannot be exported}} - static union { bool a; }; // expected-error {{declaration of 'a' with internal linkage cannot be exported}} + static union { bool b; }; // expected-error {{declaration of 'b' with internal linkage cannot be exported}} } 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,255 @@ +/////////////// +// RUN: %clang -cc1 -fsyntax-only -verify -std=c++2c -Wunused-parameter -Wunused -Wpre-c++26-compat %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 incompatible with C++ standards before C++2c}} \ + // 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 incompatible with C++ standards before C++2c}} \ + // expected-note 4{{placeholder declared here}} + _ == 42; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + { + // no extension warning as we only introduce a single placeholder. + 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 incompatible with C++ standards before C++2c}} + } + { + // There are only 3 extension warnings because the first + // introduction of `_` is valid in all C++ standards + auto [_, _, _, _] = arr; // expected-warning 3{{placeholder variables are incompatible with C++ standards before C++2c}} + } +} + +namespace StaticBindings { + +int arr[2] = {0, 1}; +static auto [_, _] = arr; // expected-error {{redefinition of '_'}} \ + // expected-note {{previous definition is here}} + +void f() { + int arr[2] = {0, 1}; + static auto [_, _] = arr; // expected-error {{redefinition of '_'}} \ + // expected-note {{previous definition is here}} +} + +} + +void lambda() { + (void)[_ = 0, _ = 1] { // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 4{{placeholder declared here}} + (void)_++; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + }; + + { + int _ = 12; + (void)[_ = 0]{}; // no warning (different scope) + } +} + +namespace global_var { + int _; // expected-note {{previous definition is here}} + int _; // expected-error {{redefinition of '_'}} +} + +namespace { + 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){} +} + +typedef int _; +typedef int _; // Type redeclaration, nothing to do with placeholders + +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 2{{placeholder declared here}} + int _; // expected-warning{{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 2{{placeholder declared here}} + void f() { + _++; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + } + void attributes() __attribute__((diagnose_if(_ != 0, "oh no!", "warning"))); // 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'}} + +} + +void unused_warning() { + int _ = 12; // placeholder variable, no unused-but-set warning + int x = 12; // expected-warning {{unused variable 'x'}} + int _ = 12; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} +} + +struct ShadowMembers { + int _; + void f() { + int _; + _ = 12; // Ok, access the local variable + (void)({ int _ = 12; _;}); // Ok, inside a different scope + } +}; + +struct MemberPtrs { + int _, _; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 4{{placeholder declared here}} +}; +constexpr int oh_no = __builtin_offsetof(MemberPtrs, _); // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} +int MemberPtrs::* ref = &MemberPtrs::_; // expected-error{{ambiguous reference to placeholder '_', which is defined multiple times}} + + +struct MemberInitializer { + MemberInitializer() : _(0) {} // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + int _, _; // expected-note 2{{placeholder declared here}} \ + // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} +}; + +struct MemberAndUnion { + int _; // expected-note {{placeholder declared here}} + union { int _; int _; }; // expected-note 2 {{placeholder declared here}} \ + // expected-warning 2{{placeholder variables are incompatible with C++ standards before C++2c}} + + + MemberAndUnion() : _(0) {} // expected-error {{ambiguous reference to placeholder '_', which is defined multiple time}} +}; + +struct Union { union { int _, _, _; }; }; // expected-note 3{{placeholder declared here}} \ + // expected-warning 2{{placeholder variables are incompatible with C++ standards before C++2c}} + +void TestUnion() { + Union c; + c._ = 0; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} +} + +void AnonymousLocals() { + union {int _, _;}; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 2{{placeholder declared here}} + union {int _, _;}; // expected-warning 2{{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 2{{placeholder declared here}} + _. = 0; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} +} + +namespace StaticUnions { + +static union { int _ = 42; }; // expected-note {{previous declaration is here}} +static union { int _ = 43; }; // expected-error {{member of anonymous union redeclares '_'}} + +inline void StaticUnion() { + static union { int _{}; }; // expected-note {{previous declaration is here}} + static union { int _{}; }; // expected-error {{member of anonymous union redeclares '_'}} +} + +} + +namespace TagVariables { + +[[maybe_unused]] struct { + int _, _, _; // expected-warning 2{{placeholder variables are incompatible with C++ standards before C++2c}} +} a; + +[[maybe_unused]] union { + int _, _, _; // expected-warning 2{{placeholder variables are incompatible with C++ standards before C++2c}} +} b; + +} + +namespace MemberLookupTests { + +struct S { + int _, _; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 8{{placeholder declared here}} + + void f() { + _ ++ ; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + } +}; + +struct T : S { + +}; + +void Test() { + S s{._ =0}; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + S{}._; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + T{}._; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} +}; + +}; + +namespace Bases { + struct S { + int _, _; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 2{{placeholder declared here}} + int a; + }; + struct T : S{ + int _, _; // expected-warning {{placeholder variables are incompatible with C++ standards before C++2c}} \ + // expected-note 2{{placeholder declared here}} + int a; + void f() { + _; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} + S::_; // expected-error {{ambiguous reference to placeholder '_', which is defined multiple times}} \ + // expected-error {{a type specifier is required for all declarations}} + } + }; +} 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