Skip to content

Commit c3b75ee

Browse files
authored
Merge pull request #79580 from xedin/extensible-enums
[Serialization/TypeChecker] Introduce `ExtensibleEnums` feature
2 parents 4c0e141 + b84bf05 commit c3b75ee

File tree

15 files changed

+190
-8
lines changed

15 files changed

+190
-8
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
736736
HasAnyUnavailableDuringLoweringValues : 1
737737
);
738738

739-
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,
739+
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,
740740
/// If the module is compiled as static library.
741741
StaticLibrary : 1,
742742

@@ -805,7 +805,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
805805
SerializePackageEnabled : 1,
806806

807807
/// Whether this module has enabled strict memory safety checking.
808-
StrictMemorySafety : 1
808+
StrictMemorySafety : 1,
809+
810+
/// Whether this module has enabled `ExtensibleEnums` feature.
811+
ExtensibleEnums : 1
809812
);
810813

811814
SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,

include/swift/AST/Module.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,14 @@ class ModuleDecl
840840
Bits.ModuleDecl.ObjCNameLookupCachePopulated = value;
841841
}
842842

843+
bool supportsExtensibleEnums() const {
844+
return Bits.ModuleDecl.ExtensibleEnums;
845+
}
846+
847+
void setSupportsExtensibleEnums(bool value = true) {
848+
Bits.ModuleDecl.ExtensibleEnums = value;
849+
}
850+
843851
/// For the main module, retrieves the list of primary source files being
844852
/// compiled, that is, the files we're generating code for.
845853
ArrayRef<SourceFile *> getPrimarySourceFiles() const;

include/swift/Basic/Features.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,11 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true)
450450
/// Be strict about the Sendable conformance of metatypes.
451451
EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true)
452452

453+
/// Allow public enumerations to be extensible by default
454+
/// regardless of whether the module they are declared in
455+
/// is resilient or not.
456+
EXPERIMENTAL_FEATURE(ExtensibleEnums, true)
457+
453458
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
454459
#undef EXPERIMENTAL_FEATURE
455460
#undef UPCOMING_FEATURE

include/swift/Serialization/Validation.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ class ExtendedValidationInfo {
150150
unsigned AllowNonResilientAccess: 1;
151151
unsigned SerializePackageEnabled: 1;
152152
unsigned StrictMemorySafety: 1;
153+
unsigned SupportsExtensibleEnums : 1;
153154
} Bits;
155+
154156
public:
155157
ExtendedValidationInfo() : Bits() {}
156158

@@ -270,6 +272,11 @@ class ExtendedValidationInfo {
270272
version, SourceLoc(), /*Diags=*/nullptr))
271273
SwiftInterfaceCompilerVersion = genericVersion.value();
272274
}
275+
276+
bool supportsExtensibleEnums() const { return Bits.SupportsExtensibleEnums; }
277+
void setSupportsExtensibleEnums(bool val) {
278+
Bits.SupportsExtensibleEnums = val;
279+
}
273280
};
274281

275282
struct SearchPath {

lib/AST/Decl.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6696,8 +6696,22 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
66966696
}
66976697

66986698
bool EnumDecl::treatAsExhaustiveForDiags(const DeclContext *useDC) const {
6699-
return isFormallyExhaustive(useDC) ||
6700-
(useDC && getModuleContext()->inSamePackage(useDC->getParentModule()));
6699+
if (useDC) {
6700+
auto *enumModule = getModuleContext();
6701+
if (enumModule->inSamePackage(useDC->getParentModule()))
6702+
return true;
6703+
6704+
// If the module where enum is declared supports extensible enumerations
6705+
// and this enum is not explicitly marked as "@frozen", cross-module
6706+
// access cannot be exhaustive and requires `@unknown default:`.
6707+
if (enumModule->supportsExtensibleEnums() &&
6708+
!getAttrs().hasAttribute<FrozenAttr>()) {
6709+
if (useDC != enumModule->getDeclContext())
6710+
return false;
6711+
}
6712+
}
6713+
6714+
return isFormallyExhaustive(useDC);
67016715
}
67026716

