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

Merged
merged 5 commits into from
Jun 9, 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
74 changes: 74 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6501,3 +6501,77 @@ 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
=======================================

C23 introduced the notion of `underspecified object declarations <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3006.htm>`_
(note, the final standards text is different from WG14 N3006 due to changes
during national body comment review). When an object is declared with the
``constexpr`` storage class specifier or has a deduced type (with the ``auto``
specifier), it is said to be "underspecified". Underspecified declarations have
different requirements than non-underspecified declarations. In particular, the
identifier being declared cannot be used in its initialization. e.g.,

.. code-block:: c

auto x = x; // Invalid
constexpr int y = y; // Invalid

The standard leaves it implementation-defined whether an underspecified
declaration may introduce additional identifiers as part of the declaration.

Clang allows additional identifiers to be declared in the following cases:

* A compound literal may introduce a new type. e.g.,

.. code-block:: c

auto x = (struct S { int x, y; }){ 1, 2 }; // Accepted by Clang
constexpr int i = (struct T { int x; }){ 1 }.x; // Accepted by Clang

* The type specifier for a ``constexpr`` declaration may define a new type.
e.g.,

.. code-block:: c

constexpr struct S { int x; } s = { 1 }; // Accepted by Clang

* A function declarator may be declared with parameters, including parameters
which introduce a new type. e.g.,

.. code-block:: c

constexpr int (*fp)(int x) = nullptr; // Accepted by Clang
auto f = (void (*)(struct S { int x; } s))nullptr; // Accepted by Clang

* The initializer may contain a GNU statement expression which defines new
types or objects. e.g.,

.. code-block:: c

constexpr int i = ({ // Accepted by Clang
constexpr int x = 12;
constexpr struct S { int x; } s = { x };
s.x;
});
auto x = ({ struct S { int x; } s = { 0 }; s; }); // Accepted by Clang

Clang intentionally does not implement the changed scoping rules from C23
for underspecified declarations. Doing so would significantly complicate the
implementation in order to get reasonable diagnostic behavior and also means
Clang fails to reject some code that should be rejected. e.g.,

.. code-block:: c

// This should be rejected because 'x' is not in scope within the initializer
// of an underspecified declaration. Clang accepts because it treats the scope
// of the identifier as beginning immediately after the declarator, same as with
// a non-underspecified declaration.
constexpr int x = sizeof(x);

// Clang rejects this code with a diagnostic about using the variable within its
// own initializer rather than rejecting the code with an undeclared identifier
// diagnostic.
auto x = x;
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ C23 Feature Support
directive. Fixes #GH126940.
- Fixed a crash when a declaration of a ``constexpr`` variable with an invalid
type. Fixes #GH140887
- 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
^^^^^^^^^^^^^^^^^^^
Expand Down
113 changes: 113 additions & 0 deletions clang/test/C/C23/n3006.c
Original file line number Diff line number Diff line change
@@ -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 {})){};
}

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;
});
}
2 changes: 1 addition & 1 deletion clang/www/c_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading