Skip to content

[C23][N3006] Documented behavior of underspecified object declarations #140911

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

to268
Copy link
Contributor

@to268 to268 commented May 21, 2025

This PR is documenting the behavior of Clang towards underspecified object declarations in C23 as advised by @AaronBallman.

@llvmbot llvmbot added the clang Clang issues not falling into any other category label May 21, 2025
@llvmbot
Copy link
Member

llvmbot commented May 21, 2025

@llvm/pr-subscribers-clang

Author: Guillot Tony (to268)

Changes

This PR is documenting the behavior of Clang towards underspecified object declarations in C23 as advised by @AaronBallman.


Full diff: https://github.com/llvm/llvm-project/pull/140911.diff

4 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+48)
  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (added) clang/test/C/C23/n3006.c (+113)
  • (modified) clang/www/c_status.html (+1-1)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index a40dd4d1a1673..aa76daf0ea459 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -6454,3 +6454,51 @@ qualifications.
 Note, Clang does not allow an ``_Atomic`` function type because
 of explicit constraints against atomically qualified (arrays and) function
 types.
+
+
+Underspecified object declarations in C
+=======================================
+
+In C23 (N3006), when an object is declared inside of another object and that
+only exists in the scope of the declaration of the outer object. Clang allows
+underspecified object declarations for structs, unions and enums when valid.
+
+.. code-block:: c
+
+  auto s1 = (struct S1 { int x, y; }){ 1, 2 };
+  auto u1 = (union U1 { int a; double b; }){ .a = 34 };
+  auto e1 = (enum E1 { FOO, BAR }){ BAR };
+
+Note, The ``constexpr`` keyword and getting a struct member that is
+underspecified is also allowed.
+
+.. code-block:: c
+
+  constexpr auto cs1 = (struct S1 { int x, y; }){ 1, 2 };
+  constexpr auto cu1 = (union U1 { int a; double b; }){ .a = 34 };
+  constexpr auto ce1 = (enum E1 { FOO, BAR }){ BAR };
+  int i1 = (struct T { int a, b; }){0, 1}.a;
+  constexpr int ci2 = (struct T2 { int a, b; }){0, 1}.a;
+
+Moreover, some unusual cases are also allowed as described bellow.
+
+.. code-block:: c
+
+  constexpr struct S { int a, b; } y = { 0 };
+  constexpr typeof(struct s *) x = 0;
+  auto so = sizeof(struct S {});
+  auto s = ({struct T { int x; } s = {}; s.x; });
+  constexpr int (*fp)(struct X { int x; } val) = 0;
+  auto v = (void (*)(int y))0;
+
+  constexpr struct {
+      int a;
+  } si = {};
+
+  auto z = ({
+      int a = 12;
+      struct {} s;
+      a;
+  });
+
+All other cases are prohibited by Clang.
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 537f29521fb7f..c8de10f951933 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -273,6 +273,8 @@ C23 Feature Support
   be completed).
 - Fixed a failed assertion with an invalid parameter to the ``#embed``
   directive. Fixes #GH126940.
+- Documented `WG14 N3006 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3006.htm>`_
+  which clarified how Clang is handling underspecified object declarations.
 
 C11 Feature Support
 ^^^^^^^^^^^^^^^^^^^
