Skip to content

[C2y] Implement WG14 N3409 #130299

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

Merged
merged 5 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/IncludeFixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel,
case diag::err_array_incomplete_or_sizeless_type:
case diag::err_array_size_incomplete_type:
case diag::err_asm_incomplete_type:
case diag::err_assoc_type_incomplete:
case diag::err_bad_cast_incomplete:
case diag::err_call_function_incomplete_return:
case diag::err_call_incomplete_argument:
Expand Down
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ C Language Changes

C2y Feature Support
^^^^^^^^^^^^^^^^^^^
- Implement `WG14 N3409 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3409.pdf>`_
which removes UB around use of ``void`` expressions. In practice, this means
that ``_Generic`` selection associations may now have ``void`` type, but it
also removes UB with code like ``(void)(void)1;``.
- Implemented `WG14 N3411 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3411.pdf>`_
which allows a source file to not end with a newline character. This is still
reported as a conforming extension in earlier language modes.
Expand Down
9 changes: 7 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10425,8 +10425,13 @@ def warn_type_safety_null_pointer_required : Warning<
"specified %0 type tag requires a null pointer">, InGroup<TypeSafety>;

// Generic selections.
def err_assoc_type_incomplete : Error<
"type %0 in generic association incomplete">;
def ext_assoc_type_incomplete : Extension<
"incomplete type %0 in a '_Generic' association is a C2y extension">,
InGroup<C2y>;
def warn_c2y_compat_assoc_type_incomplete : Warning<
"use of incomplete type %0 in a '_Generic' association is incompatible with "
"C standards before C2y">,
InGroup<CPre2yCompat>, DefaultIgnore;
def err_assoc_type_nonobject : Error<
"type %0 in generic association not an object type">;
def err_assoc_type_variably_modified : Error<
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1748,9 +1748,14 @@ ExprResult Sema::CreateGenericSelectionExpr(
//
// C11 6.5.1.1p2 "The type name in a generic association shall specify a
// complete object type other than a variably modified type."
// C2y removed the requirement that an expression form must
// use a complete type, though it's still as-if the type has undergone
// lvalue conversion. We support this as an extension in C23 and
// earlier because GCC does so.
unsigned D = 0;
if (ControllingExpr && Types[i]->getType()->isIncompleteType())
D = diag::err_assoc_type_incomplete;
D = LangOpts.C2y ? diag::warn_c2y_compat_assoc_type_incomplete
: diag::ext_assoc_type_incomplete;
else if (ControllingExpr && !Types[i]->getType()->isObjectType())
D = diag::err_assoc_type_nonobject;
else if (Types[i]->getType()->isVariablyModifiedType())
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4052,9 +4052,9 @@ StmtResult Sema::BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp,
Diag(ReturnLoc, D) << CurDecl << isa<CXXDestructorDecl>(CurDecl)
<< RetValExp->getSourceRange();
}
// return (some void expression); is legal in C++.
// return (some void expression); is legal in C++ and C2y.
else if (D != diag::ext_return_has_void_expr ||
!getLangOpts().CPlusPlus) {
(!getLangOpts().CPlusPlus && !getLangOpts().C2y)) {
NamedDecl *CurDecl = getCurFunctionOrMethodDecl();

int FunctionKind = 0;
Expand Down
36 changes: 36 additions & 0 deletions clang/test/C/C2y/n3409.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %clang_cc1 -verify -std=c2y -pedantic %s
// RUN: %clang_cc1 -verify=pre-c2y -std=c2y -Wpre-c2y-compat %s
// RUN: %clang_cc1 -verify=ext -std=c23 -pedantic %s
// expected-no-diagnostics

/* WG14 N3409: Clang 21
* Slay Some Earthly Demons X
*
* Removes the requirement that an expression with type void cannot be used in
* any way. This was making it UB to use a void expression in a _Generic
* selection expression for no good reason, as well as making it UB to cast a
* void expression to void, etc.
*/

extern void x;
void foo() {
// FIXME: this is technically an extension before C2y and should be diagnosed
// under -pedantic.
(void)(void)1;
// FIXME: same with this.
x;
_Generic(x, void: 1); /* pre-c2y-warning {{use of incomplete type 'void' in a '_Generic' association is incompatible with C standards before C2y}}
ext-warning {{incomplete type 'void' in a '_Generic' association is a C2y extension}}
*/
_Generic(x, typeof(x): 1); /* pre-c2y-warning {{use of incomplete type 'typeof (x)' (aka 'void') in a '_Generic' association is incompatible with C standards before C2y}}
ext-warning {{incomplete type 'typeof (x)' (aka 'void') in a '_Generic' association is a C2y extension}}
*/
(void)_Generic(void, default : 1); /* pre-c2y-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}}
ext-warning {{passing a type argument as the first operand to '_Generic' is a C2y extension}}
*/

// This is not sufficiently important of an extension to warrant a "not
// compatible with standards before C2y" warning, but it is an extension in
// C23 and earlier.
return x; // ext-warning {{void function 'foo' should not return void expression}}
}
2 changes: 1 addition & 1 deletion clang/test/Sema/generic-selection-type-extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static_assert(_Generic(ci, int : 1, const int : 0) == 1); // expected-warning {{
// but the expression operand form still rejects them.
static_assert(_Generic(struct incomplete, struct incomplete : 1, default : 0) == 1);
static_assert(_Generic(struct another_incomplete, struct incomplete : 1, default : 0) == 0);
static_assert(_Generic(1, struct also_incomplete : 1, default : 0) == 0); // expected-error {{type 'struct also_incomplete' in generic association incomplete}}
static_assert(_Generic(1, struct also_incomplete : 1, default : 0) == 0);

void foo(int);
static_assert(_Generic(__typeof__(foo), void(int) : 1, default : 0) == 1);
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/generic-selection.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ void g(void);

void foo(int n) {
(void) _Generic(0, // ext-warning {{'_Generic' is a C11 extension}}
struct A: 0, // expected-error {{type 'struct A' in generic association incomplete}}
struct A: 0, // ext-warning {{incomplete type 'struct A' in a '_Generic' association is a C2y extension}}
void(): 0, // expected-error {{type 'void ()' in generic association not an object type}}
int[n]: 0); // expected-error {{type 'int[n]' in generic association is a variably modified type}}

Expand Down
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/generic-selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ void func(struct S s) {
// is an elaborated type specifier followed by the association's value and
// it should work the same as in C.
(void)_Generic(s, struct S : 1);
(void)_Generic(s, struct T : 1);

// The rest of these cases test that we still produce a reasonable diagnostic
// when referencing an unknown type or trying to define a type in other ways.
(void)_Generic(s, struct T : 1); // expected-error {{type 'struct T' in generic association incomplete}}
(void)_Generic(s, struct U { int a; } : 1); // expected-error {{'U' cannot be defined in a type specifier}}
(void)_Generic(s, struct V : S); // expected-error {{'S' does not refer to a value}}
(void)_Generic(s, struct W : S { int b; } : 1); // expected-error {{expected '(' for function-style cast or type construction}}
Expand Down