[clang] [clang-tools-extra] [C2y] Implement WG14 N3409 (PR #130299)

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 7 10:21:35 PST 2025


https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/130299

>From 8ee44f1f2beb659a37c693b4f323491f6bfd8caa Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Fri, 7 Mar 2025 11:04:31 -0500
Subject: [PATCH 1/2] [C2y] Implement WG14 N3409

This paper removes UB around use of void expressions. Previously, code
like this had undefined behavior:

  void foo(void) {
    (void)(void)1;
    extern void x;
    x;
  }

and this is now well-defined in C2y. Functionally, this now means that
it is valid to use `void` as a `_Generic` association.
---
 clang/docs/ReleaseNotes.rst                   |  4 +++
 .../clang/Basic/DiagnosticSemaKinds.td        |  9 ++++--
 clang/lib/Sema/SemaExpr.cpp                   |  7 +++-
 clang/test/C/C2y/n3409.c                      | 32 +++++++++++++++++++
 4 files changed, 49 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/C/C2y/n3409.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 577b3f2130df7..df8d2eed1ec0c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -114,6 +114,10 @@ C2y Feature Support
 - Implemented N3411 which allows a source file to not end with a newline
   character. This is still reported as a conforming extension in earlier
   language modes.
+- 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;``.
 
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1b46920e09619..d6e5005003322 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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<
+  "ISO C requires a complete type in a '_Generic' association; %0 is an "
+  "incomplete type">;
+def warn_c2y_compat_assoc_type_incomplete : Warning<
+  "use of an incomplete type in a '_Generic' association is incompatible with "
+  "C standards before C2y; %0 is an incomplete type">,
+  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<
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index f896ccab53a54..de7be6b2805af 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -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())
diff --git a/clang/test/C/C2y/n3409.c b/clang/test/C/C2y/n3409.c
new file mode 100644
index 0000000000000..2fc789891c71d
--- /dev/null
+++ b/clang/test/C/C2y/n3409.c
@@ -0,0 +1,32 @@
+// 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 an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'void' is an incomplete type}}
+                                ext-warning {{ISO C requires a complete type in a '_Generic' association; 'void' is an incomplete type}}
+                              */
+  _Generic(x, typeof(x): 1); /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'typeof (x)' (aka 'void') is an incomplete type}}
+                                ext-warning {{ISO C requires a complete type in a '_Generic' association; 'typeof (x)' (aka 'void') is an incomplete type}}
+                              */
+  (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}}
+                                      */
+}
+

>From c6a0b3f4c486a4210c1d20daa015809adf70995b Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Fri, 7 Mar 2025 13:08:20 -0500
Subject: [PATCH 2/2] Update based on review feedback; fix tests

---
 clang-tools-extra/clangd/IncludeFixer.cpp       |  1 -
 .../include/clang/Basic/DiagnosticSemaKinds.td  |  8 ++++----
 clang/lib/Sema/SemaStmt.cpp                     | 17 +++++++----------
 clang/test/C/C2y/n3409.c                        | 14 +++++++++-----
 .../Sema/generic-selection-type-extension.c     |  2 +-
 clang/test/Sema/generic-selection.c             |  2 +-
 clang/test/SemaCXX/generic-selection.cpp        |  2 +-
 7 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp
index fadd1105691fc..8b74c761d23ca 100644
--- a/clang-tools-extra/clangd/IncludeFixer.cpp
+++ b/clang-tools-extra/clangd/IncludeFixer.cpp
@@ -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:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d6e5005003322..21be7c358a61d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10426,11 +10426,11 @@ def warn_type_safety_null_pointer_required : Warning<
 
 // Generic selections.
 def ext_assoc_type_incomplete : Extension<
-  "ISO C requires a complete type in a '_Generic' association; %0 is an "
-  "incomplete type">;
+  "incomplete type %0 in a '_Generic' association is a C2y extension">,
+  InGroup<C2y>;
 def warn_c2y_compat_assoc_type_incomplete : Warning<
