-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Clang] Implement CWG2351 void{}
#78060
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang Author: Mital Ashok (MitalAshok) ChangesAs per CWG2351, allow Note that the AST for the expression
(Since the As for For reference, C++98 [5.2.3p2] says: > The expression Though it is a bit of a misnomer that, for Full diff: https://github.com/llvm/llvm-project/pull/78060.diff 5 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3cbce1be159437..00e200ea76e5bc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -226,6 +226,8 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+- Implemented `CWG2351 <https://wg21.link/CWG2351>`_ which allows ``void{}``
+ as a prvalue of type ``void``.
C Language Changes
------------------
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 24278016431837..e7b732e4181127 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -2160,8 +2160,9 @@ class LambdaExpr final : public Expr,
const_child_range children() const;
};
-/// An expression "T()" which creates a value-initialized rvalue of type
-/// T, which is a non-class type. See (C++98 [5.2.3p2]).
+/// An expression "T()" which creates an rvalue of type T, which is a
+/// non-class type. For non-void T, the rvalue is value-initialized.
+/// See (C++98 [5.2.3p2]).
class CXXScalarValueInitExpr : public Expr {
friend class ASTStmtReader;
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 4ae04358d5df7c..1792ec6bb292d5 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -1600,12 +1600,21 @@ Sema::BuildCXXTypeConstructExpr(TypeSourceInfo *TInfo,
return ExprError(Diag(TyBeginLoc, diag::err_init_for_function_type)
<< Ty << FullRange);
- // C++17 [expr.type.conv]p2:
- // If the type is cv void and the initializer is (), the expression is a
- // prvalue of the specified type that performs no initialization.
- if (!Ty->isVoidType() &&
- RequireCompleteType(TyBeginLoc, ElemTy,
- diag::err_invalid_incomplete_type_use, FullRange))
+ // C++17 [expr.type.conv]p2, per DR2351:
+ // If the type is cv void and the initializer is () or {}, the expression is
+ // a prvalue of the specified type that performs no initialization.
+ if (Ty->isVoidType()) {
+ if (Exprs.empty())
+ return new (Context) CXXScalarValueInitExpr(Context.VoidTy, TInfo,
+ Kind.getRange().getEnd());
+ if (ListInitialization && cast<InitListExpr>(Exprs[0])->getNumInits() == 0)
+ return CXXFunctionalCastExpr::Create(
+ Context, Context.VoidTy, VK_PRValue, TInfo, CK_NoOp, Exprs[0],
+ /*Path=*/nullptr, CurFPFeatureOverrides(), Exprs[0]->getBeginLoc(),
+ Exprs[0]->getEndLoc());
+ } else if (RequireCompleteType(TyBeginLoc, ElemTy,
+ diag::err_invalid_incomplete_type_use,
+ FullRange))
return ExprError();
// Otherwise, the expression is a prvalue of the specified type whose
diff --git a/clang/test/CXX/drs/dr23xx.cpp b/clang/test/CXX/drs/dr23xx.cpp
index d2f4e7652ab568..1bf30a80f743e5 100644
--- a/clang/test/CXX/drs/dr23xx.cpp
+++ b/clang/test/CXX/drs/dr23xx.cpp
@@ -69,6 +69,37 @@ namespace dr2346 { // dr2346: 11
}
}
+namespace dr2351 { // dr2351: 18
+#if __cplusplus >= 201103L
+ static_assert((void{}, true), "");
+ // since-cxx11-warning@-1 {{left operand of comma operator has no effect}}
+
+ void f() {
+ return void{};
+ }
+
+ template<typename T>
+ void g() {
+ return T{};
+ }
+ template void g<void>();
+
+ void h() {
+ return {};
+ // since-cxx11-error@-1 {{void function 'h' must not return a value}}
+ }
+
+ template<typename T, int... I>
+ T i() {
+ return T{I...};
+ }
+ template void i<void>();
+
+ static_assert((void({}), true), "");
+ // since-cxx11-error@-1 {{cannot initialize non-class type 'void' with a parenthesized initializer list}}
+#endif
+}
+
namespace dr2352 { // dr2352: 10
int **p;
const int *const *const &f1() { return p; }
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 4a3ed19161f9a5..207f032a5d45e4 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -13914,7 +13914,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2351.html">2351</a></td>
<td>CD5</td>
<td><TT>void{}</TT></td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2352">
<td><a href="https://cplusplus.github.io/CWG/issues/2352.html">2352</a></td>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DR testing part looks fine.
I'm worried there are no regular tests. It's also not clear what happens in 98 mode. New code doesn't seem to care about language mode.
Initializer list syntax isn't available in C++98 mode (even as an extension? I can't find the option) |
I'm not confident enough to properly review your changes, but my line of thinking is the following:
Formally, defect reports apply to then latest publication, which is C++14, if not 17. Implementation indeed try to backport them as far back as they reasonably can. Start accepting uniform initialization syntax in 98 mode because of |
Looking at other places, it looks like init-list stuff is guarded behind It looks like this DR is CD5 (after C++17, applies to C++17), but As for "regular tests", do you mean in I also found this: https://stackoverflow.com/q/41131806 that |
Corentin told me offline that check for list initialization that you do might be sufficient, as it can't pass in 98.
Yes, 11 and 14 are totally fine. We may want a pedantic warning, though.
No, this is C++ conformance test suite. I meant |
I would recommend adding a test in C++98 mode. I don't think gating is necessary, the code should not parse. Adding a test for ({}) is also a good idea. |
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a quick question
// C++17 [expr.type.conv]p2, per DR2351: | ||
// If the type is cv void and the initializer is () or {}, the expression is | ||
// a prvalue of the specified type that performs no initialization. | ||
if (Ty->isVoidType()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to understand why the check is here, and I think it is b/c the diagnostic is coming from one of these two lines:
InitializationSequence InitSeq(*this, Entity, Kind, Exprs);
ExprResult Result = InitSeq.Perform(*this, Entity, Kind, Exprs);
is that correct? So then we need to handle this case before we attempt the initialization sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[expr.type.conv]p2 reads "T(expression-list)
or T braced-init-list
is a cast expression, a prvalue void
, or direct-initialized from the initializer". So when we encounter void()
or void{}
, we shouldn't actually use initialization semantics.
Previously, void()
worked because
llvm-project/clang/lib/Sema/SemaInit.cpp
Lines 6255 to 6260 in 2d09ac4
// - If the initializer is (), the object is value-initialized. | |
if (Kind.getKind() == InitializationKind::IK_Value || | |
(Kind.getKind() == InitializationKind::IK_Direct && Args.empty())) { | |
TryValueInitialization(S, Entity, Kind, *this); | |
return; | |
} |
I've now added an assertion for void
in TryValueInitialization
because you shouldn't actually be able to get there, since there's no standard way to call for the value-intialization of void
@MitalAshok ping |
@MitalAshok ping |
Nonetheless, I think it'd be more reasonable to use |
void{}
void{}
@zygoloid The commit message was a bit outdated, it now takes the void type from I've also changed it to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LG
Per CWG2351, allow
void{}
, treated the same asvoid()
: a prvalue expression of typevoid
that performs no initialization.Note that the AST for the expression
T{}
looks like:As for
void()
/T() [T = const void]
, that looked likeCXXScalarValueInitExpr 'void'
and is unchanged after this.For reference, C++98 [5.2.3p2] says:
Though it is a bit of a misnomer that, for
T = void
,CXXScalarValueInitExpr
does not perform value initialization, it would be a breaking change to change the AST node forvoid()
, so I simply reworded the doc comment.