Skip to content

Commit d150f96

Browse files
authored
Merge pull request #14945 from jrose-apple/frozen-enums
Implementation for /most/ of SE-0192 (frozen and non-frozen enums)
2 parents 478a195 + 348f966 commit d150f96

File tree

78 files changed

+1967
-327
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+1967
-327
lines changed

include/swift/AST/Attr.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ SIMPLE_DECL_ATTR(nonobjc, NonObjC,
164164
OnExtension | OnFunc | OnVar | OnSubscript | OnConstructor, 30)
165165

166166
SIMPLE_DECL_ATTR(_fixed_layout, FixedLayout,
167-
OnVar | OnClass | OnStruct | OnEnum | UserInaccessible, 31)
167+
OnVar | OnClass | OnStruct | UserInaccessible, 31)
168168

169169
SIMPLE_DECL_ATTR(_inlineable, Inlineable,
170170
OnVar | OnSubscript | OnFunc | OnConstructor | OnDestructor |
@@ -314,6 +314,8 @@ SIMPLE_DECL_ATTR(_weakLinked, WeakLinked,
314314
UserInaccessible,
315315
75)
316316

317+
SIMPLE_DECL_ATTR(_frozen, Frozen, OnEnum | UserInaccessible, 76)
318+
317319
#undef TYPE_ATTR
318320
#undef DECL_ATTR_ALIAS
319321
#undef SIMPLE_DECL_ATTR

include/swift/AST/Decl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3224,6 +3224,13 @@ class EnumDecl final : public NominalTypeDecl {
32243224
bool isIndirect() const {
32253225
return getAttrs().hasAttribute<IndirectAttr>();
32263226
}
3227+
3228+
/// True if the enum can be exhaustively switched within \p useDC.
3229+
///
3230+
/// Note that this property is \e not necessarily true for all children of
3231+
/// \p useDC. In particular, an inlinable function does not get to switch
3232+
/// exhaustively over a non-exhaustive enum declared in the same module.
3233+
bool isExhaustive(const DeclContext *useDC) const;
32273234
};
32283235

32293236
/// StructDecl - This is the declaration of a struct, for example:

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1204,11 +1204,15 @@ WARNING(expr_dynamic_lookup_swift3_objc_inference,none,
12041204
ERROR(alignment_not_power_of_two,none,
12051205
"alignment value must be a power of two", ())
12061206

1207-
// Indirect enums
1207+
// Enum annotations
12081208
ERROR(indirect_case_without_payload,none,
12091209
"enum case %0 without associated value cannot be 'indirect'", (Identifier))
12101210
ERROR(indirect_case_in_indirect_enum,none,
12111211
"enum case in 'indirect' enum cannot also be 'indirect'", ())
1212+
WARNING(enum_frozen_nonresilient,none,
1213+
"%0 has no effect without -enable-resilience", (DeclAttribute))
1214+
WARNING(enum_frozen_nonpublic,none,
1215+
"%0 has no effect on non-public enums", (DeclAttribute))
12121216

12131217
// Variables (var and let).
12141218
ERROR(unimplemented_static_var,none,

include/swift/Basic/LangOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ namespace swift {
239239
/// Diagnose uses of NSCoding with classes that have unstable mangled names.
240240
bool EnableNSKeyedArchiverDiagnostics = true;
241241

242+
/// Diagnose switches over non-frozen enums that do not have catch-all
243+
/// cases.
244+
bool EnableNonFrozenEnumExhaustivityDiagnostics = false;
245+
242246
/// Regex for the passes that should report passed and missed optimizations.
243247
///
244248
/// These are shared_ptrs so that this class remains copyable.

include/swift/Option/FrontendOptions.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ def disable_nskeyedarchiver_diagnostics :
321321
Flag<["-"], "disable-nskeyedarchiver-diagnostics">,
322322
HelpText<"Allow classes with unstable mangled names to adopt NSCoding">;
323323

324+
def enable_nonfrozen_enum_exhaustivity_diagnostics :
325+
Flag<["-"], "enable-nonfrozen-enum-exhaustivity-diagnostics">,
326+
HelpText<"Diagnose switches over non-frozen enums without catch-all cases">;
327+
def disable_nonfrozen_enum_exhaustivity_diagnostics :
328+
Flag<["-"], "disable-nonfrozen-enum-exhaustivity-diagnostics">,
329+
HelpText<"Allow switches over non-frozen enums without catch-all cases">;
330+
324331
def warn_long_function_bodies : Separate<["-"], "warn-long-function-bodies">,
325332
MetaVarName<"<n>">,
326333
HelpText<"Warns when type-checking a function takes longer than <n> ms">;

lib/AST/ASTDumper.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,9 @@ namespace {
757757

758758
if (NTD->hasInterfaceType()) {
759759
if (NTD->isResilient())
760-
OS << " @_resilient_layout";
760+
OS << " resilient";
761761
else
762-
OS << " @_fixed_layout";
762+
OS << " non-resilient";
763763
}
764764
}
765765

lib/AST/Decl.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2286,9 +2286,11 @@ bool NominalTypeDecl::isFormallyResilient() const {
22862286
/*respectVersionedAttr=*/true).isPublic())
22872287
return false;
22882288

2289-
// Check for an explicit @_fixed_layout attribute.
2290-
if (getAttrs().hasAttribute<FixedLayoutAttr>())
2289+
// Check for an explicit @_fixed_layout or @_frozen attribute.
2290+
if (getAttrs().hasAttribute<FixedLayoutAttr>() ||
2291+
getAttrs().hasAttribute<FrozenAttr>()) {
22912292
return false;
2293+
}
22922294

22932295
// Structs and enums imported from C *always* have a fixed layout.
22942296
// We know their size, and pass them as values in SIL and IRGen.
@@ -3020,6 +3022,53 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
30203022
return true;
30213023
}
30223024

3025+
bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
3026+
// Enums explicitly marked frozen are exhaustive.
3027+
if (getAttrs().hasAttribute<FrozenAttr>())
3028+
return true;
3029+
3030+
// Objective-C enums /not/ marked frozen are /not/ exhaustive.
3031+
// Note: This implicitly holds @objc enums defined in Swift to a higher
3032+
// standard!
3033+
if (hasClangNode())
3034+
return false;
3035+
3036+
// Non-imported enums in non-resilient modules are exhaustive.
3037+
const ModuleDecl *containingModule = getModuleContext();
3038+
switch (containingModule->getResilienceStrategy()) {
3039+
case ResilienceStrategy::Default:
3040+
return true;
3041+
case ResilienceStrategy::Resilient:
3042+
break;
3043+
}
3044+
3045+
// Non-public, non-versioned enums are always exhaustive.
3046+
AccessScope accessScope = getFormalAccessScope(/*useDC*/nullptr,
3047+
/*respectVersioned*/true);
3048+
if (!accessScope.isPublic())
3049+
return true;
3050+
3051+
// All other checks are use-site specific; with no further information, the
3052+
// enum must be treated non-exhaustively.
3053+
if (!useDC)
3054+
return false;
3055+
3056+
// Enums in the same module as the use site are exhaustive /unless/ the use
3057+
// site is inlinable.
3058+
if (useDC->getParentModule() == containingModule)
3059+
if (useDC->getResilienceExpansion() == ResilienceExpansion::Maximal)
3060+
return true;
3061+
3062+
// Testably imported enums are exhaustive, on the grounds that only the author
3063+
// of the original library can import it testably.
3064+
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
3065+
if (useSF->hasTestableImport(containingModule))
3066+
return true;
3067+
3068+
// Otherwise, the enum is non-exhaustive.
3069+
return false;
3070+
}
3071+
30233072
ProtocolDecl::ProtocolDecl(DeclContext *DC, SourceLoc ProtocolLoc,
30243073
SourceLoc NameLoc, Identifier Name,
30253074
MutableArrayRef<TypeLoc> Inherited,

lib/ClangImporter/ImportDecl.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2653,7 +2653,8 @@ namespace {
26532653
break;
26542654
}
26552655

2656-
case EnumKind::Enum: {
2656+
case EnumKind::NonFrozenEnum:
2657+
case EnumKind::FrozenEnum: {
26572658
auto &C = Impl.SwiftContext;
26582659
EnumDecl *nativeDecl;
26592660
bool declaredNative = hasNativeSwiftDecl(decl, name, dc, nativeDecl);
@@ -2757,6 +2758,14 @@ namespace {
27572758
Impl.importSourceLoc(decl->getLocation()), None, nullptr, enumDC);
27582759
enumDecl->computeType();
27592760

2761+
// Annotate as 'frozen' if appropriate.
2762+
assert((DeclAttribute::getOptions(DAK_Frozen) &
2763+
DeclAttribute::UserInaccessible) &&
2764+
"Once 'frozen' is supported, the attribute should not be "
2765+
"implicit (below)");
2766+
if (enumKind == EnumKind::FrozenEnum)
2767+
enumDecl->getAttrs().add(new (C) FrozenAttr(/*implicit*/true));
2768+
27602769
// Set up the C underlying type as its Swift raw type.
27612770
enumDecl->setRawType(underlyingType);
27622771

@@ -2860,7 +2869,8 @@ namespace {
28602869
addEnumeratorsAsMembers = false;
28612870
break;
28622871
case EnumKind::Options:
2863-
case EnumKind::Enum:
2872+
case EnumKind::NonFrozenEnum:
2873+
case EnumKind::FrozenEnum:
28642874
addEnumeratorsAsMembers = true;
28652875
break;
28662876
}
@@ -2870,7 +2880,8 @@ namespace {
28702880
EnumElementDecl *>, 8,
28712881
APSIntRefDenseMapInfo> canonicalEnumConstants;
28722882

2873-
if (enumKind == EnumKind::Enum) {
2883+
if (enumKind == EnumKind::NonFrozenEnum ||
2884+
enumKind == EnumKind::FrozenEnum) {
28742885
for (auto constant : decl->enumerators()) {
28752886
if (Impl.isUnavailableInSwift(constant))
28762887
continue;
@@ -2931,7 +2942,8 @@ namespace {
29312942
return true;
29322943
});
29332944
break;
2934-
case EnumKind::Enum: {
2945+
case EnumKind::NonFrozenEnum:
2946+
case EnumKind::FrozenEnum: {
29352947
auto canonicalCaseIter =
29362948
canonicalEnumConstants.find(&constant->getInitVal());
29372949

@@ -3379,7 +3391,8 @@ namespace {
33793391
return result;
33803392
}
33813393

3382-
case EnumKind::Enum:
3394+
case EnumKind::NonFrozenEnum:
3395+
case EnumKind::FrozenEnum:
33833396
case EnumKind::Options: {
33843397
// The enumeration was mapped to a high-level Swift type, and its
33853398
// elements were created as children of that enum. They aren't available

lib/ClangImporter/ImportEnumInfo.cpp

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,55 @@ STATISTIC(EnumInfoNumCacheMisses, "# of times the enum info cache was missed");
3333
using namespace swift;
3434
using namespace importer;
3535

36+
static void rememberToChangeThisBehaviorInSwift5() {
37+
// Note: Once the compiler starts advertising itself as Swift 5, even
38+
// Swift 4 mode is supposed to treat C enums as non-exhaustive. Because
39+
// it's Swift 4 mode, failing to switch over the whole enum will only
40+
// produce a warning, not an error.
41+
//
42+
// This is an assertion rather than a condition because we /want/ to be
43+
// reminded to take it out when we're ready for the Swift 5 release.
44+
assert(version::getSwiftNumericVersion().first < 5 &&
45+
"When the compiler starts advertising itself as Swift 5, even "
46+
"Swift 4 mode is supposed to treat C enums as non-exhaustive.");
47+
}
48+
49+
/// Find the last extensibility attribute on \p decl as arranged by source
50+
/// location...unless there's an API note, in which case that one wins.
51+
///
52+
/// This is not what Clang will do, but it's more useful for us since CF_ENUM
53+
/// already has enum_extensibility(open) in it.
54+
static clang::EnumExtensibilityAttr *
55+
getBestExtensibilityAttr(clang::Preprocessor &pp, const clang::EnumDecl *decl) {
56+
clang::EnumExtensibilityAttr *bestSoFar = nullptr;
57+
const clang::SourceManager &sourceMgr = pp.getSourceManager();
58+
for (auto *next : decl->specific_attrs<clang::EnumExtensibilityAttr>()) {
59+
if (next->getLocation().isInvalid()) {
60+
// This is from API notes -- use it!
61+
return next;
62+
}
63+
64+
// Temporarily ignore enum_extensibility attributes inside CF_ENUM and
65+
// similar. In the Swift 5 release we can start respecting this annotation,
66+
// meaning this entire block can be dropped.
67+
{
68+
rememberToChangeThisBehaviorInSwift5();
69+
auto loc = next->getLocation();
70+
if (loc.isMacroID() &&
71+
pp.getImmediateMacroName(loc) == "__CF_ENUM_ATTRIBUTES") {
72+
continue;
73+
}
74+
}
75+
76+
if (!bestSoFar ||
77+
sourceMgr.isBeforeInTranslationUnit(bestSoFar->getLocation(),
78+
next->getLocation())) {
79+
bestSoFar = next;
80+
}
81+
}
82+
return bestSoFar;
83+
}
84+
3685
/// Classify the given Clang enumeration to describe how to import it.
3786
void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
3887
clang::Preprocessor &pp) {
@@ -49,21 +98,24 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
4998
return;
5099
}
51100

52-
// First, check for attributes that denote the classification
101+
// First, check for attributes that denote the classification.
53102
if (auto domainAttr = decl->getAttr<clang::NSErrorDomainAttr>()) {
54-
kind = EnumKind::Enum;
103+
kind = EnumKind::NonFrozenEnum;
55104
nsErrorDomain = domainAttr->getErrorDomain()->getName();
56-
return;
57105
}
58106
if (decl->hasAttr<clang::FlagEnumAttr>()) {
59107
kind = EnumKind::Options;
60108
return;
61109
}
62-
if (decl->hasAttr<clang::EnumExtensibilityAttr>()) {
63-
// FIXME: Distinguish between open and closed enums.
64-
kind = EnumKind::Enum;
110+
if (auto *attr = getBestExtensibilityAttr(pp, decl)) {
111+
if (attr->getExtensibility() == clang::EnumExtensibilityAttr::Closed)
112+
kind = EnumKind::FrozenEnum;
113+
else
114+
kind = EnumKind::NonFrozenEnum;
65115
return;
66116
}
117+
if (!nsErrorDomain.empty())
118+
return;
67119

68120
// If API notes have /removed/ a FlagEnum or EnumExtensibility attribute,
69121
// then we don't need to check the macros.
@@ -79,15 +131,15 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
79131

80132
// Was the enum declared using *_ENUM or *_OPTIONS?
81133
// FIXME: Stop using these once flag_enum and enum_extensibility
82-
// have been adopted everywhere, or at least relegate them to Swift 3 mode
134+
// have been adopted everywhere, or at least relegate them to Swift 4 mode
83135
// only.
84136
auto loc = decl->getLocStart();
85137
if (loc.isMacroID()) {
86138
StringRef MacroName = pp.getImmediateMacroName(loc);
87139
if (MacroName == "CF_ENUM" || MacroName == "__CF_NAMED_ENUM" ||
88140
MacroName == "OBJC_ENUM" || MacroName == "SWIFT_ENUM" ||
89141
MacroName == "SWIFT_ENUM_NAMED") {
90-
kind = EnumKind::Enum;
142+
kind = EnumKind::NonFrozenEnum;
91143
return;
92144
}
93145
if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" ||
@@ -99,7 +151,7 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
99151

100152
// Hardcode a particular annoying case in the OS X headers.
101153
if (decl->getName() == "DYLD_BOOL") {
102-
kind = EnumKind::Enum;
154+
kind = EnumKind::FrozenEnum;
103155
return;
104156
}
105157

@@ -205,7 +257,8 @@ StringRef importer::getCommonPluralPrefix(StringRef singular,
205257
/// within the given enum.
206258
void EnumInfo::determineConstantNamePrefix(const clang::EnumDecl *decl) {
207259
switch (getKind()) {
208-
case EnumKind::Enum:
260+
case EnumKind::NonFrozenEnum:
261+
case EnumKind::FrozenEnum:
209262
case EnumKind::Options:
210263
// Enums are mapped to Swift enums, Options to Swift option sets, both
211264
// of which attempt prefix-stripping.

lib/ClangImporter/ImportEnumInfo.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ namespace importer {
3535
/// into Swift. All of the possibilities have the same storage
3636
/// representation, but can be used in different ways.
3737
enum class EnumKind {
38-
/// The enumeration type should map to an enum, which means that
39-
/// all of the cases are independent.
40-
Enum,
41-
/// The enumeration type should map to an option set, which means
42-
/// that
38+
/// The enumeration type should map to a frozen enum, which means that
39+
/// all of the cases are independent and there are no private cases.
40+
FrozenEnum,
41+
/// The enumeration type should map to a non-frozen enum, which means that
42+
/// all of the cases are independent, but there may be values not represented
43+
/// in the listed cases.
44+
NonFrozenEnum,
45+
/// The enumeration type should map to an option set, which means that
4346
/// the constants represent combinations of independent flags.
4447
Options,
4548
/// The enumeration type should map to a distinct type, but we don't
@@ -76,7 +79,15 @@ class EnumInfo {
7679

7780
/// Whether this maps to an enum who also provides an error domain
7881
bool isErrorEnum() const {
79-
return getKind() == EnumKind::Enum && !nsErrorDomain.empty();
82+
switch (getKind()) {
83+
case EnumKind::FrozenEnum:
84+
case EnumKind::NonFrozenEnum:
85+
return !nsErrorDomain.empty();
86+
case EnumKind::Options:
87+
case EnumKind::Unknown:
88+
case EnumKind::Constants:
89+
return false;
90+
}
8091
}
8192

8293
/// For this error enum, extract the name of the error domain constant

lib/ClangImporter/ImportName.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,8 @@ NameImporter::determineEffectiveContext(const clang::NamedDecl *decl,
887887
if (isa<clang::EnumConstantDecl>(decl)) {
888888
auto enumDecl = cast<clang::EnumDecl>(dc);
889889
switch (getEnumKind(enumDecl)) {
890-
case EnumKind::Enum:
890+
case EnumKind::NonFrozenEnum:
891+
case EnumKind::FrozenEnum:
891892
case EnumKind::Options:
892893
// Enums are mapped to Swift enums, Options to Swift option sets.
893894
if (version != ImportNameVersion::raw()) {
@@ -1005,7 +1006,8 @@ static bool shouldBeSwiftPrivate(NameImporter &nameImporter,
10051006
if (auto *ECD = dyn_cast<clang::EnumConstantDecl>(decl)) {
10061007
auto *ED = cast<clang::EnumDecl>(ECD->getDeclContext());
10071008
switch (nameImporter.getEnumKind(ED)) {
1008-
case EnumKind::Enum:
1009+
case EnumKind::NonFrozenEnum:
1010+
case EnumKind::FrozenEnum:
10091011
case EnumKind::Options:
10101012
if (version != ImportNameVersion::raw())
10111013
break;

0 commit comments

Comments
 (0)