Skip to content
Closed
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
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ C2y Feature Support
C23 Feature Support
^^^^^^^^^^^^^^^^^^^

- Clang now supports `N3006 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3006.htm>`_ Underspecified object declarations.

Non-comprehensive list of changes in this release
-------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7937,6 +7937,8 @@ def err_attribute_arm_mve_polymorphism : Error<
"'__clang_arm_mve_strict_polymorphism' attribute can only be applied to an MVE/NEON vector type">;
def err_attribute_webassembly_funcref : Error<
"'__funcref' attribute can only be applied to a function pointer type">;
def err_c23_underspecified_object_declaration: Error<
"'%select{struct|<ERROR>|union|<ERROR>|enum}0 %1' is defined as an underspecified object initializer">;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that the diagnostic message need to be corrected.

Copy link
Contributor

@cor3ntin cor3ntin Jan 29, 2024

Choose a reason for hiding this comment

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

I don't think underspecified object is useful for users
Maybe error: the deduced type of foo refers to a struct|union|enum bar which is defined in its initializer.

Copy link
Contributor

@Fznamznon Fznamznon Jan 29, 2024

Choose a reason for hiding this comment

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

AFAIK "underspecified" is how the standard calls it. And constexpr declarations in C are also "underspecified".
gcc agrees https://godbolt.org/z/1KcW9zTcd .
I agree that underspecified sounds weird for ordinary people though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also didn't wanted to just use the same wording as GCC.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the terminology used in the standard is really hard for mortals to understand, but I also think the definition of "underspecified" makes it hard to capture this in a single diagnostic and still be precise. But I think existing diagnostics cover the other cases, mostly.

C23 6.7.1p12: A declaration such that the declaration specifiers contain no type specifier or that is declared with constexpr is said to be underspecified. If such a declaration is not a definition, if it declares no or more than one ordinary identifier, if the declared identifier already has a declaration in the same scope, if the declared entity is not an object, or if anywhere within the sequence of tokens making up the declaration identifiers that are not ordinary are declared, the behavior is implementation-defined.

So I think the best I can come up with is:

%select{declaration using a deduced type|constexpr declaration}0 %1 also declares %2 which is not an ordinary identifier

I think "if such a declaration is not a definition" is already covered by constexpr variable 'i' must be initialized by a constant expression: https://godbolt.org/z/nvKEzv975
I think "if it declares no or more than one ordinary identifier" is already covered by expected identifier or '(' or is supported by Clang: https://godbolt.org/z/Yas4od5Ev (Note, we should add documentation for the implementation-defined extension accepting multiple declarations in a group.)
I think "if the declared identifier already has a declaration in the same scope" is not correctly supported: https://godbolt.org/z/Kab7GvW85 but this should be handled by an existing redeclaration diagnostic when we fix it.
I think "if the declared entity is not an object" is already covered by "'auto' not allowed in function return type": https://godbolt.org/z/KzeTeczY7
I think "if anywhere within the sequence of tokens making up the declaration identifiers that are not ordinary are declared" is the only thing we're missing a diagnostic for, and the above suggested wording would handle it.

So I think we can get away without inflicting "underspecified" on users.

The other half of this paper has to do with scope of identifiers, we may end up needing to get creative with diagnostic wording when lookup fails to find an identifier from an underspecified declaration.


def warn_setter_getter_impl_required : Warning<
"property %0 requires method %1 to be defined - "
Expand Down
34 changes: 31 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7101,10 +7101,38 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo,
diagID))
return ExprError();
}
} else if (LangOpts.C23 &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think these changes are quite sufficient because I think we need to handle more cases than just compound literals. Consider:

constexpr typeof(struct s *) x = 0; // declares `s` which is not an ordinary identifier
constexpr struct S { int a, b; } y = { 0 }; // declares `S`, `a`, and `b`, none of which are ordinary identifiers
constexpr int a = 0, b = 0; // declares `a` and `b` as ordinary identifiers
auto c = (struct T { int x, y; }){0, 0}; // declares `T`, `x`, and `y`, none of which are ordinary identifiers
constexpr int (*fp)(struct X { int x; } val) = 0; // declares `X` and `x` which are not ordinary identifiers

(https://godbolt.org/z/Pq87aMnch)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

3 out of these 5 cases are not conforming curently:

constexpr typeof(struct s *) x = 0;               // Wrong, no underspecifed diagnostics
constexpr struct S { int a, b; } y = { 0 };       // Wrong, no underspecifed diagnostics
constexpr int a = 0, b = 0;                       // Ok, no underspecifed diagnostics
auto c = (struct T { int x, y; }){0, 0};          // Ok, `struct T` diagnosed as underspecifed
constexpr int (*fp)(struct X { int x; } val) = 0; // Wrong, no underspecifed diagnostics but dignoses warning: `declaration of 'struct X' will not be visible outside of this function`

I need to do more work outside of compound literals.

(literalType->isRecordType() || literalType->isEnumeralType())) {
// C23 6.2.1p7: Structure, union, and enumeration tags have scope that
// begins just after the appearance of the tag in a type specifier that
// declares the tag.
// [...]
// An ordinary identifier that has an underspecified definition has scope
// that starts when the definition is completed; if the same ordinary
// identifier declares another entity with a scope that encloses the current
// block, that declaration is hidden as soon as the inner declarator is
// completed*.)
// [...]
// *) That means, that the outer declaration is not visible for the
// initializer.
auto Range = SourceRange(LParenLoc, RParenLoc);
const auto *Tag = literalType->castAs<TagType>();
const auto &TagRange = Tag->getDecl()->getSourceRange();

// We should diagnose underspecified declaration, unless the identifier has
// been diagnosed as being a redefinition, since the tag is made anonymous.
if (Range.fullyContains(TagRange) && Tag->getDecl()->getIdentifier()) {
Diag(TagRange.getBegin(), diag::err_c23_underspecified_object_declaration)
<< (unsigned)Tag->getDecl()->getTagKind() << Tag->getDecl()->getName()
<< TagRange;
return ExprError();
}
} else if (!literalType->isDependentType() &&
RequireCompleteType(LParenLoc, literalType,
diag::err_typecheck_decl_incomplete_type,
SourceRange(LParenLoc, LiteralExpr->getSourceRange().getEnd())))
RequireCompleteType(
LParenLoc, literalType,
diag::err_typecheck_decl_incomplete_type,
SourceRange(LParenLoc,
LiteralExpr->getSourceRange().getEnd())))
Comment on lines +7131 to +7135
Copy link
Contributor