diff --git a/clang/test/C/C23/n3006.c b/clang/test/C/C23/n3006.c
new file mode 100644
index 0000000000000..033e0b407e7f5
--- /dev/null
+++ b/clang/test/C/C23/n3006.c
@@ -0,0 +1,113 @@
+// RUN: %clang_cc1 -std=c23 -verify %s
+
+/* WG14 N3006: Yes
+ * Underspecified object declarations
+ */
+
+void struct_test(void) {
+  struct S1 { int x, y; };                                                  // expected-note {{field 'x' has type 'int' here}}
+
+  auto normal_struct = (struct S1){ 1, 2 };
+  auto normal_struct2 = (struct S1) { .x = 1, .y = 2 };
+  auto underspecified_struct = (struct S2 { int x, y; }){ 1, 2 };
+  auto underspecified_struct_redef = (struct S1 { char x, y; }){ 'A', 'B'}; // expected-error {{type 'struct S1' has incompatible definitions}} \
+                                                                               expected-error {{cannot use 'auto' with array in C}} \
+                                                                               expected-note {{field 'x' has type 'char' here}}
+  auto underspecified_empty_struct = (struct S3 { }){ };
+  auto zero_init_struct = (struct S4 { int x; }){ 0 };
+  int field_struct = (struct S5 { int y; }){ 0 }.y;
+}
+
+void union_test(void) {
+  union U1 { int a; double b; };                                                  // expected-note {{field 'a' has type 'int' here}}
+
+  auto normal_union_int = (union U1){ .a = 12 };
+  auto normal_union_double = (union U1){ .b = 2.4 };
+  auto underspecified_union = (union U2 { int a; double b; }){ .a = 34 };
+  auto underspecified_union_redef = (union U1 { char a; double b; }){ .a = 'A' }; // expected-error {{type 'union U1' has incompatible definitions}} \
+                                                                                     expected-error {{cannot use 'auto' with array in C}} \
+                                                                                     expected-note {{field 'a' has type 'char' here}}
+  auto underspecified_empty_union = (union U3 {  }){  };
+}
+
+void enum_test(void) {
+  enum E1 { FOO, BAR };                                           // expected-note {{enumerator 'BAR' with value 1 here}}
+
+  auto normal_enum_foo = (enum E1){ FOO };
+  auto normal_enum_bar = (enum E1){ BAR };
+  auto underspecified_enum = (enum E2 { BAZ, QUX }){ BAZ };
+  auto underspecified_enum_redef = (enum E1 { ONE, TWO }){ ONE }; // expected-error {{type 'enum E1' has incompatible definitions}} \
+                                                                     expected-error {{cannot use 'auto' with array in C}} \
+                                                                     expected-note {{enumerator 'ONE' with value 0 here}}
+  auto underspecified_empty_enum = (enum E3 {  }){ };             // expected-error {{use of empty enum}}
+  auto underspecified_undeclared_enum = (enum E4){ FOO };         // expected-error {{variable has incomplete type 'enum E4'}} \
+                                                                     expected-note {{forward declaration of 'enum E4'}}
+}
+
+void constexpr_test(void) {
+  constexpr auto ce_struct = (struct S1){ 1, 2 };                   // expected-error {{variable has incomplete type 'struct S1'}} \
+                                                                       expected-note {{forward declaration of 'struct S1'}}
+  constexpr auto ce_struct_zero_init = (struct S2 { int x; }){ 0 };
+  constexpr int ce_struct_field = (struct S3 { int y; }){ 0 }.y;
+  constexpr auto ce_union = (union U1){ .a = 12 };                  // expected-error {{variable has incomplete type 'union U1'}} \
+                                                                       expected-note {{forward declaration of 'union U1'}}
+
+  constexpr auto ce_enum = (enum E1 { BAZ, QUX }){ BAZ };
+  constexpr auto ce_empty_enum = (enum E2){ FOO };                  // expected-error {{use of undeclared identifier 'FOO'}}
+}
+
+void self_reference_test(void) {
+  constexpr int i = i;  // expected-error {{constexpr variable 'i' must be initialized by a constant expression}} \
+                           expected-note {{read of object outside its lifetime is not allowed in a constant expression}}
+  auto j = j;           // expected-error {{variable 'j' declared with deduced type 'auto' cannot appear in its own initializer}}
+}
+
+void redefinition_test(void) {
+  const struct S { int x; } s;  // expected-warning {{default initialization of an object of type 'const struct S' leaves the object uninitialized}} \
+                                   expected-note {{previous definition is here}}
+  constexpr struct S s = {0};   // expected-error {{redefinition of 's'}}
+}
+
+void declaring_an_underspecified_defied_object_test(void) {
+  struct S { int x, y; };
+  constexpr int i = (struct T { int a, b; }){0, 1}.a;
+
+  struct T t = { 1, 2 };
+}
+
+void constexpr_complience_test(void) {
+  int x = (struct Foo { int x; }){ 0 }.x;
+  constexpr int y = (struct Bar { int x; }){ 0 }.x;
+}
+
+void builtin_functions_test(void) {
+  constexpr typeof(struct s *) x = 0;
+  auto so = sizeof(struct S {});
+  auto to = typeof(struct S {});        // expected-error {{expected expression}}
+}
+
+void misc_test(void) {
+  constexpr struct S { int a, b; } y = { 0 };
+  constexpr int a = 0, b = 0;
+  auto c = (struct T { int x, y; }){0, 0};
+  auto s2 = ({struct T { int x; } s = {}; s.x; });
+  auto s3 = ((struct {}){},0); // expected-warning {{left operand of comma operator has no effect}}
+  constexpr int (*fp)(struct X { int x; } val) = 0;
+  auto v = (void (*)(int y))0;
+}
+
+void misc_struct_test(void) {
+  constexpr struct {
+      int a;
+  } a = {};
+
+  constexpr struct {
+      int b;
+  } b = (struct S { int x; }){ 0 };  // expected-error-re {{initializing 'const struct (unnamed struct at {{.*}}n3006.c:104:13)' with an expression of incompatible type 'struct S'}}
+
+  auto z = ({
+      int a = 12;
+      struct {} s;
+      a;
+  });
+}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index e47466e3273f2..dcff2fc2b1a3e 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -864,7 +864,7 @@ <h2 id="c2x">C23 implementation status</h2>
     <tr>
       <td>Underspecified object definitions</td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3006.htm">N3006</a></td>
-      <td class="none" align="center">No</td>
+      <td class="unreleased" align="center">Yes</td>
     </tr>
     <tr>
       <td>Type inference for object declarations</td>

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think the only other thing we should consider documenting is that we intentionally do not implement the changed scoping rules from C23. Doing so would significantly complicate the implementation in order to get the same QoI, but not doing so means we fail to reject some code that should be rejected. e.g.,

auto x = x;

With the new scoping rules, this would result in "use of undefined identifier 'x'" for the initializer which is not nearly as helpful as "variable 'x' declared with deduced type 'auto' cannot appear in its own initializer" which we emit today.

Conversely:

constexpr int x = sizeof(x);

should be rejected according to the standard, but we accept it because... why should that be rejected?

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c23 clang Clang issues not falling into any other category documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants