Skip to content

Commit 3c268af

Browse files
committed
Add support for attribute enum_extensibility.
This commit adds support for a new attribute that will be used to distinguish between extensible and inextensible enums. There are three main purposes of this attribute: 1. Give better control over when enum-related warnings are issued. For example, in the code below, clang will not issue a -Wassign-enum warning if the enum is marked "open": enum __attribute__((enum_extensibility(closed))) EnumClosed { B0 = 1, B1 = 10 }; enum __attribute__((enum_extensibility(open))) EnumOpen { C0 = 1, C1 = 10 }; enum EnumClosed ec = 100; // warning issued enum EnumOpen eo = 100; // no warning 2. Enable code-completion and debugging tools to offer better suggestions. 3. Make it easier for swift's clang importer to determine which swift type an enum should be mapped to. For more details, see the discussion I started on cfe-dev: http://lists.llvm.org/pipermail/cfe-dev/2017-February/052748.html rdar://problem/12764379 rdar://problem/23145650 Differential Revision: https://reviews.llvm.org/D30766 llvm-svn: 298332
1 parent 9a4bce7 commit 3c268af

File tree

10 files changed

+362
-15
lines changed

10 files changed

+362
-15
lines changed

clang/include/clang/AST/Decl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3235,6 +3235,18 @@ class EnumDecl : public TagDecl {
32353235
return isCompleteDefinition() || isFixed();
32363236
}
32373237

3238+
/// Returns true if this enum is either annotated with
3239+
/// enum_extensibility(closed) or isn't annotated with enum_extensibility.
3240+
bool isClosed() const;
3241+
3242+
/// Returns true if this enum is annotated with flag_enum and isn't annotated
3243+
/// with enum_extensibility(open).
3244+
bool isClosedFlag() const;
3245+
3246+
/// Returns true if this enum is annotated with neither flag_enum nor
3247+
/// enum_extensibility(open).
3248+
bool isClosedNonFlag() const;
3249+
32383250
/// \brief Retrieve the enum definition from which this enumeration could
32393251
/// be instantiated, if it is an instantiation (rather than a non-template).
32403252
EnumDecl *getTemplateInstantiationPattern() const;

clang/include/clang/Basic/Attr.td

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,15 @@ def FlagEnum : InheritableAttr {
881881
let Spellings = [GNU<"flag_enum">];
882882
let Subjects = SubjectList<[Enum]>;
883883
let Documentation = [FlagEnumDocs];
884-
let LangOpts = [COnly];
884+
}
885+
886+
def EnumExtensibility : InheritableAttr {
887+
let Spellings = [GNU<"enum_extensibility">,
888+
CXX11<"clang", "enum_extensibility">];
889+
let Subjects = SubjectList<[Enum]>;
890+
let Args = [EnumArgument<"Extensibility", "Kind",
891+
["closed", "open"], ["Closed", "Open"]>];
892+
let Documentation = [EnumExtensibilityDocs];
885893
}
886894

887895
def Flatten : InheritableAttr {

clang/include/clang/Basic/AttrDocs.td

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,55 @@ manipulating bits of the enumerator when issuing warnings.
19631963
}];
19641964
}
19651965

1966+
def EnumExtensibilityDocs : Documentation {
1967+
let Category = DocCatType;
1968+
let Content = [{
1969+
Attribute ``enum_extensibility`` is used to distinguish between enum definitions
1970+
that are extensible and those that are not. The attribute can take either
1971+
``closed`` or ``open`` as an argument. ``closed`` indicates a variable of the
1972+
enum type takes a value that corresponds to one of the enumerators listed in the
1973+
enum definition or, when the enum is annotated with ``flag_enum``, a value that
1974+
can be constructed using values corresponding to the enumerators. ``open``
1975+
indicates a variable of the enum type can take any values allowed by the
1976+
standard and instructs clang to be more lenient when issuing warnings.
1977+
1978+
.. code-block:: c
1979+
1980+
enum __attribute__((enum_extensibility(closed))) ClosedEnum {
1981+
A0, A1
1982+
};
1983+
1984+
enum __attribute__((enum_extensibility(open))) OpenEnum {
1985+
B0, B1
1986+
};
1987+
1988+
enum __attribute__((enum_extensibility(closed),flag_enum)) ClosedFlagEnum {
1989+
C0 = 1 << 0, C1 = 1 << 1
1990+
};
1991+
1992+
enum __attribute__((enum_extensibility(open),flag_enum)) OpenFlagEnum {
1993+
D0 = 1 << 0, D1 = 1 << 1
1994+
};
1995+
1996+
void foo1() {
1997+
enum ClosedEnum ce;
1998+
enum OpenEnum oe;
1999+
enum ClosedFlagEnum cfe;
2000+
enum OpenFlagEnum ofe;
2001+
2002+
ce = A1; // no warnings
2003+
ce = 100; // warning issued
2004+
oe = B1; // no warnings
2005+
oe = 100; // no warnings
2006+
cfe = C0 | C1; // no warnings
2007+
cfe = C0 | C1 | 4; // warning issued
2008+
ofe = D0 | D1; // no warnings
2009+
ofe = D0 | D1 | 4; // no warnings
2010+
}
2011+
2012+
}];
2013+
}
2014+
19662015
def EmptyBasesDocs : Documentation {
19672016
let Category = DocCatType;
19682017
let Content = [{

clang/lib/AST/Decl.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3742,6 +3742,20 @@ void EnumDecl::completeDefinition(QualType NewType,
37423742
TagDecl::completeDefinition();
37433743
}
37443744

3745+
bool EnumDecl::isClosed() const {
3746+
if (const auto *A = getAttr<EnumExtensibilityAttr>())
3747+
return A->getExtensibility() == EnumExtensibilityAttr::Closed;
3748+
return true;
3749+
}
3750+
3751+
bool EnumDecl::isClosedFlag() const {
3752+
return isClosed() && hasAttr<FlagEnumAttr>();
3753+
}
3754+
3755+
bool EnumDecl::isClosedNonFlag() const {
3756+
return isClosed() && !hasAttr<FlagEnumAttr>();
3757+
}
3758+
37453759
TemplateSpecializationKind EnumDecl::getTemplateSpecializationKind() const {
37463760
if (MemberSpecializationInfo *MSI = getMemberSpecializationInfo())
37473761
return MSI->getTemplateSpecializationKind();

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15358,7 +15358,7 @@ static void CheckForDuplicateEnumValues(Sema &S, ArrayRef<Decl *> Elements,
1535815358

1535915359
bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
1536015360
bool AllowMask) const {
15361-
assert(ED->hasAttr<FlagEnumAttr>() && "looking for value in non-flag enum");
15361+
assert(ED->isClosedFlag() && "looking for value in non-flag or open enum");
1536215362
assert(ED->isCompleteDefinition() && "expected enum definition");
1536315363

1536415364
auto R = FlagBitsCache.insert(std::make_pair(ED, llvm::APInt()));
@@ -15603,7 +15603,7 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
1560315603

1560415604
CheckForDuplicateEnumValues(*this, Elements, Enum, EnumType);
1560515605

15606-
if (Enum->hasAttr<FlagEnumAttr>()) {
15606+
if (Enum->isClosedFlag()) {
1560715607
for (Decl *D : Elements) {
1560815608
EnumConstantDecl *ECD = cast_or_null<EnumConstantDecl>(D);
1560915609
if (!ECD) continue; // Already issued a diagnostic.

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,6 +2943,28 @@ static void handleCleanupAttr(Sema &S, Decl *D, const AttributeList &Attr) {
29432943
Attr.getAttributeSpellingListIndex()));
29442944
}
29452945

2946+
static void handleEnumExtensibilityAttr(Sema &S, Decl *D,
2947+
const AttributeList &Attr) {
2948+
if (!Attr.isArgIdent(0)) {
2949+
S.Diag(Attr.getLoc(), diag::err_attribute_argument_n_type)
2950+
<< Attr.getName() << 0 << AANT_ArgumentIdentifier;
2951+
return;
2952+
}
2953+
2954+
EnumExtensibilityAttr::Kind ExtensibilityKind;
2955+
IdentifierInfo *II = Attr.getArgAsIdent(0)->Ident;
2956+
if (!EnumExtensibilityAttr::ConvertStrToKind(II->getName(),
2957+
ExtensibilityKind)) {
2958+
S.Diag(Attr.getLoc(), diag::warn_attribute_type_not_supported)
2959+
<< Attr.getName() << II;
2960+
return;
2961+
}
2962+
2963+
D->addAttr(::new (S.Context) EnumExtensibilityAttr(
2964+
Attr.getRange(), S.Context, ExtensibilityKind,
2965+
Attr.getAttributeSpellingListIndex()));
2966+
}
2967+
29462968
/// Handle __attribute__((format_arg((idx)))) attribute based on
29472969
/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
29482970
static void handleFormatArgAttr(Sema &S, Decl *D, const AttributeList &Attr) {
@@ -5856,6 +5878,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
58565878
case AttributeList::AT_FlagEnum:
58575879
handleSimpleAttribute<FlagEnumAttr>(S, D, Attr);
58585880
break;
5881+
case AttributeList::AT_EnumExtensibility:
5882+
handleEnumExtensibilityAttr(S, D, Attr);
5883+
break;
58595884
case AttributeList::AT_Flatten:
58605885
handleSimpleAttribute<FlattenAttr>(S, D, Attr);
58615886
break;

clang/lib/Sema/SemaStmt.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,9 @@ static bool ShouldDiagnoseSwitchCaseNotInEnum(const Sema &S,
711711
EnumValsTy::iterator &EI,
712712
EnumValsTy::iterator &EIEnd,
713713
const llvm::APSInt &Val) {
714+
if (!ED->isClosed())
715+
return false;
716+
714717
if (const DeclRefExpr *DRE =
715718
dyn_cast<DeclRefExpr>(CaseExpr->IgnoreParenImpCasts())) {
716719
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
@@ -722,15 +725,14 @@ static bool ShouldDiagnoseSwitchCaseNotInEnum(const Sema &S,
722725
}
723726
}
724727

725-
if (ED->hasAttr<FlagEnumAttr>()) {
728+
if (ED->hasAttr<FlagEnumAttr>())
726729
return !S.IsValueInFlagEnum(ED, Val, false);
727-
} else {
728-
while (EI != EIEnd && EI->first < Val)
729-
EI++;
730730

731-
if (EI != EIEnd && EI->first == Val)
732-
return false;
733-
}
731+
while (EI != EIEnd && EI->first < Val)
732+
EI++;
733+
734+
if (EI != EIEnd && EI->first == Val)
735+
return false;
734736

735737
return true;
736738
}
@@ -1147,7 +1149,7 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, Stmt *Switch,
11471149
}
11481150
}
11491151

1150-
if (TheDefaultStmt && UnhandledNames.empty())
1152+
if (TheDefaultStmt && UnhandledNames.empty() && ED->isClosedNonFlag())
11511153
Diag(TheDefaultStmt->getDefaultLoc(), diag::warn_unreachable_default);
11521154

11531155
// Produce a nice diagnostic if multiple values aren't handled.
@@ -1198,6 +1200,9 @@ Sema::DiagnoseAssignmentEnum(QualType DstType, QualType SrcType,
11981200
AdjustAPSInt(RhsVal, DstWidth, DstIsSigned);
11991201
const EnumDecl *ED = ET->getDecl();
12001202

1203+
if (!ED->isClosed())
1204+
return;
1205+
12011206
if (ED->hasAttr<FlagEnumAttr>()) {
12021207
if (!IsValueInFlagEnum(ED, RhsVal, true))
12031208
Diag(SrcExpr->getExprLoc(), diag::warn_not_in_enum_assignment)

clang/test/Sema/enum-attr.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -Wassign-enum -Wswitch-enum -Wcovered-switch-default %s
2+
3+
enum Enum {
4+
A0 = 1, A1 = 10
5+
};
6+
7+
enum __attribute__((enum_extensibility(closed))) EnumClosed {
8+
B0 = 1, B1 = 10
9+
};
10+
11+
enum __attribute__((enum_extensibility(open))) EnumOpen {
12+
C0 = 1, C1 = 10
13+
};
14+
15+
enum __attribute__((flag_enum)) EnumFlag {
16+
D0 = 1, D1 = 8
17+
};
18+
19+
enum __attribute__((flag_enum,enum_extensibility(closed))) EnumFlagClosed {
20+
E0 = 1, E1 = 8
21+
};
22+
23+
enum __attribute__((flag_enum,enum_extensibility(open))) EnumFlagOpen {
24+
F0 = 1, F1 = 8
25+
};
26+
27+
enum __attribute__((enum_extensibility(arg1))) EnumInvalidArg { // expected-warning{{'enum_extensibility' attribute argument not supported: 'arg1'}}
28+
X0
29+
};
30+
31+
// FIXME: The warning should mention that enum_extensibility takes only one argument.
32+
enum __attribute__((enum_extensibility(closed,open))) EnumTooManyArgs { // expected-error{{use of undeclared identifier 'open'}}
33+
X1
34+
};
35+
36+
enum __attribute__((enum_extensibility())) EnumTooFewArgs { // expected-error{{'enum_extensibility' attribute takes one argument}}
37+
X2
38+
};
39+
40+
struct __attribute__((enum_extensibility(open))) S { // expected-warning{{'enum_extensibility' attribute only applies to enums}}{
41+
};
42+
43+
void test() {
44+
enum Enum t0 = 100; // expected-warning{{integer constant not in range of enumerated type}}
45+
t0 = 1;
46+
47+
switch (t0) { // expected-warning{{enumeration value 'A1' not handled in switch}}
48+
case A0: break;
49+
case 16: break; // expected-warning{{case value not in enumerated type}}
50+
}
51+
52+
switch (t0) {
53+
case A0: break;
54+
case A1: break;
55+
default: break; // expected-warning{{default label in switch which covers all enumeration}}
56+
}
57+
58+
enum EnumClosed t1 = 100; // expected-warning{{integer constant not in range of enumerated type}}
59+
t1 = 1;
60+
61+
switch (t1) { // expected-warning{{enumeration value 'B1' not handled in switch}}
62+
case B0: break;
63+
case 16: break; // expected-warning{{case value not in enumerated type}}
64+
}
65+
66+
switch (t1) {
67+
case B0: break;
68+
case B1: break;
69+
default: break; // expected-warning{{default label in switch which covers all enumeration}}
70+
}
71+
72+
enum EnumOpen t2 = 100;
73+
t2 = 1;
74+
75+
switch (t2) { // expected-warning{{enumeration value 'C1' not handled in switch}}
76+
case C0: break;
77+
case 16: break;
78+
}
79+
80+
switch (t2) {
81+
case C0: break;
82+
case C1: break;
83+
default: break;
84+
}
85+
86+
enum EnumFlag t3 = 5; // expected-warning{{integer constant not in range of enumerated type}}
87+
t3 = 9;
88+
89+
switch (t3) { // expected-warning{{enumeration value 'D1' not handled in switch}}
90+
case D0: break;
91+
case 9: break;
92+
case 16: break; // expected-warning{{case value not in enumerated type}}
93+
}
94+
95+
switch (t3) {
96+
case D0: break;
97+
case D1: break;
98+
default: break;
99+
}
100+
101+
enum EnumFlagClosed t4 = 5; // expected-warning{{integer constant not in range of enumerated type}}
102+
t4 = 9;
103+
104+
switch (t4) { // expected-warning{{enumeration value 'E1' not handled in switch}}
105+
case E0: break;
106+
case 9: break;
107+
case 16: break; // expected-warning{{case value not in enumerated type}}
108+
}
109+
110+
switch (t4) {
111+
case E0: break;
112+
case E1: break;
113+
default: break;
114+
}
115+
116+
enum EnumFlagOpen t5 = 5;
117+
t5 = 9;
118+
119+
switch (t5) { // expected-warning{{enumeration value 'F1' not handled in switch}}
120+
case F0: break;
121+
case 9: break;
122+
case 16: break;
123+
}
124+
125+
switch (t5) {
126+
case F0: break;
127+
case F1: break;
128+
default: break;
129+
}
130+
}

clang/test/SemaCXX/attr-flag-enum-reject.cpp

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)