Choose a reason for hiding this comment

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

White space changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the formatting CI was yelling at me for this.
I'll probably revert this since it's not part of the feature.

return ExprError();

InitializedEntity Entity
Expand Down
66 changes: 66 additions & 0 deletions clang/test/C/C23/n3006.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %clang_cc1 -std=c2x -verify %s

/* WG14 N3006: Full
* Underspecified object declarations
*/

struct S1 { int x, y; }; // expected-note {{previous definition is here}}
union U1 { int a; double b; }; // expected-note {{previous definition is here}}
enum E1 { FOO, BAR }; // expected-note {{previous definition is 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 }; // expected-error {{'struct S2' is defined as an underspecified object initializer}}
auto underspecified_struct_redef = (struct S1 { char x, y; }){ 'A', 'B'}; // expected-error {{redefinition of 'S1'}}
auto underspecified_empty_struct = (struct S3 { }){ }; // expected-error {{'struct S3' is defined as an underspecified object initializer}}

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 }; // expected-error {{'union U2' is defined as an underspecified object initializer}}
auto underspecified_union_redef = (union U1 { char a; double b; }){ .a = 'A' }; // expected-error {{redefinition of 'U1'}}
auto underspecified_empty_union = (union U3 { }){ }; // expected-error {{'union U3' is defined as an underspecified object initializer}}

auto normal_enum_foo = (enum E1){ FOO };
auto normal_enum_bar = (enum E1){ BAR };
auto underspecified_enum = (enum E2 { BAZ, QUX }){ BAZ }; // expected-error {{'enum E2' is defined as an underspecified object initializer}}
auto underspecified_enum_redef = (enum E1 { ONE, TWO }){ ONE }; // expected-error {{redefinition of 'E1'}}
auto underspecified_empty_enum = (enum E3 { }){ }; // expected-error {{'enum E3' is defined as an underspecified object initializer}} \
expected-error {{use of empty enum}}
void constexpr_test() {
constexpr auto ce_struct = (struct S1){ 1, 2 };
constexpr auto ce_union = (union U1){ .a = 12 };
constexpr auto ce_enum = (enum E1){ FOO };
}

void trivial_test() {
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 double_definition_test() {
const struct S { int x; } s; // expected-note {{previous definition is here}}
constexpr struct S s = {0}; // expected-error {{redefinition of 's'}}
}

void declaring_an_underspecified_defied_object_test() {
struct S { int x, y; };
constexpr int i = (struct T { int a, b; }){0, 1}.a; // expected-error {{'struct T' is defined as an underspecified object initializer}} \
FIXME: `constexpr variable 'i' must be initialized by a constant expression` shoud appear

struct T t = { 1, 2 }; // TODO: Should this be diagnosed as an invalid declaration?
}

void constexpr_complience_test() {
int x = (struct Foo { int x; }){ 0 }.x; // expected-error {{'struct Foo' is defined as an underspecified object initializer}}
constexpr int y = (struct Bar { int x; }){ 0 }.x; // expected-error {{'struct Bar' is defined as an underspecified object initializer}}
}

void special_test() {
constexpr typeof(struct s *) x = 0; // FIXME: declares `s` which is not an ordinary identifier
constexpr struct S { int a, b; } y = { 0 }; // FIXME: declares `S`, `a`, and `b`, none of which are ordinary identifiers
constexpr int a = 0, b = 0;
auto c = (struct T { int x, y; }){0, 0}; // expected-error {{'struct T' is defined as an underspecified object initializer}}
constexpr int (*fp)(struct X { int x; } val) = 0; // expected-warning {{declaration of 'struct X' will not be visible outside of this function}} \
FIXME: declares `X` and `x` which are not ordinary identifiers
}
12 changes: 12 additions & 0 deletions clang/test/Parser/c23-underspecified-decls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c23 -std=c23 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c17 -std=c17 %s

auto underspecified_struct = (struct S1 { int x, y; }){ 1, 2 }; // c23-error {{'struct S1' is defined as an underspecified object initializer}} \
c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}} \
c17-error {{illegal storage class on file-scoped variable}}
auto underspecified_union = (union U1 { int a; double b; }){ .a = 34 }; // c23-error {{'union U1' is defined as an underspecified object initializer}} \
c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}} \
c17-error {{illegal storage class on file-scoped variable}}
auto underspecified_enum = (enum E1 { FOO, BAR }){ BAR }; // c23-error {{'enum E1' is defined as an underspecified object initializer}} \
c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}} \
c17-error {{illegal storage class on file-scoped variable}}
2 changes: 1 addition & 1 deletion clang/www/c_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,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">Clang 21</td>
</tr>
<tr>
<td>Type inference for object declarations</td>
Expand Down