Skip to content

[6.2][AST/Sema] SE-0487: Implement @nonexhaustive attribute and new enum exhaustivity checking rule #82922

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

Draft
wants to merge 8 commits into
base: release/6.2
Choose a base branch
from
Draft
33 changes: 33 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ class DeclAttribute : public AttributeBase {
SWIFT_INLINE_BITFIELD(LifetimeAttr, DeclAttribute, 1,
isUnderscored : 1
);

SWIFT_INLINE_BITFIELD(NonexhaustiveAttr, DeclAttribute, NumNonexhaustiveModeBits,
mode : NumNonexhaustiveModeBits
);
} Bits;
// clang-format on

Expand Down Expand Up @@ -3334,6 +3338,35 @@ class ABIAttr : public DeclAttribute {
}
};

/// Defines a @nonexhaustive attribute.
class NonexhaustiveAttr : public DeclAttribute {
public:
NonexhaustiveAttr(SourceLoc atLoc, SourceRange range, NonexhaustiveMode mode,
bool implicit = false)
: DeclAttribute(DeclAttrKind::Nonexhaustive, atLoc, range, implicit) {
Bits.NonexhaustiveAttr.mode = unsigned(mode);
}

NonexhaustiveAttr(NonexhaustiveMode mode)
: NonexhaustiveAttr(SourceLoc(), SourceRange(), mode) {}

NonexhaustiveMode getMode() const {
return NonexhaustiveMode(Bits.NonexhaustiveAttr.mode);
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::Nonexhaustive;
}

NonexhaustiveAttr *clone(ASTContext &ctx) const {
return new (ctx) NonexhaustiveAttr(AtLoc, Range, getMode(), isImplicit());
}

bool isEquivalent(const NonexhaustiveAttr *other, Decl *attachedTo) const {
return getMode() == other->getMode();
}
};

/// Attributes that may be applied to declarations.
class DeclAttributes {
/// Linked list of declaration attributes.
Expand Down
11 changes: 11 additions & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ enum : unsigned {
InheritActorContextModifier::Last_InheritActorContextKind))
};

enum class NonexhaustiveMode : uint8_t {
Error = 0,
Warning,
Last_NonexhaustiveMode = Warning
};

enum : unsigned {
NumNonexhaustiveModeBits = countBitsUsed(static_cast<unsigned>(
NonexhaustiveMode::Last_NonexhaustiveMode))
};

enum class DeclAttrKind : unsigned {
#define DECL_ATTR(_, CLASS, ...) CLASS,
#define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS,
Expand Down
7 changes: 2 additions & 5 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
HasAnyUnavailableDuringLoweringValues : 1
);

SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+8,
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+8,
/// If the module is compiled as static library.
StaticLibrary : 1,

Expand Down Expand Up @@ -820,10 +820,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
SerializePackageEnabled : 1,

/// Whether this module has enabled strict memory safety checking.
StrictMemorySafety : 1,

/// Whether this module has enabled `ExtensibleEnums` feature.
ExtensibleEnums : 1
StrictMemorySafety : 1
);

SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,
Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -876,11 +876,19 @@ SIMPLE_DECL_ATTR(constInitialized, ConstInitialized,
168)
DECL_ATTR_FEATURE_REQUIREMENT(ConstInitialized, CompileTimeValues)

DECL_ATTR(nonexhaustive, Nonexhaustive,
OnEnum,
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | ForbiddenInABIAttr,
169)
DECL_ATTR_FEATURE_REQUIREMENT(Nonexhaustive, NonexhaustiveAttribute)

SIMPLE_DECL_ATTR(concurrent, Concurrent,
OnFunc | OnConstructor | OnSubscript | OnVar,
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UnconstrainedInABIAttr,
170)

// Unused '171': Used to be `@preEnumExtensibility`

LAST_DECL_ATTR(Concurrent)

#undef DECL_ATTR_ALIAS
Expand Down
13 changes: 13 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8711,6 +8711,19 @@ GROUPED_WARNING(
"behavior",
(StringRef))

//===----------------------------------------------------------------------===//
// MARK: @nonexhaustive and @preEnumExtensibility Attributes
//===----------------------------------------------------------------------===//

ERROR(nonexhaustive_attr_on_frozen_type,none,
"cannot use '@nonexhaustive' together with '@frozen'", ())

ERROR(nonexhaustive_attr_on_internal_type,none,
"'@nonexhaustive' attribute can only be applied to public or package "
"declarations, but %0 is "
"%select{private|fileprivate|internal|%error|%error|%error}1",
(DeclName, AccessLevel))

//===----------------------------------------------------------------------===//
// MARK: `using` declaration
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ IDENTIFIER(value)
IDENTIFIER_WITH_NAME(value_, "_value")
IDENTIFIER(Void)
IDENTIFIER(WinSDK)
IDENTIFIER(warn)
IDENTIFIER(with)
IDENTIFIER(withArguments)
IDENTIFIER(withKeywordArguments)
Expand Down
8 changes: 0 additions & 8 deletions include/swift/AST/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -835,14 +835,6 @@ class ModuleDecl
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
}

