Skip to content

[AST/Sema] SE-0487: Adjust implementation based on the LSG feedback #82807

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

Merged
merged 3 commits into from
Jul 8, 2025
Merged
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
39 changes: 39 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ enum : unsigned {
InheritActorContextModifier::Last_InheritActorContextKind))
};

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

enum : unsigned { NumDeclAttrKindBits = countBitsUsed(NumDeclAttrKinds - 1) };

enum : unsigned { NumTypeAttrKindBits = countBitsUsed(NumTypeAttrKinds - 1) };
Expand Down Expand Up @@ -277,6 +282,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 @@ -3500,6 +3509,36 @@ 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();
}
};


/// The kind of unary operator, if any.
enum class UnaryOperatorKind : uint8_t { None, Prefix, Postfix };

Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ enum class ENUM_EXTENSIBILITY_ATTR(closed)
Last_InheritActorContextKind = Always
};

enum class ENUM_EXTENSIBILITY_ATTR(closed) NonexhaustiveMode : uint8_t {
Error SWIFT_NAME("error") = 0,
Warning SWIFT_NAME("warning") = 1,
Last_NonexhaustiveMode = Warning
};

enum class ENUM_EXTENSIBILITY_ATTR(closed) DeclAttrKind : unsigned {
#define DECL_ATTR(_, CLASS, ...) CLASS,
#define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS,
Expand Down
10 changes: 3 additions & 7 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -877,22 +877,18 @@ SIMPLE_DECL_ATTR(constInitialized, ConstInitialized,
168)
DECL_ATTR_FEATURE_REQUIREMENT(ConstInitialized, CompileTimeValues)

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

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

SIMPLE_DECL_ATTR(preEnumExtensibility, PreEnumExtensibility,
OnEnum,
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove | UnconstrainedInABIAttr,
171)
DECL_ATTR_FEATURE_REQUIREMENT(PreEnumExtensibility, ExtensibleAttribute)
// Unused '171': Used to be `@preEnumExtensibility`

DECL_ATTR(specialized, Specialized,
OnConstructor | OnFunc | OnAccessor,
Expand Down
13 changes: 5 additions & 8 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8799,21 +8799,18 @@ GROUPED_WARNING(
(StringRef))

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

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

ERROR(extensible_attr_on_internal_type,none,
"'@extensible' attribute can only be applied to public or package "
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))

ERROR(pre_enum_extensibility_without_extensible,none,
"%0 can only be used together with '@extensible' attribute", (DeclAttribute))

//===----------------------------------------------------------------------===//
// 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
4 changes: 2 additions & 2 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ EXPERIMENTAL_FEATURE(AllowRuntimeSymbolDeclarations, true)
/// Allow use of `@cdecl`
EXPERIMENTAL_FEATURE(CDecl, false)

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

/// Allow use of `Module::name` syntax
EXPERIMENTAL_FEATURE(ModuleSelector, 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
3 changes: 1 addition & 2 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5047,9 +5047,8 @@ 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(Extensible, extensible)
TRIVIAL_ATTR_PRINTER(Nonexhaustive, nonexhaustive)
TRIVIAL_ATTR_PRINTER(Concurrent, concurrent)
TRIVIAL_ATTR_PRINTER(PreEnumExtensibility, preEnumExtensibility)

#undef TRIVIAL_ATTR_PRINTER

Expand Down
7 changes: 3 additions & 4 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3325,10 +3325,9 @@ suppressingFeatureAddressableTypes(PrintOptions &options,
}

static void
suppressingFeatureExtensibleAttribute(PrintOptions &options,
llvm::function_ref<void()> action) {
ExcludeAttrRAII scope1(options.ExcludeAttrList, DeclAttrKind::Extensible);
ExcludeAttrRAII scope2(options.ExcludeAttrList, DeclAttrKind::PreEnumExtensibility);
suppressingFeatureNonexhaustiveAttribute(PrintOptions &options,
llvm::function_ref<void()> action) {
ExcludeAttrRAII scope(options.ExcludeAttrList, DeclAttrKind::Nonexhaustive);
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 @@ -1732,6 +1732,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 @@ -1967,6 +1980,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
4 changes: 2 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7035,9 +7035,9 @@ bool EnumDecl::treatAsExhaustiveForDiags(const DeclContext *useDC) const {
if (enumModule->inSamePackage(useDC->getParentModule()))
return true;

// When the enum is marked as `@extensible` cross-module access
// When the enum is marked as `@nonexhaustive` cross-module access
// cannot be exhaustive and requires `@unknown default:`.
if (getAttrs().hasAttribute<ExtensibleAttr>() &&
if (getAttrs().hasAttribute<NonexhaustiveAttr>() &&
enumModule != useDC->getParentModule())
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,8 @@ static bool usesFeatureAsyncExecutionBehaviorAttributes(Decl *decl) {
return false;
}

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

static bool usesFeatureAlwaysInheritActorContext(Decl *decl) {
Expand Down
3 changes: 1 addition & 2 deletions lib/ASTGen/Sources/ASTGen/DeclAttrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,7 @@ extension ASTGenVisitor {
.DynamicCallable,
.EagerMove,
.Exported,
.Extensible,
.PreEnumExtensibility,
.Nonexhaustive,
.DiscardableResult,
.DisfavoredOverload,
.DynamicMemberLookup,
Expand Down
5 changes: 5 additions & 0 deletions lib/IDE/CompletionLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3278,6 +3278,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 @@ -4033,6 +4033,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
14 changes: 3 additions & 11 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,29 +233,21 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
}
}

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

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

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

void visitPreEnumExtensibilityAttr(PreEnumExtensibilityAttr *attr) {
if (!D->getAttrs().hasAttribute<ExtensibleAttr>()) {
diagnoseAndRemoveAttr(
attr, diag::pre_enum_extensibility_without_extensible, attr);
return;
}
}

void visitConcurrentAttr(ConcurrentAttr *attr) {
checkExecutionBehaviorAttribute(attr);

Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1614,8 +1614,7 @@ namespace {
UNINTERESTING_ATTR(Isolated)
UNINTERESTING_ATTR(Optimize)
UNINTERESTING_ATTR(Exclusivity)
UNINTERESTING_ATTR(Extensible)
UNINTERESTING_ATTR(PreEnumExtensibility)
UNINTERESTING_ATTR(Nonexhaustive)
UNINTERESTING_ATTR(NoLocks)
UNINTERESTING_ATTR(NoAllocation)
UNINTERESTING_ATTR(NoRuntime)
Expand Down
24 changes: 15 additions & 9 deletions lib/Sema/TypeCheckSwitchStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1160,21 +1160,27 @@ namespace {
auto *enumModule = theEnum->getParentModule();
shouldIncludeFutureVersionComment =
enumModule->isSystemModule() ||
theEnum->getAttrs().hasAttribute<ExtensibleAttr>();
theEnum->getAttrs().hasAttribute<NonexhaustiveAttr>();
}

auto diag =
DE.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only,
subjectType, shouldIncludeFutureVersionComment);

// Presence of `@preEnumExtensibility` pushed the warning farther
// into the future.
if (theEnum &&
theEnum->getAttrs().hasAttribute<PreEnumExtensibilityAttr>()) {
diag.warnUntilFutureSwiftVersion();
} else {
diag.warnUntilSwiftVersion(6);
}
auto shouldWarnUntilVersion = [&theEnum]() -> unsigned {
if (theEnum) {
// Presence of `@nonexhaustive(warn)` pushes the warning farther,
// into the future.
if (auto *nonexhaustive =
theEnum->getAttrs().getAttribute<NonexhaustiveAttr>()) {
if (nonexhaustive->getMode() == NonexhaustiveMode::Warning)
return swift::version::Version::getFutureMajorLanguageVersion();
}
}
return 6;
};

diag.warnUntilSwiftVersion(shouldWarnUntilVersion());

mainDiagType = std::nullopt;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6676,6 +6676,14 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
break;
}

case decls_block::Nonexhaustive_DECL_ATTR: {
unsigned mode;
serialization::decls_block::NonexhaustiveDeclAttrLayout::readRecord(
scratch, mode);
Attr = new (ctx) NonexhaustiveAttr((NonexhaustiveMode)mode);
break;
}

#define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \
case decls_block::CLASS##_DECL_ATTR: { \
bool isImplicit; \
Expand Down
5 changes: 5 additions & 0 deletions lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -2591,6 +2591,11 @@ namespace decls_block {
BCArray<BCFixed<1>> // concatenated param indices
>;

using NonexhaustiveDeclAttrLayout = BCRecordLayout<
Nonexhaustive_DECL_ATTR,
BCFixed<2> // mode
>;

// clang-format on

#undef SYNTAX_SUGAR_TYPE_LAYOUT
Expand Down
7 changes: 7 additions & 0 deletions lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3548,6 +3548,13 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
case DeclAttrKind::Lifetime: {
return;
}
case DeclAttrKind::Nonexhaustive: {
auto *theAttr = cast<NonexhaustiveAttr>(DA);
auto abbrCode = S.DeclTypeAbbrCodes[NonexhaustiveDeclAttrLayout::Code];
NonexhaustiveDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
(unsigned)theAttr->getMode());
return;
}
}
}

Expand Down
Loading