Skip to content

Commit a8bef88

Browse files
committed
[Clang] Implement P2169 A nice placeholder with no name
This is a C++ feature that allows the use of `_` to declare multiple variable of that name in the same scope; these variables can then not be referred to. In addition, while P2169 does not extend to parameter declarations, we stop warning on unused parameters of that name, for consistency. The feature is backported to all C++ language modes. Reviewed By: #clang-language-wg, aaron.ballman Differential Revision: https://reviews.llvm.org/D153536
1 parent 1e92e25 commit a8bef88

23 files changed

+607
-64
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ C++23 Feature Support
6666
C++2c Feature Support
6767
^^^^^^^^^^^^^^^^^^^^^
6868

69+
- Implemented `P2169R4: A nice placeholder with no name <https://wg21.link/P2169R4>`_. This allows using ``_``
70+
as a variable name multiple times in the same scope and is supported in all C++ language modes as an extension.
71+
An extension warning is produced when multiple variables are introduced by ``_`` in the same scope.
72+
Unused warnings are no longer produced for variables named ``_``.
73+
Currently, inspecting placeholders variables in a debugger when more than one are declared in the same scope
74+
is not supported.
75+
76+
.. code-block:: cpp
77+
struct S {
78+
int _, _; // Was invalid, now OK
79+
};
80+
void func() {
81+
int _, _; // Was invalid, now OK
82+
}
83+
void other() {
84+
int _; // Previously diagnosed under -Wunused, no longer diagnosed
85+
}
86+
87+
88+
6989
Resolutions to C++ Defect Reports
7090
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7191

clang/include/clang/AST/Decl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ class NamedDecl : public Decl {
452452
return hasCachedLinkage();
453453
}
454454