67036717
bool EnumDecl::isFormallyExhaustive(const DeclContext *useDC) const {

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ UNINTERESTING_FEATURE(SuppressedAssociatedTypes)
122122
UNINTERESTING_FEATURE(StructLetDestructuring)
123123
UNINTERESTING_FEATURE(MacrosOnImports)
124124
UNINTERESTING_FEATURE(AsyncCallerExecution)
125+
UNINTERESTING_FEATURE(ExtensibleEnums)
125126

126127
static bool usesFeatureNonescapableTypes(Decl *decl) {
127128
auto containsNonEscapable =

lib/Frontend/Frontend.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,8 @@ ModuleDecl *CompilerInstance::getMainModule() const {
14571457
MainModule->setSerializePackageEnabled();
14581458
if (Invocation.getLangOptions().hasFeature(Feature::WarnUnsafe))
14591459
MainModule->setStrictMemorySafety(true);
1460+
if (Invocation.getLangOptions().hasFeature(Feature::ExtensibleEnums))
1461+
MainModule->setSupportsExtensibleEnums(true);
14601462

14611463
configureAvailabilityDomains(getASTContext(),
14621464
Invocation.getFrontendOptions(), MainModule);

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,13 +1154,19 @@ namespace {
11541154
assert(defaultReason == RequiresDefault::No);
11551155
Type subjectType = Switch->getSubjectExpr()->getType();
11561156
bool shouldIncludeFutureVersionComment = false;
1157+
bool shouldDowngradeToWarning = true;
11571158
if (auto *theEnum = subjectType->getEnumOrBoundGenericEnum()) {
1159+
auto *enumModule = theEnum->getParentModule();
11581160
shouldIncludeFutureVersionComment =
1159-
theEnum->getParentModule()->isSystemModule();
1161+
enumModule->isSystemModule() ||
1162+
enumModule->supportsExtensibleEnums();
1163+
// Since the module enabled `ExtensibleEnums` feature they
1164+
// opted-in all of their clients into exhaustivity errors.
1165+
shouldDowngradeToWarning = !enumModule->supportsExtensibleEnums();
11601166
}
11611167
DE.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only,
11621168
subjectType, shouldIncludeFutureVersionComment)
1163-
.warnUntilSwiftVersion(6);
1169+
.warnUntilSwiftVersionIf(shouldDowngradeToWarning, 6);
11641170
mainDiagType = std::nullopt;
11651171
}
11661172
break;

lib/Serialization/ModuleFile.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,11 @@ class ModuleFile
707707
/// \c true if this module was built with strict memory safety.
708708
bool strictMemorySafety() const { return Core->strictMemorySafety(); }
709709

710+
/// \c true if this module was built with `ExtensibleEnums` feature enabled.
711+
bool supportsExtensibleEnums() const {
712+
return Core->supportsExtensibleEnums();
713+
}
714+
710715
/// Associates this module file with the AST node representing it.
711716
///
712717
/// Checks that the file is compatible with the AST module it's being loaded

lib/Serialization/ModuleFileSharedCore.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ static bool readOptionsBlock(llvm::BitstreamCursor &cursor,
224224
case options_block::STRICT_MEMORY_SAFETY:
225225
extendedInfo.setStrictMemorySafety(true);
226226
break;
227+
case options_block::EXTENSIBLE_ENUMS:
228+
extendedInfo.setSupportsExtensibleEnums(true);
229+
break;
227230
default:
228231
// Unknown options record, possibly for use by a future version of the
229232
// module format.
@@ -1501,6 +1504,7 @@ ModuleFileSharedCore::ModuleFileSharedCore(
15011504
Bits.AllowNonResilientAccess = extInfo.allowNonResilientAccess();
15021505
Bits.SerializePackageEnabled = extInfo.serializePackageEnabled();
15031506
Bits.StrictMemorySafety = extInfo.strictMemorySafety();
1507+
Bits.SupportsExtensibleEnums = extInfo.supportsExtensibleEnums();
15041508
MiscVersion = info.miscVersion;
15051509
SDKVersion = info.sdkVersion;
15061510
ModuleABIName = extInfo.getModuleABIName();

lib/Serialization/ModuleFileSharedCore.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,11 @@ class ModuleFileSharedCore {
418418
/// Whether this module enabled strict memory safety.
419419
unsigned StrictMemorySafety : 1;
420420

421+
/// Whether this module enabled has `ExtensibleEnums` feature enabled.
422+
unsigned SupportsExtensibleEnums : 1;
423+
421424
// Explicitly pad out to the next word boundary.
422-
unsigned : 2;
425+
unsigned : 1;
423426
} Bits = {};
424427
static_assert(sizeof(ModuleBits) <= 8, "The bit set should be small");
425428

@@ -678,6 +681,8 @@ class ModuleFileSharedCore {
678681

679682
bool strictMemorySafety() const { return Bits.StrictMemorySafety; }
680683

684+
bool supportsExtensibleEnums() const { return Bits.SupportsExtensibleEnums; }
685+
681686
/// How should \p dependency be loaded for a transitive import via \c this?
682687
///
683688
/// If \p importNonPublicDependencies, more transitive dependencies

lib/Serialization/ModuleFormat.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 923; // debug values
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 924; // ExtensibleEnums feature
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///
@@ -974,6 +974,7 @@ namespace options_block {
974974
PUBLIC_MODULE_NAME,
975975
SWIFT_INTERFACE_COMPILER_VERSION,
976976
STRICT_MEMORY_SAFETY,
977+
EXTENSIBLE_ENUMS,
977978
};
978979

979980
using SDKPathLayout = BCRecordLayout<
@@ -1083,6 +1084,10 @@ namespace options_block {
10831084
SWIFT_INTERFACE_COMPILER_VERSION,
10841085
BCBlob // version tuple
10851086
>;
1087+
1088+
using ExtensibleEnumsLayout = BCRecordLayout<
1089+
EXTENSIBLE_ENUMS
1090+
>;
10861091
}
10871092

10881093
/// The record types within the input block.

lib/Serialization/Serialization.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,11 @@ void Serializer::writeHeader() {
11811181
static_cast<uint8_t>(M->getCXXStdlibKind()));
11821182
}
11831183

1184+
if (M->supportsExtensibleEnums()) {
1185+
options_block::ExtensibleEnumsLayout ExtensibleEnums(Out);
1186+
ExtensibleEnums.emit(ScratchRecord);
1187+
}
1188+
11841189
if (Options.SerializeOptionsForDebugging) {
11851190
options_block::SDKPathLayout SDKPath(Out);
11861191
options_block::XCCLayout XCC(Out);

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,8 @@ LoadedFile *SerializedModuleLoaderBase::loadAST(
10901090
if (!loadedModuleFile->getModulePackageName().empty()) {
10911091
M.setPackageName(Ctx.getIdentifier(loadedModuleFile->getModulePackageName()));
10921092
}
1093+
if (loadedModuleFile->supportsExtensibleEnums())
1094+
M.setSupportsExtensibleEnums();
10931095
M.setUserModuleVersion(loadedModuleFile->getUserModuleVersion());
10941096
M.setSwiftInterfaceCompilerVersion(
10951097
loadedModuleFile->getSwiftInterfaceCompilerVersion());
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/src)
3+
// RUN: split-file %s %t/src
4+
5+
/// Build the library
6+
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
7+
// RUN: -module-name Lib \
8+
// RUN: -emit-module-path %t/Lib.swiftmodule \
9+
// RUN: -enable-experimental-feature ExtensibleEnums
10+
11+
// Check that the errors are produced when using enums from module with `ExtensibleEnums` feature enabled.
12+
// RUN: %target-swift-frontend -typecheck %t/src/TestChecking.swift \
13+
// RUN: -swift-version 5 -module-name Client -I %t \
14+
// RUN: -verify
15+
16+
// Test to make sure that if the library and client are in the same package enums are checked exhaustively
17+
18+
/// Build the library
19+
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
20+
// RUN: -module-name Lib \
21+
// RUN: -package-name Test \
22+
// RUN: -emit-module-path %t/Lib.swiftmodule \
23+
// RUN: -enable-experimental-feature ExtensibleEnums
24+
25+
// Different module but the same package
26+
// RUN: %target-swift-frontend -typecheck %t/src/TestSamePackage.swift \
27+
// RUN: -swift-version 5 -module-name Client -I %t \
28+
// RUN: -package-name Test \
29+
// RUN: -verify
30+
31+
// REQUIRES: swift_feature_ExtensibleEnums
32+
33+
//--- Lib.swift
34+
35+
public enum E {
36+
case a
37+
}
38+
39+
@frozen
40+
public enum F {
41+
case a
42+
case b
43+
}
44+
45+
func test_same_module(e: E, f: F) {
46+
switch e { // Ok
47+
case .a: break
48+
}
49+
50+
switch f { // Ok
51+
case .a: break
52+
case .b: break
53+
}
54+
}
55+
56+
//--- TestChecking.swift
57+
import Lib
58+
59+
func test(e: E, f: F) {
60+
// `E` is not marked as `@frozen` which means it gets new semantics
61+
62+
switch e {
63+
// expected-error@-1 {{switch covers known cases, but 'E' may have additional unknown values, possibly added in future versions}}
64+
// expected-note@-2 {{handle unknown values using "@unknown default"}}
65+
case .a: break
66+
}
67+
68+
switch e { // Ok (no warnings)
69+
case .a: break
70+
@unknown default: break
71+
}
72+
73+
// `F` is marked as `@frozen` which means regular rules apply even with `ExtensibleEnums` feature enabled.
74+
75+
switch f { // Ok (no errors because `F` is `@frozen`)
76+
case .a: break
77+
case .b: break
78+
}
79+
80+
switch f { // expected-error {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
81+
case .a: break
82+
}
83+
84+
switch f { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
85+
case .a: break
86+
@unknown default: break
87+
}
88+
}
89+
90+
//--- TestSamePackage.swift
91+
import Lib
92+
93+
func test_no_default(e: E, f: F) {
94+
switch e { // Ok
95+
case .a: break
96+
}
97+
98+
switch e { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.a'}}
99+
@unknown default: break
100+
}
101+
102+
switch f { // expected-error {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
103+
case .a: break
104+
}
105+
106+
switch f { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
107+
case .a: break
108+
@unknown default: break
109+
}
110+
}

0 commit comments

Comments
 (0)