-  "use of an incomplete type in a '_Generic' association is incompatible with "
-  "C standards before C2y; %0 is an incomplete type">,
+  "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">;
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 0a193b5299bcc..01d1b3da9ff22 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2269,11 +2269,10 @@ StmtResult Sema::ActOnForStmt(SourceLocation ForLoc, SourceLocation LParenLoc,
       for (auto *DI : DS->decls()) {
         if (VarDecl *VD = dyn_cast<VarDecl>(DI)) {
           VarDeclSeen = true;
-          if (VD->isLocalVarDecl() && !VD->hasLocalStorage())
-            Diag(DI->getLocation(),
-                 getLangOpts().C23
-                     ? diag::warn_c17_non_local_variable_decl_in_for
-                     : diag::ext_c23_non_local_variable_decl_in_for);
+          if (VD->isLocalVarDecl() && !VD->hasLocalStorage()) {
+            Diag(DI->getLocation(), diag::err_non_local_variable_decl_in_for);
+            DI->setInvalidDecl();
+          }
         } else if (!NonVarSeen) {
           // Keep track of the first non-variable declaration we saw so that
           // we can diagnose if we don't see any variable declarations. This
@@ -2285,9 +2284,7 @@ StmtResult Sema::ActOnForStmt(SourceLocation ForLoc, SourceLocation LParenLoc,
       // Diagnose if we saw a non-variable declaration but no variable
       // declarations.
       if (NonVarSeen && !VarDeclSeen)
-        Diag(NonVarSeen->getLocation(),
-             getLangOpts().C23 ? diag::warn_c17_non_variable_decl_in_for
-                               : diag::ext_c23_non_variable_decl_in_for);
+        Diag(NonVarSeen->getLocation(), diag::err_non_variable_decl_in_for);
     }
   }
 
@@ -4052,9 +4049,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;
diff --git a/clang/test/C/C2y/n3409.c b/clang/test/C/C2y/n3409.c
index 2fc789891c71d..01be716132b11 100644
--- a/clang/test/C/C2y/n3409.c
+++ b/clang/test/C/C2y/n3409.c
@@ -19,14 +19,18 @@ void foo() {
   (void)(void)1;
   // FIXME: same with this.
   x;
-  _Generic(x, void: 1);      /* pre-c2y-warning {{use of an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'void' is an incomplete type}}
-                                ext-warning {{ISO C requires a complete type in a '_Generic' association; 'void' is an incomplete type}}
+  _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 an incomplete type in a '_Generic' association is incompatible with C standards before C2y; 'typeof (x)' (aka 'void') is an incomplete type}}
-                                ext-warning {{ISO C requires a complete type in a '_Generic' association; 'typeof (x)' (aka 'void') is an incomplete type}}
+  _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}}
+}
diff --git a/clang/test/Sema/generic-selection-type-extension.c b/clang/test/Sema/generic-selection-type-extension.c
index 89ac3235807da..1efd045acba2d 100644
--- a/clang/test/Sema/generic-selection-type-extension.c
+++ b/clang/test/Sema/generic-selection-type-extension.c
@@ -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);
diff --git a/clang/test/Sema/generic-selection.c b/clang/test/Sema/generic-selection.c
index 1f17896ca4cda..8e7b6ed00dd50 100644
--- a/clang/test/Sema/generic-selection.c
+++ b/clang/test/Sema/generic-selection.c
@@ -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}}
 
diff --git a/clang/test/SemaCXX/generic-selection.cpp b/clang/test/SemaCXX/generic-selection.cpp
index 9e47dde3ea444..aa4a4c435adec 100644
--- a/clang/test/SemaCXX/generic-selection.cpp
+++ b/clang/test/SemaCXX/generic-selection.cpp
@@ -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}}



More information about the cfe-commits mailing list