455+
bool isPlaceholderVar(const LangOptions &LangOpts) const;
456+
455457
/// Looks through UsingDecls and ObjCCompatibleAliasDecls for
456458
/// the underlying named decl.
457459
NamedDecl *getUnderlyingDecl() {

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6611,6 +6611,16 @@ def warn_atomic_member_access : Warning<
66116611
InGroup<DiagGroup<"atomic-access">>, DefaultError;
66126612

66136613
// Expressions.
6614+
def err_using_placeholder_variable : Error<
6615+
"ambiguous reference to placeholder '_', which is defined multiple times">;
6616+
def note_reference_placeholder : Note<
6617+
"placeholder declared here">;
6618+
def ext_placeholder_var_definition : ExtWarn<
6619+
"placeholder variables are a C++2c extension">, InGroup<CXX26>;
6620+
def warn_cxx23_placeholder_var_definition : Warning<
6621+
"placeholder variables are incompatible with C++ standards before C++2c">,
6622+
DefaultIgnore, InGroup<CXXPre26Compat>;
6623+
66146624
def ext_sizeof_alignof_function_type : Extension<
66156625
"invalid application of '%0' to a function type">, InGroup<PointerArith>;
66166626
def ext_sizeof_alignof_void_type : Extension<

clang/include/clang/Basic/IdentifierTable.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
504504
/// If the identifier is an "uglified" reserved name, return a cleaned form.
505505
/// e.g. _Foo => Foo. Otherwise, just returns the name.
506506
StringRef deuglifiedName() const;
507+
bool isPlaceholder() const {
508+
return getLength() == 1 && getNameStart()[0] == '_';
509+
}
507510

508511
/// Provide less than operator for lexicographical sorting.
509512
bool operator<(const IdentifierInfo &RHS) const {

clang/include/clang/Sema/Lookup.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ class LookupResult {
117117
/// @endcode
118118
AmbiguousReference,
119119

120+
/// Name lookup results in an ambiguity because multiple placeholder
121+
/// variables were found in the same scope.
122+
/// @code
123+
/// void f() {
124+
/// int _ = 0;
125+
/// int _ = 0;
126+
/// return _; // ambiguous use of placeholder variable
127+
/// }
128+
/// @endcode
129+
AmbiguousReferenceToPlaceholderVariable,
130+
120131
/// Name lookup results in an ambiguity because an entity with a
121132
/// tag name was hidden by an entity with an ordinary name from
122133
/// a different context.

clang/include/clang/Sema/Sema.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,10 @@ class Sema final {
29602960
NamedDecl *
29612961
ActOnDecompositionDeclarator(Scope *S, Declarator &D,
29622962
MultiTemplateParamsArg TemplateParamLists);
2963+
void DiagPlaceholderVariableDefinition(SourceLocation Loc);
2964+
bool DiagRedefinedPlaceholderFieldDecl(SourceLocation Loc,
2965+
RecordDecl *ClassDecl,
2966+
const IdentifierInfo *Name);
29632967
// Returns true if the variable declaration is a redeclaration
29642968
bool CheckVariableDeclaration(VarDecl *NewVD, LookupResult &Previous);
29652969
void CheckVariableDeclarationType(VarDecl *NewVD);
@@ -3316,6 +3320,12 @@ class Sema final {
33163320
RecordDecl *Record,
33173321
const PrintingPolicy &Policy);
33183322

3323+
/// Called once it is known whether
3324+
/// a tag declaration is an anonymous union or struct.
3325+
void ActOnDefinedDeclarationSpecifier(Decl *D);
3326+
3327+
void DiagPlaceholderFieldDeclDefinitions(RecordDecl *Record);
3328+
33193329
Decl *BuildMicrosoftCAnonymousStruct(Scope *S, DeclSpec &DS,
33203330
RecordDecl *Record);
33213331

@@ -6167,6 +6177,9 @@ class Sema final {
61676177
CXXRecordDecl *getStdBadAlloc() const;
61686178
EnumDecl *getStdAlignValT() const;
61696179

6180+
ValueDecl *tryLookupUnambiguousFieldDecl(RecordDecl *ClassDecl,
6181+
const IdentifierInfo *MemberOrBase);
6182+
61706183
private:
61716184
// A cache representing if we've fully checked the various comparison category
61726185
// types stored in ASTContext. The bit-index corresponds to the integer value

clang/lib/AST/Decl.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,42 @@ bool NamedDecl::isLinkageValid() const {
10961096
return L == getCachedLinkage();
10971097
}
10981098

1099+
bool NamedDecl::isPlaceholderVar(const LangOptions &LangOpts) const {
1100+
// [C++2c] [basic.scope.scope]/p5
1101+
// A declaration is name-independent if its name is _ and it declares
1102+
// - a variable with automatic storage duration,
1103+
// - a structured binding not inhabiting a namespace scope,
1104+
// - the variable introduced by an init-capture
1105+
// - or a non-static data member.
1106+
1107+
if (!LangOpts.CPlusPlus || !getIdentifier() ||
1108+
!getIdentifier()->isPlaceholder())
1109+
return false;
1110+
if (isa<FieldDecl>(this))
1111+
return true;
1112+
if (auto *IFD = dyn_cast<IndirectFieldDecl>(this)) {
1113+
if (!getDeclContext()->isFunctionOrMethod() &&
1114+
!getDeclContext()->isRecord())
1115+
return false;
1116+
VarDecl *VD = IFD->getVarDecl();
1117+
return !VD || VD->getStorageDuration() == SD_Automatic;
1118+
}
1119+
// and it declares a variable with automatic storage duration
1120+
if (const auto *VD = dyn_cast<VarDecl>(this)) {
1121+
if (isa<ParmVarDecl>(VD))
1122+
return false;
1123+
if (VD->isInitCapture())
1124+
return true;
1125+
return VD->getStorageDuration() == StorageDuration::SD_Automatic;
1126+
}
1127+
if (const auto *BD = dyn_cast<BindingDecl>(this);
1128+
BD && getDeclContext()->isFunctionOrMethod()) {
1129+
VarDecl *VD = BD->getHoldingVar();
1130+
return !VD || VD->getStorageDuration() == StorageDuration::SD_Automatic;
1131+
}
1132+
return false;
1133+
}
1134+
10991135
ReservedIdentifierStatus
11001136
NamedDecl::isReserved(const LangOptions &LangOpts) const {
11011137
const IdentifierInfo *II = getIdentifier();

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
720720
if (LangOpts.CPlusPlus11)
721721
Builder.defineMacro("__cpp_static_call_operator", "202207L");
722722
Builder.defineMacro("__cpp_named_character_escapes", "202207L");
723+
Builder.defineMacro("__cpp_placeholder_variables", "202306L");
723724

724725
if (LangOpts.Char8)
725726
Builder.defineMacro("__cpp_char8_t", "202207L");

clang/lib/Parse/ParseDecl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,6 +1939,7 @@ Parser::DeclGroupPtrTy Parser::ParseSimpleDeclaration(
19391939
RecordDecl *AnonRecord = nullptr;
19401940
Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec(
19411941
getCurScope(), AS_none, DS, ParsedAttributesView::none(), AnonRecord);
1942+
Actions.ActOnDefinedDeclarationSpecifier(TheDecl);
19421943
DS.complete(TheDecl);
19431944
if (AnonRecord) {
19441945
Decl* decls[] = {AnonRecord, TheDecl};
@@ -1947,6 +1948,9 @@ Parser::DeclGroupPtrTy Parser::ParseSimpleDeclaration(
19471948
return Actions.ConvertDeclToDeclGroup(TheDecl);
19481949
}
19491950

1951+
if (DS.hasTagDefinition())
1952+
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());
1953+
19501954
if (DeclSpecStart)
19511955
DS.SetRangeStart(*DeclSpecStart);
19521956

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2861,6 +2861,7 @@ Parser::ParseCXXClassMemberDeclaration(AccessSpecifier AS,
28612861
RecordDecl *AnonRecord = nullptr;
28622862
Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec(
28632863
getCurScope(), AS, DS, DeclAttrs, TemplateParams, false, AnonRecord);
2864+
Actions.ActOnDefinedDeclarationSpecifier(TheDecl);
28642865
DS.complete(TheDecl);
28652866
if (AnonRecord) {
28662867
Decl *decls[] = {AnonRecord, TheDecl};
@@ -2869,6 +2870,9 @@ Parser::ParseCXXClassMemberDeclaration(AccessSpecifier AS,
28692870
return Actions.ConvertDeclToDeclGroup(TheDecl);
28702871
}
28712872

2873+
if (DS.hasTagDefinition())
2874+
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());
2875+
28722876
ParsingDeclarator DeclaratorInfo(*this, DS, DeclAttrs,
28732877
DeclaratorContext::Member);
28742878
if (TemplateInfo.TemplateParams)

clang/lib/Parse/ParseTemplate.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,16 @@ Decl *Parser::ParseSingleDeclarationAfterTemplate(
248248
: MultiTemplateParamsArg(),
249249
TemplateInfo.Kind == ParsedTemplateInfo::ExplicitInstantiation,
250250
AnonRecord);
251+
Actions.ActOnDefinedDeclarationSpecifier(Decl);
251252
assert(!AnonRecord &&
252253
"Anonymous unions/structs should not be valid with template");
253254
DS.complete(Decl);
254255
return Decl;
255256
}
256257

258+
if (DS.hasTagDefinition())
259+
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());
260+
257261
// Move the attributes from the prefix into the DS.
258262
if (TemplateInfo.Kind == ParsedTemplateInfo::ExplicitInstantiation)
259263
ProhibitAttributes(prefixAttrs);

clang/lib/Parse/Parser.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,13 +1157,17 @@ Parser::DeclGroupPtrTy Parser::ParseDeclOrFunctionDefInternal(
11571157
Decl *TheDecl = Actions.ParsedFreeStandingDeclSpec(
11581158
getCurScope(), AS_none, DS, ParsedAttributesView::none(), AnonRecord);
11591159
DS.complete(TheDecl);
1160+
Actions.ActOnDefinedDeclarationSpecifier(TheDecl);
11601161
if (AnonRecord) {
11611162
Decl* decls[] = {AnonRecord, TheDecl};
11621163
return Actions.BuildDeclaratorGroup(decls);
11631164
}
11641165
return Actions.ConvertDeclToDeclGroup(TheDecl);
11651166
}
11661167

1168+
if (DS.hasTagDefinition())
1169+
Actions.ActOnDefinedDeclarationSpecifier(DS.getRepAsDecl());
1170+
11671171
// ObjC2 allows prefix attributes on class interfaces and protocols.
11681172
// FIXME: This still needs better diagnostics. We should only accept
11691173
// attributes here, no types, etc.

0 commit comments

Comments
 (0)