-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[clang][C23] N3006 Underspecified object declarations #79845
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7101,10 +7101,38 @@ Sema::BuildCompoundLiteralExpr(SourceLocation LParenLoc, TypeSourceInfo *TInfo, | |
| diagID)) | ||
| return ExprError(); | ||
| } | ||
| } else if (LangOpts.C23 && | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()))) | ||
|
||
| return ExprError(); | ||
|
|
||
| InitializedEntity Entity | ||
|
|
||
| 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 | ||
| } |
| 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}} |
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.
I think that the diagnostic message need to be corrected.
Uh oh!
There was an error while loading. Please reload this page.
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.
I don't think
underspecified objectis useful for usersMaybe
error: the deduced type of foo refers to a struct|union|enum bar which is defined in its initializer.Uh oh!
There was an error while loading. Please reload this page.
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.
AFAIK "underspecified" is how the standard calls it. And
constexprdeclarations in C are also "underspecified".gcc agrees https://godbolt.org/z/1KcW9zTcd .
I agree that
underspecifiedsounds weird for ordinary people though.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.
I also didn't wanted to just use the same wording as GCC.
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.
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 identifierI 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/nvKEzv975I 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.