Skip to content

[WIP] Implementation of non-exhaustive enums #11961

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

Closed
wants to merge 14 commits into from
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
5 changes: 4 additions & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ SIMPLE_DECL_ATTR(nonobjc, NonObjC,
OnExtension | OnFunc | OnVar | OnSubscript | OnConstructor, 30)

SIMPLE_DECL_ATTR(_fixed_layout, FixedLayout,
OnVar | OnClass | OnStruct | OnEnum | UserInaccessible, 31)
OnVar | OnClass | OnStruct | UserInaccessible, 31)

SIMPLE_DECL_ATTR(_inlineable, Inlineable,
OnVar | OnSubscript | OnFunc | OnConstructor | OnDestructor |
Expand Down Expand Up @@ -307,6 +307,9 @@ DECL_ATTR(_clangImporterSynthesizedType, ClangImporterSynthesizedType,
LongAttribute | NotSerialized | RejectByParser | UserInaccessible,
/*Not serialized*/74)

SIMPLE_DECL_ATTR(frozen, Frozen, OnEnum, 75)
SIMPLE_DECL_ATTR(_nonfrozen, NonFrozen, OnEnum | UserInaccessible, 76)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef SIMPLE_DECL_ATTR
Expand Down
9 changes: 9 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,8 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
///
/// This is used for diagnostics, because we do not want a behavior
/// change between builds with resilience enabled and disabled.
/// For enums, this means clients can assume the representation will not
/// change, although more cases may be added within that representation.
bool isFormallyResilient() const;

/// \brief Do we need to use resilient access patterns outside of this type's
Expand Down Expand Up @@ -3167,6 +3169,13 @@ class EnumDecl final : public NominalTypeDecl {
bool isIndirect() const {
return getAttrs().hasAttribute<IndirectAttr>();
}

/// True if the enum can be exhaustively switched within \p useDC.
///
/// Note that this property is \e not necessarily true for all children of
/// \p useDC. In particular, an inlinable function does not get to switch
/// exhaustively over a non-frozen enum declared in the same module.
bool isExhaustive(const DeclContext *useDC) const;
};

/// StructDecl - This is the declaration of a struct, for example:
Expand Down
6 changes: 5 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1200,11 +1200,15 @@ WARNING(expr_dynamic_lookup_swift3_objc_inference,none,
ERROR(alignment_not_power_of_two,none,
"alignment value must be a power of two", ())

// Indirect enums
// Enum annotations
ERROR(indirect_case_without_payload,none,
"enum case %0 without associated value cannot be 'indirect'", (Identifier))
ERROR(indirect_case_in_indirect_enum,none,
"enum case in 'indirect' enum cannot also be 'indirect'", ())
ERROR(enum_frozen_and_nonfrozen,none,
"enum cannot be both '@frozen' and '@_nonfrozen'", ())
WARNING(enum_frozen_nonpublic,none,
"%0 has no effect on non-public enums", (DeclAttribute))

// Variables (var and let).
ERROR(unimplemented_static_var,none,
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -757,9 +757,9 @@ namespace {

if (NTD->hasInterfaceType()) {
if (NTD->isResilient())
OS << " @_resilient_layout";
OS << " resilient";
else
OS << " @_fixed_layout";
OS << " non-resilient";
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2680,6 +2680,18 @@ class Verifier : public ASTWalker {
verifyCheckedBase(PD);
}

void verifyChecked(EnumDecl *ED) {
if (ED->getFormalAccess() >= AccessLevel::Public) {
if (!ED->getAttrs().hasAttribute<FrozenAttr>() &&
!ED->getAttrs().hasAttribute<NonFrozenAttr>()) {
Out << "Public enum is neither '@frozen' nor '@_nonfrozen'\n";
ED->dumpContext();
abort();
}
}
verifyCheckedBase(ED);
}

void verifyChecked(ConstructorDecl *CD) {
PrettyStackTraceDecl debugStack("verifying ConstructorDecl", CD);

Expand Down
38 changes: 36 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2279,9 +2279,11 @@ bool NominalTypeDecl::isFormallyResilient() const {
/*respectVersionedAttr=*/true).isPublic())
return false;

// Check for an explicit @_fixed_layout attribute.
if (getAttrs().hasAttribute<FixedLayoutAttr>())
// Check for an explicit @_fixed_layout or @frozen attribute.
if (getAttrs().hasAttribute<FixedLayoutAttr>() ||
getAttrs().hasAttribute<FrozenAttr>()) {
return false;
}

// Structs and enums imported from C *always* have a fixed layout.
// We know their size, and pass them as values in SIL and IRGen.
Expand Down Expand Up @@ -3067,6 +3069,38 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
return true;
}

bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
// Enums explicitly marked @frozen are exhaustive.
if (getAttrs().hasAttribute<FrozenAttr>())
return true;

// Non-public, non-versioned enums are always exhaustive.
AccessScope accessScope = getFormalAccessScope(/*useDC*/nullptr,
/*respectVersioned*/true);
if (!accessScope.isPublic())
return true;

// Using from an arbitrary module must assume the enum is non-exhaustive at
// this point.
if (!useDC)
return false;

// Enums in the same module are exhaustive /unless/ the use site is inlinable.
const ModuleDecl *containingModule = getModuleContext();
if (useDC->getResilienceExpansion() == ResilienceExpansion::Maximal)
if (useDC->getParentModule() == containingModule)
return true;

// Testably imported enums are exhaustive, on the grounds that only the author
// of the original library can import it testably.
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
if (useSF->hasTestableImport(containingModule))
return true;

// Otherwise, the enum is non-exhaustive.
return false;
}

ProtocolDecl::ProtocolDecl(DeclContext *DC, SourceLoc ProtocolLoc,
SourceLoc NameLoc, Identifier Name,
MutableArrayRef<TypeLoc> Inherited,
Expand Down
24 changes: 19 additions & 5 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2651,7 +2651,8 @@ namespace {
break;
}

case EnumKind::Enum: {
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum: {
auto &C = Impl.SwiftContext;
EnumDecl *nativeDecl;
bool declaredNative = hasNativeSwiftDecl(decl, name, dc, nativeDecl);
Expand Down Expand Up @@ -2755,6 +2756,15 @@ namespace {
Impl.importSourceLoc(decl->getLocation()), None, nullptr, enumDC);
enumDecl->computeType();

// Annotate as '@frozen' or '@_nonfrozen' as appropriate.
if (enumKind == EnumKind::FrozenEnum) {
enumDecl->getAttrs().add(new (C) FrozenAttr(/*implicit*/false));
} else {
bool nonFrozenIsDefault = C.isSwiftVersionAtLeast(5);
enumDecl->getAttrs().add(
new (C) NonFrozenAttr(/*implicit*/nonFrozenIsDefault));
}

// Set up the C underlying type as its Swift raw type.
enumDecl->setRawType(underlyingType);

Expand Down Expand Up @@ -2858,7 +2868,8 @@ namespace {
addEnumeratorsAsMembers = false;
break;
case EnumKind::Options:
case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
addEnumeratorsAsMembers = true;
break;
}
Expand All @@ -2868,7 +2879,8 @@ namespace {
EnumElementDecl *>, 8,
APSIntRefDenseMapInfo> canonicalEnumConstants;

if (enumKind == EnumKind::Enum) {
if (enumKind == EnumKind::NonFrozenEnum ||
enumKind == EnumKind::FrozenEnum) {
for (auto constant : decl->enumerators()) {
if (Impl.isUnavailableInSwift(constant))
continue;
Expand Down Expand Up @@ -2929,7 +2941,8 @@ namespace {
return true;
});
break;
case EnumKind::Enum: {
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum: {
auto canonicalCaseIter =
canonicalEnumConstants.find(&constant->getInitVal());

Expand Down Expand Up @@ -3358,7 +3371,8 @@ namespace {
return result;
}

case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
case EnumKind::Options: {
// The enumeration was mapped to a high-level Swift type, and its
// elements were created as children of that enum. They aren't available
Expand Down
48 changes: 38 additions & 10 deletions lib/ClangImporter/ImportEnumInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@ STATISTIC(EnumInfoNumCacheMisses, "# of times the enum info cache was missed");
using namespace swift;
using namespace importer;

/// Find the last extensibility attribute on \p decl as arranged by source
/// location...unless there's an API note, in which case that one wins.
///
/// This is not what Clang will do, but it's more useful for us since CF_ENUM
/// already has enum_extensibility(open) in it.
static clang::EnumExtensibilityAttr *
getBestExtensibilityAttr(clang::Preprocessor &pp, const clang::EnumDecl *decl) {
clang::EnumExtensibilityAttr *bestSoFar = nullptr;
const clang::SourceManager &sourceMgr = pp.getSourceManager();
for (auto *next : decl->specific_attrs<clang::EnumExtensibilityAttr>()) {
if (next->getLocation().isInvalid()) {
// This is from API notes -- use it!
return next;
}

if (!bestSoFar ||
sourceMgr.isBeforeInTranslationUnit(bestSoFar->getLocation(),
next->getLocation())) {
bestSoFar = next;
}
}
return bestSoFar;
}

/// Classify the given Clang enumeration to describe how to import it.
void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
clang::Preprocessor &pp) {
Expand All @@ -49,21 +73,24 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
return;
}

// First, check for attributes that denote the classification
// First, check for attributes that denote the classification.
if (auto domainAttr = decl->getAttr<clang::NSErrorDomainAttr>()) {
kind = EnumKind::Enum;
kind = EnumKind::NonFrozenEnum;
nsErrorDomain = domainAttr->getErrorDomain()->getName();
return;
}
if (decl->hasAttr<clang::FlagEnumAttr>()) {
kind = EnumKind::Options;
return;
}
if (decl->hasAttr<clang::EnumExtensibilityAttr>()) {
// FIXME: Distinguish between open and closed enums.
kind = EnumKind::Enum;
if (auto *attr = getBestExtensibilityAttr(pp, decl)) {
if (attr->getExtensibility() == clang::EnumExtensibilityAttr::Closed)
kind = EnumKind::FrozenEnum;
else
kind = EnumKind::NonFrozenEnum;
return;
}
if (!nsErrorDomain.empty())
return;

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

// Was the enum declared using *_ENUM or *_OPTIONS?
// FIXME: Stop using these once flag_enum and enum_extensibility
// have been adopted everywhere, or at least relegate them to Swift 3 mode
// have been adopted everywhere, or at least relegate them to Swift 4 mode
// only.
auto loc = decl->getLocStart();
if (loc.isMacroID()) {
StringRef MacroName = pp.getImmediateMacroName(loc);
if (MacroName == "CF_ENUM" || MacroName == "__CF_NAMED_ENUM" ||
MacroName == "OBJC_ENUM" || MacroName == "SWIFT_ENUM" ||
MacroName == "SWIFT_ENUM_NAMED") {
kind = EnumKind::Enum;
kind = EnumKind::NonFrozenEnum;
return;
}
if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" ||
Expand All @@ -99,7 +126,7 @@ void EnumInfo::classifyEnum(const clang::EnumDecl *decl,

// Hardcode a particular annoying case in the OS X headers.
if (decl->getName() == "DYLD_BOOL") {
kind = EnumKind::Enum;
kind = EnumKind::FrozenEnum;
return;
}

Expand Down Expand Up @@ -205,7 +232,8 @@ StringRef importer::getCommonPluralPrefix(StringRef singular,
/// within the given enum.
void EnumInfo::determineConstantNamePrefix(const clang::EnumDecl *decl) {
switch (getKind()) {
case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
case EnumKind::Options:
// Enums are mapped to Swift enums, Options to Swift option sets, both
// of which attempt prefix-stripping.
Expand Down
23 changes: 17 additions & 6 deletions lib/ClangImporter/ImportEnumInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ namespace importer {
/// into Swift. All of the possibilities have the same storage
/// representation, but can be used in different ways.
enum class EnumKind {
/// The enumeration type should map to an enum, which means that
/// all of the cases are independent.
Enum,
/// The enumeration type should map to an option set, which means
/// that
/// The enumeration type should map to a frozen enum, which means that
/// all of the cases are independent and there are no private cases.
FrozenEnum,
/// The enumeration type should map to a non-frozen enum, which means that
/// all of the cases are independent, but there may be values not represented
/// in the listed cases.
NonFrozenEnum,
/// The enumeration type should map to an option set, which means that
/// the constants represent combinations of independent flags.
Options,
/// The enumeration type should map to a distinct type, but we don't
Expand Down Expand Up @@ -76,7 +79,15 @@ class EnumInfo {

/// Whether this maps to an enum who also provides an error domain
bool isErrorEnum() const {
return getKind() == EnumKind::Enum && !nsErrorDomain.empty();
switch (getKind()) {
case EnumKind::FrozenEnum:
case EnumKind::NonFrozenEnum:
return !nsErrorDomain.empty();
case EnumKind::Options:
case EnumKind::Unknown:
case EnumKind::Constants:
return false;
}
}

/// For this error enum, extract the name of the error domain constant
Expand Down
6 changes: 4 additions & 2 deletions lib/ClangImporter/ImportName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,8 @@ NameImporter::determineEffectiveContext(const clang::NamedDecl *decl,
if (isa<clang::EnumConstantDecl>(decl)) {
auto enumDecl = cast<clang::EnumDecl>(dc);
switch (getEnumKind(enumDecl)) {
case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
case EnumKind::Options:
// Enums are mapped to Swift enums, Options to Swift option sets.
if (version != ImportNameVersion::raw()) {
Expand Down Expand Up @@ -1005,7 +1006,8 @@ static bool shouldBeSwiftPrivate(NameImporter &nameImporter,
if (auto *ECD = dyn_cast<clang::EnumConstantDecl>(decl)) {
auto *ED = cast<clang::EnumDecl>(ECD->getDeclContext());
switch (nameImporter.getEnumKind(ED)) {
case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
case EnumKind::Options:
if (version != ImportNameVersion::raw())
break;
Expand Down
3 changes: 2 additions & 1 deletion lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,8 @@ namespace {
// Import the underlying integer type.
return Visit(clangDecl->getIntegerType());
}
case EnumKind::Enum:
case EnumKind::NonFrozenEnum:
case EnumKind::FrozenEnum:
case EnumKind::Unknown:
case EnumKind::Options: {
auto decl = dyn_cast_or_null<TypeDecl>(
Expand Down
3 changes: 2 additions & 1 deletion lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3664,7 +3664,8 @@ IRGenModule::getAddrOfGlobalUTF16ConstantString(StringRef utf8) {
///
/// IRGen is primarily concerned with resilient handling of the following:
/// - For structs, a struct's size might change
/// - For enums, new cases can be added
/// - For enums, new cases can be added that would change the representation.
/// (Note that enums with known representation can still grow new cases.)
/// - For classes, the superclass might change the size or number
/// of stored properties
bool IRGenModule::isResilient(NominalTypeDecl *D, ResilienceExpansion expansion) {
Expand Down
Loading