bool supportsExtensibleEnums() const {
return Bits.ModuleDecl.ExtensibleEnums;
}

void setSupportsExtensibleEnums(bool value = true) {
Bits.ModuleDecl.ExtensibleEnums = value;
}

/// For the main module, retrieves the list of primary source files being
/// compiled, that is, the files we're generating code for.
ArrayRef<SourceFile *> getPrimarySourceFiles() const;
Expand Down
8 changes: 3 additions & 5 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -500,11 +500,6 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(AddressableTypes, true)
/// Allow custom availability domains to be defined and referenced.
EXPERIMENTAL_FEATURE(CustomAvailability, true)

/// Allow public enumerations to be extensible by default
/// regardless of whether the module they are declared in
/// is resilient or not.
EXPERIMENTAL_FEATURE(ExtensibleEnums, true)

/// Syntax sugar features for concurrency.
EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true)

Expand All @@ -523,6 +518,9 @@ EXPERIMENTAL_FEATURE(AllowRuntimeSymbolDeclarations, true)
/// Optimize copies of ObjectiveC blocks.
EXPERIMENTAL_FEATURE(CopyBlockOptimization, true)

/// Allow use of `@nonexhaustive` on public enums
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(NonexhaustiveAttribute, false)

/// Allow use of `using` declaration that control default isolation
/// in a file scope.
EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false)
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Parse/IDEInspectionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ enum class ParameterizedDeclAttributeKind {
FreestandingMacro,
AttachedMacro,
StorageRestrictions,
InheritActorContext
InheritActorContext,
Nonexhaustive,
};

/// A bit of a hack. When completing inside the '@storageRestrictions'
Expand Down
6 changes: 0 additions & 6 deletions include/swift/Serialization/Validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ class ExtendedValidationInfo {
unsigned AllowNonResilientAccess: 1;
unsigned SerializePackageEnabled: 1;
unsigned StrictMemorySafety: 1;
unsigned SupportsExtensibleEnums : 1;
} Bits;

public:
Expand Down Expand Up @@ -272,11 +271,6 @@ class ExtendedValidationInfo {
version, SourceLoc(), /*Diags=*/nullptr))
SwiftInterfaceCompilerVersion = genericVersion.value();
}

bool supportsExtensibleEnums() const { return Bits.SupportsExtensibleEnums; }
void setSupportsExtensibleEnums(bool val) {
Bits.SupportsExtensibleEnums = val;
}
};

struct SearchPath {
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5064,6 +5064,7 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
TRIVIAL_ATTR_PRINTER(Used, used)
TRIVIAL_ATTR_PRINTER(WarnUnqualifiedAccess, warn_unqualified_access)
TRIVIAL_ATTR_PRINTER(WeakLinked, weak_linked)
TRIVIAL_ATTR_PRINTER(Nonexhaustive, nonexhaustive)
TRIVIAL_ATTR_PRINTER(Concurrent, concurrent)

#undef TRIVIAL_ATTR_PRINTER
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3325,6 +3325,13 @@ suppressingFeatureAddressableTypes(PrintOptions &options,
action();
}

static void
suppressingFeatureNonexhaustiveAttribute(PrintOptions &options,
llvm::function_ref<void()> action) {
ExcludeAttrRAII scope(options.ExcludeAttrList, DeclAttrKind::Nonexhaustive);
action();
}

/// Suppress the printing of a particular feature.
static void suppressingFeature(PrintOptions &options, Feature feature,
llvm::function_ref<void()> action) {
Expand Down
15 changes: 15 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,19 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
break;
}

case DeclAttrKind::Nonexhaustive: {
auto *attr = cast<NonexhaustiveAttr>(this);
Printer << "@nonexhaustive";
switch (attr->getMode()) {
case NonexhaustiveMode::Error:
break;
case NonexhaustiveMode::Warning:
Printer << "(warn)";
break;
}
break;
}

#define SIMPLE_DECL_ATTR(X, CLASS, ...) case DeclAttrKind::CLASS:
#include "swift/AST/DeclAttr.def"
llvm_unreachable("handled above");
Expand Down Expand Up @@ -1956,6 +1969,8 @@ StringRef DeclAttribute::getAttrName() const {
}
case DeclAttrKind::Lifetime:
return cast<LifetimeAttr>(this)->isUnderscored() ? "_lifetime" : "lifetime";
case DeclAttrKind::Nonexhaustive:
return "nonexhaustive";
}
llvm_unreachable("bad DeclAttrKind");
}
Expand Down
13 changes: 5 additions & 8 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7013,14 +7013,11 @@ bool EnumDecl::treatAsExhaustiveForDiags(const DeclContext *useDC) const {
if (enumModule->inSamePackage(useDC->getParentModule()))
return true;

// If the module where enum is declared supports extensible enumerations
// and this enum is not explicitly marked as "@frozen", cross-module
// access cannot be exhaustive and requires `@unknown default:`.
if (enumModule->supportsExtensibleEnums() &&
!getAttrs().hasAttribute<FrozenAttr>()) {
if (useDC != enumModule->getDeclContext())
return false;
}
// When the enum is marked as `@nonexhaustive` cross-module access
// cannot be exhaustive and requires `@unknown default:`.
if (getAttrs().hasAttribute<NonexhaustiveAttr>() &&
enumModule != useDC->getParentModule())
return false;
}

