diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1944,6 +1944,20 @@ let LangOpts = [CPlusPlus]; } +def RequireDesignatedInit : InheritableAttr { + let Spellings = [GNU<"require_designated_init">]; + let Subjects = SubjectList<[Type]>; + // use the following for error instead of warning + // let Subjects = SubjectList<[Type], ErrorDiag>; + let Documentation = [RequireDesignatedInitDocs]; +} + +def Required : InheritableAttr { + let Spellings = [GNU<"required">]; + let Subjects = SubjectList<[Field]>; + let Documentation = [RequiredDocs]; +} + def WorkGroupSizeHint : InheritableAttr { // Does not have a [[]] spelling because it is an OpenCL-related attribute. let Spellings = [GNU<"work_group_size_hint">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1445,6 +1445,61 @@ }]; } +def RequireDesignatedInitDocs : Documentation { + let Category = DocCatType; + let Content = [{ +This attribute can be applied to a struct definition to require that anytime a +variable of that struct's type is declared, the declaration uses `designated +initializer syntax `_. +For a struct ``Foo`` with one integer field ``x``, the following declarations +are valid and invalid when this attribute is applied to the struct's definition. + +.. code-block:: c++ + + Foo foo {.x = 1}; // valid + Foo foo {}; // valid + Foo foo {1}; // invalid + Foo foo; // invalid + +For a struct ``Foo`` with three integer fields ``x``, ``y``, and ``z``, the +following declarations are valid and invalid when this attribute is applied to +the struct's definition. + +.. code-block:: c++ + + Foo foo {.x = 1, .y = 2, .z = 3}; // valid + Foo foo {.z = 3, .x = 1, .y = 2}; // valid + Foo foo {.x = 1, .z = 3}; // valid + Foo foo {}; // valid + Foo foo {1, 2, 3}; // invalid + Foo foo {.x = 1, 2, 3}; // invalid + Foo foo; // invalid + }]; +} + +def RequiredDocs : Documentation { + let Category = DocCatType; + let Content = [{ +This attribute can be applied to a field definition within a struct or a class +to require that anytime a variable of the struct/class's type is declared, that +field must be initialized using `designated initializer syntax `_. +A field marked with this attribute may not be omitted or default-constructed. +For a struct ``Foo`` with a ``required`` integer field ``x``, the following +declarations are valid and invalid. + +.. code-block:: c++ + + struct Foo { + int __attribute__((required)) x; + }; + + Foo foo; // invalid + Foo foo {}; // invalid + Foo foo {1}; // invalid + Foo foo {.x = 1}; // valid + }]; +} + def WarnMaybeUnusedDocs : Documentation { let Category = DocCatVariable; let Heading = "maybe_unused, unused"; @@ -4194,4 +4249,4 @@ not initialized on device side. It has internal linkage and is initialized by the initializer on host side. }]; -} \ No newline at end of file +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3530,6 +3530,16 @@ "'objc_designated_initializer' attribute only applies to init methods " "of interface or class extension declarations">; +def err_require_designated_init_failed : Error< + "variable declaration does not use designated initializer syntax">; +def note_declared_required_designated_init_here : Note< + "required by 'require_designated_init' attribute here">; + +def err_required_failed : Error< + "initializer for variable %0 must explicitly initialize field %1">; +def note_declared_required_here : Note< + "enforced by 'required' attribute here">; + // objc_bridge attribute diagnostics. def err_objc_attr_not_id : Error< "parameter of %0 attribute must be a single name of an Objective-C %select{class|protocol}1">; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -11211,6 +11211,82 @@ Init = Result.get(); } + if (auto *TD = VDecl->getType().getTypePtr()->getAsTagDecl()) { + // If the type of the declaration is a struct/class and that type has the + // require_designated_init attribute, check that the initializer has + // the proper designated initializer syntax. + if (TD->hasAttr()) { + if (auto *ILE = dyn_cast(Init)) { + for (unsigned i = 0; i < ILE->getNumInits(); i++) { + Expr *init = ILE->getInit(i); + if (auto *DIE = dyn_cast(init)) + continue; + SourceRange SR(VDecl->getSourceRange().getBegin(), + Init->getSourceRange().getEnd()); + Diag(init->getExprLoc(), diag::err_require_designated_init_failed) + << SR; + auto attr = TD->getAttr(); + Diag(attr->getLocation(), + diag::note_declared_required_designated_init_here) + << attr->getRange(); + VDecl->setInvalidDecl(); + return; + } + } + } + + // If the type of the declaration is a struct/class, we must check whether + // any of the fields have the required attribute. If any of them do, we must + // confirm that each of those fields are initialized with designated + // initializer syntax. + if (RecordDecl *RD = dyn_cast(TD)) { + // Iterate through all the fields of the record and add all of the + // required fields to a set. The field will be removed later if it is + // properly initialized. + std::set RequiredFields; + for (const auto *FD : RD->fields()) { + if (FD->hasAttr()) { + RequiredFields.insert(FD->getIdentifier()); + } + } + // Iterate through all the initializers and remove a field from the set if + // it is initialized correctly using designated initializer syntax. + if (RequiredFields.size() > 0) { + if (InitListExpr *ILE = dyn_cast(Init)) { + for (unsigned I = 0, E = ILE->getNumInits(); I != E; I++) { + Expr *init = ILE->getInit(I); + if (DesignatedInitExpr *DIE = dyn_cast(init)) { + DesignatedInitExpr::Designator *D = DIE->getDesignator(0); + if (D->isFieldDesignator()) { + IdentifierInfo *name = D->getFieldName(); + if (RequiredFields.count(name) != 0) + RequiredFields.erase(name); + } + } + } + } + // Iterate through all the remaining fields and emit a diagnostic for + // each field. + for (auto FD : RD->fields()) { + if (RequiredFields.count(FD->getIdentifier()) != 0) { + SourceRange SR(VDecl->getSourceRange().getBegin(), + Init->getSourceRange().getEnd()); + Diag(Init->getExprLoc(), diag::err_required_failed) + << SR << VDecl->getName() << FD->getName(); + auto attr = FD->getAttr(); + Diag(attr->getLocation(), diag::note_declared_required_here) + << attr->getRange(); + VDecl->setInvalidDecl(); + RequiredFields.erase(FD->getIdentifier()); + } + } + // Return here so all attribute violation errors get emitted. + if (VDecl->isInvalidDecl()) + return; + } + } + } + // Perform the initialization. ParenListExpr *CXXDirectInit = dyn_cast(Init); if (!VDecl->isInvalidDecl()) { @@ -11552,6 +11628,39 @@ if (VarDecl *Var = dyn_cast(RealDecl)) { QualType Type = Var->getType(); + if (TagDecl *TD = Type.getTypePtr()->getAsTagDecl()) { + // If the type of the declaration is a struct/class and that type has the + // require_designated_init attribute, an initializer is mandatory. + if (TD->hasAttr()) { + Diag(Var->getLocation(), diag::err_require_designated_init_failed) + << Var->getSourceRange(); + auto attr = TD->getAttr(); + Diag(attr->getLocation(), + diag::note_declared_required_designated_init_here) + << attr->getRange(); + Var->setInvalidDecl(); + return; + } + // If the type of the declaration is a struct/class, we must check whether + // any of the fields have the required attribute. For each that does, emit + // an error since it is not initialized with designated initializer + // syntax. + if (RecordDecl *RD = dyn_cast(TD)) { + for (auto FD : RD->fields()) { + if (FD->hasAttr()) { + Diag(Var->getLocation(), diag::err_required_failed) + << Var->getSourceRange() << Var->getName() << FD->getName(); + auto attr = FD->getAttr(); + Diag(attr->getLocation(), diag::note_declared_required_here) + << attr->getRange(); + Var->setInvalidDecl(); + } + } + if (Var->isInvalidDecl()) + return; + } + } + // C++1z [dcl.dcl]p1 grammar implies that an initializer is mandatory. if (isa(RealDecl)) { Diag(Var->getLocation(), diag::err_decomp_decl_requires_init) << Var; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5309,6 +5309,23 @@ AL.getAttributeSpellingListIndex())); } +static void handleRequireDesignatedInit(Sema &S, Decl *D, + const ParsedAttr &AL) { + if (TagDecl *TD = dyn_cast(D)) + TD->addAttr(::new (S.Context) RequireDesignatedInitAttr( + AL.getRange(), S.Context, AL.getAttributeSpellingListIndex())); + else + S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL.getName(); +} + +static void handleRequired(Sema &S, Decl *D, const ParsedAttr &AL) { + if (FieldDecl *FD = dyn_cast(D)) + FD->addAttr(::new (S.Context) RequiredAttr( + AL.getRange(), S.Context, AL.getAttributeSpellingListIndex())); + else + S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL.getName(); +} + static void handleObjCRuntimeName(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef MetaDataName; if (!S.checkStringLiteralArgumentAttr(AL, 0, MetaDataName)) @@ -6959,6 +6976,12 @@ case ParsedAttr::AT_RequireConstantInit: handleSimpleAttribute(S, D, AL); break; + case ParsedAttr::AT_RequireDesignatedInit: + handleRequireDesignatedInit(S, D, AL); + break; + case ParsedAttr::AT_Required: + handleRequired(S, D, AL); + break; case ParsedAttr::AT_InitPriority: handleInitPriorityAttr(S, D, AL); break; diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -122,6 +122,7 @@ // CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function) // CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: RequireConstantInit (SubjectMatchRule_variable_is_global) +// CHECK-NEXT: Required (SubjectMatchRule_field) // CHECK-NEXT: Restrict (SubjectMatchRule_function) // CHECK-NEXT: ReturnTypestate (SubjectMatchRule_function, SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: ReturnsNonNull (SubjectMatchRule_objc_method, SubjectMatchRule_function) diff --git a/clang/test/SemaCXX/attr-require-designated-init.cpp b/clang/test/SemaCXX/attr-require-designated-init.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-require-designated-init.cpp @@ -0,0 +1,104 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +#define ATTR __attribute__((require_designated_init)) + +// The require_designated_init attribute only applies to types. It will +// generate a warning when attached to variables, functions, arrays, etc. +int ATTR x; // expected-warning{{attribute only applies to types}} +void ATTR fun(int x) { // expected-warning{{attribute only applies to types}} + return; +} +int ATTR arr[10]; // expected-warning{{attribute only applies to types}} + +// Struct with one field with require_designated_init attribute +struct ATTR Foo { // expected-note 0+ {{required by 'require_designated_init' attribute here}} + int a; +}; + +// The following are invalid ways of initializing instances of this struct. +Foo f1; // expected-error{{variable declaration does not use designated initializer syntax}} +Foo f2{1}; // expected-error{{variable declaration does not use designated initializer syntax}} +Foo f3 = {1}; // expected-error{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this struct. +Foo f4{}; +Foo f5 = {}; +Foo f6{.a = 1}; +Foo f7 = {.a = 1}; + +// Struct with multiple fields wth require_designated_init attribute +struct ATTR Bar { // expected-note 0+ {{required by 'require_designated_init' attribute here}} + int b; + int c; +}; + +// The following are invalid ways of initializing instances of this struct. +Bar b1; // expected-error{{variable declaration does not use designated initializer syntax}} +Bar b2{1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Bar b3 = {1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Bar b4{.b = 1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Bar b5 = {.b = 1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this struct. +Bar b6{}; +Bar b7 = {}; +Bar b8{.b = 1}; +Bar b9 = {.b = 1}; +Bar b10{.b = 1, .c = 2}; +Bar b11 = {.b = 1, .c = 2}; +Bar b12 = {.c = 2, .b = 1}; + +// Struct without require_designated_init attribute +struct Baz { + int d; + int e; +}; + +// The following are all valid ways of initializing instances of this struct. +Baz z1; +Baz z2{}; +Baz z3 = {}; +Baz z4{1, 2}; +Baz z5 = {1, 2}; +Baz z6{.d = 1, .e = 2}; +Baz z7 = {.d = 1, .e = 2}; +Baz z8{1}; +Baz z9 = {1}; +Baz z10{.d = 1, 2}; +Baz z11 = {.d = 1, 2}; + +// The require_designated_init attribute can also be attached to unions. +union ATTR Uni { // expected-note 0+ {{required by 'require_designated_init' attribute here}} + int x; + int y; +}; + +// The following are invalid ways of initializing instances of this union. +Uni u1; // expected-error{{variable declaration does not use designated initializer syntax}} +Uni u2{1}; // expected-error{{variable declaration does not use designated initializer syntax}} +Uni u3 = {1}; // expected-error{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this union. +Uni u4{}; +Uni u5 = {}; +Uni u6{.x = 1}; +Uni u7 = {.x = 1}; + +// The require_designated_init attribute can also be attached to classes. +class ATTR Cla { // expected-note 0+ {{required by 'require_designated_init' attribute here}} +public: + int x; + int y; +}; + +// The following are invalid ways of initializing instances of this class. +Cla c1; // expected-error{{variable declaration does not use designated initializer syntax}} +Cla c2{1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Cla c3 = {1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Cla c4{.x = 1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +Cla c5 = {.x = 1, 2}; // expected-error{{variable declaration does not use designated initializer syntax}} +// The following are valid ways of initializing instances of this class. +Cla c6{}; +Cla c7 = {}; +Cla c8{.x = 1}; +Cla c9 = {.x = 1}; +Cla c10{.x = 1, .y = 2}; +Cla c11 = {.x = 1, .y = 2}; +Cla c12 = {.y = 2, .x = 1}; diff --git a/clang/test/SemaCXX/attr-required.cpp b/clang/test/SemaCXX/attr-required.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-required.cpp @@ -0,0 +1,63 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +#define ATTR __attribute__((required)) + +int ATTR x; // expected-warning{{'required' attribute only applies to non-static data members}} +void ATTR fun(int x) { // expected-warning{{'required' attribute only applies to non-static data members}} + return; +} +struct ATTR Foo { // expected-warning{{'required' attribute only applies to non-static data members}} + int x; +}; + +// Struct with one required field +struct Bar { + int ATTR y; // expected-note 0+ {{enforced by 'required' attribute here}} +}; + +// The following are invalid ways of initializing instances of this struct. +Bar b1; // expected-error{{initializer for variable b1 must explicitly initialize field y}} +Bar b2{}; // expected-error{{initializer for variable b2 must explicitly initialize field y}} +Bar b3{1}; // expected-error{{initializer for variable b3 must explicitly initialize field y}} + +// The following are valid ways of initializing instances of this struct. +Bar b6{.y = 1}; + +// Struct with multiple required fields +struct Baz { + int ATTR x; // expected-note 0+ {{enforced by 'required' attribute here}} + int y; + int ATTR z; // expected-note 0+ {{enforced by 'required' attribute here}} +}; + +// The following are invalid ways of initializing instances of this struct. +Baz z1; // expected-error{{initializer for variable z1 must explicitly initialize field x}} expected-error{{initializer for variable z1 must explicitly initialize field z}} +Baz z2{}; // expected-error{{initializer for variable z2 must explicitly initialize field x}} expected-error{{initializer for variable z2 must explicitly initialize field z}} +Baz z3{1, 2}; // expected-error{{initializer for variable z3 must explicitly initialize field x}} expected-error{{initializer for variable z3 must explicitly initialize field z}} +Baz z4{1, 2, 3}; // expected-error{{initializer for variable z4 must explicitly initialize field x}} expected-error{{initializer for variable z4 must explicitly initialize field z}} +Baz z5{.x = 1, 2}; // expected-error{{initializer for variable z5 must explicitly initialize field z}} +Baz z6{.x = 1, .y = 2}; // expected-error{{initializer for variable z6 must explicitly initialize field z}} + +// The following are valid ways of initializing instances of this struct. +Baz z7{.x = 1, .y = 2, .z = 3}; +Baz z8{.x = 1, .z = 3}; +Baz z9{.x = 1, 2, .z = 3}; + +// The required attribute can also be applied to public fields of classes. +class Cla { +public: + int ATTR x; // expected-note 0+ {{enforced by 'required' attribute here}} + int y; +}; + +// The following are invalid ways of initializing instances of this class. +Cla c1; // expected-error{{initializer for variable c1 must explicitly initialize field x}} +Cla c2{}; // expected-error{{initializer for variable c2 must explicitly initialize field x}} +Cla c3{1}; // expected-error{{initializer for variable c3 must explicitly initialize field x}} +Cla c4{1, 2}; // expected-error{{initializer for variable c4 must explicitly initialize field x}} +Cla c5{1, .y = 2}; // expected-error{{initializer for variable c5 must explicitly initialize field x}} + +// The following are valid ways of initializing instances of this class. +Cla c6{.x = 1}; +Cla c7{.x = 1, .y = 2}; +Cla c8{.x = 1, 2};