-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang Author: Guillot Tony (to268) ChangesThis 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:
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>
|
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.
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?
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!
This PR is documenting the behavior of Clang towards underspecified object declarations in C23 as advised by @AaronBallman.