return isFormallyExhaustive(useDC);
Expand Down
5 changes: 4 additions & 1 deletion lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ UNINTERESTING_FEATURE(Volatile)
UNINTERESTING_FEATURE(SuppressedAssociatedTypes)
UNINTERESTING_FEATURE(StructLetDestructuring)
UNINTERESTING_FEATURE(MacrosOnImports)
UNINTERESTING_FEATURE(ExtensibleEnums)
UNINTERESTING_FEATURE(NonisolatedNonsendingByDefault)
UNINTERESTING_FEATURE(KeyPathWithMethodMembers)
UNINTERESTING_FEATURE(SendableProhibitsMainActorInference)
Expand Down Expand Up @@ -657,6 +656,10 @@ static bool usesFeatureAsyncExecutionBehaviorAttributes(Decl *decl) {
return false;
}

static bool usesFeatureNonexhaustiveAttribute(Decl *decl) {
return decl->getAttrs().hasAttribute<NonexhaustiveAttr>();
}

UNINTERESTING_FEATURE(BuiltinSelect)

static bool usesFeatureAlwaysInheritActorContext(Decl *decl) {
Expand Down
1 change: 1 addition & 0 deletions lib/ASTGen/Sources/ASTGen/DeclAttrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ extension ASTGenVisitor {
.dynamicCallable,
.eagerMove,
.exported,
.nonexhaustive,
.discardableResult,
.disfavoredOverload,
.dynamicMemberLookup,
Expand Down
2 changes: 0 additions & 2 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1501,8 +1501,6 @@ ModuleDecl *CompilerInstance::getMainModule() const {
MainModule->setSerializePackageEnabled();
if (Invocation.getLangOptions().hasFeature(Feature::StrictMemorySafety))
MainModule->setStrictMemorySafety(true);
if (Invocation.getLangOptions().hasFeature(Feature::ExtensibleEnums))
MainModule->setSupportsExtensibleEnums(true);

configureAvailabilityDomains(getASTContext(),
Invocation.getFrontendOptions(), MainModule);
Expand Down
5 changes: 5 additions & 0 deletions lib/IDE/CompletionLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3273,6 +3273,11 @@ void CompletionLookup::getAttributeDeclParamCompletions(
getStoredPropertyCompletions(NT);
}
}
break;
}
case ParameterizedDeclAttributeKind::Nonexhaustive: {
addDeclAttrParamKeyword("warn", /*Parameters=*/{}, "", false);
break;
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3970,6 +3970,21 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
Attributes.add(Attr.get());
break;
}

case DeclAttrKind::Nonexhaustive: {
auto mode = parseSingleAttrOption<NonexhaustiveMode>(
*this, Loc, AttrRange, AttrName, DK,
{{Context.Id_warn, NonexhaustiveMode::Warning}},
NonexhaustiveMode::Error,
ParameterizedDeclAttributeKind::Nonexhaustive);
if (!mode)
return makeParserSuccess();

if (!DiscardAttribute)
Attributes.add(new (Context) NonexhaustiveAttr(AtLoc, AttrRange, *mode));

break;
}
}

if (DuplicateAttribute) {
Expand Down
15 changes: 15 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
}
}

void visitNonexhaustiveAttr(NonexhaustiveAttr *attr) {
auto *E = cast<EnumDecl>(D);

if (D->getAttrs().hasAttribute<FrozenAttr>()) {
diagnoseAndRemoveAttr(attr, diag::nonexhaustive_attr_on_frozen_type);
return;
}

if (E->getFormalAccess() < AccessLevel::Package) {
diagnoseAndRemoveAttr(attr, diag::nonexhaustive_attr_on_internal_type,
E->getName(), E->getFormalAccess());
return;
}
}

void visitAlignmentAttr(AlignmentAttr *attr) {
// Alignment must be a power of two.
auto value = attr->getValue();
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1616,6 +1616,7 @@ namespace {
UNINTERESTING_ATTR(Isolated)
UNINTERESTING_ATTR(Optimize)
UNINTERESTING_ATTR(Exclusivity)
UNINTERESTING_ATTR(Nonexhaustive)
UNINTERESTING_ATTR(NoLocks)
UNINTERESTING_ATTR(NoAllocation)
UNINTERESTING_ATTR(NoRuntime)
Expand Down
Loading