Skip to content

Commit da3fa78

Browse files
committed
[AST/Sema] Add @extensible attribute on enum declarations
This attribute controls whether cross-module access to the declaration needs `@unknown default:` because it's allowed to gain new cases even if the module is non-resilient.
1 parent 4d28cc6 commit da3fa78

File tree

14 files changed

+100
-23
lines changed

14 files changed

+100
-23
lines changed

include/swift/AST/DeclAttr.def

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,12 @@ SIMPLE_DECL_ATTR(constInitialized, ConstInitialized,
879879
168)
880880
DECL_ATTR_FEATURE_REQUIREMENT(ConstInitialized, CompileTimeValues)
881881

882-
LAST_DECL_ATTR(ConstInitialized)
882+
SIMPLE_DECL_ATTR(extensible, Extensible,
883+
OnEnum,
884+
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | ForbiddenInABIAttr,
885+
169)
886+
887+
LAST_DECL_ATTR(Extensible)
883888

884889
#undef DECL_ATTR_ALIAS
885890
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8572,6 +8572,19 @@ GROUPED_WARNING(
85728572
"behavior",
85738573
(StringRef, DeclAttribute))
85748574

8575+
//===----------------------------------------------------------------------===//
8576+
// MARK: @extensible Attribute
8577+
//===----------------------------------------------------------------------===//
8578+
8579+
ERROR(extensible_attr_on_frozen_type,none,
8580+
"cannot use '@extensible' together with '@frozen'", ())
8581+
8582+
ERROR(extensible_attr_on_internal_type,none,
8583+
"'@extensible' attribute can only be applied to '@usableFromInline', "
8584+
"package, or public declarations, but %0 is "
8585+
"%select{private|fileprivate|internal|%error|%error|%error}1",
8586+
(DeclName, AccessLevel))
8587+
85758588
//===----------------------------------------------------------------------===//
85768589
// MARK: SwiftSettings
85778590
//===----------------------------------------------------------------------===//

lib/AST/ASTDumper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4925,6 +4925,7 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
49254925
TRIVIAL_ATTR_PRINTER(Used, used)
49264926
TRIVIAL_ATTR_PRINTER(WarnUnqualifiedAccess, warn_unqualified_access)
49274927
TRIVIAL_ATTR_PRINTER(WeakLinked, weak_linked)
4928+
TRIVIAL_ATTR_PRINTER(Extensible, extensible)
49284929

49294930
#undef TRIVIAL_ATTR_PRINTER
49304931

lib/AST/ASTPrinter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
402402
DeclAttrKind::RestatedObjCConformance,
403403
DeclAttrKind::NonSendable,
404404
DeclAttrKind::AllowFeatureSuppression,
405+
DeclAttrKind::Extensible,
405406
};
406407

407408
return result;

lib/AST/Decl.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6854,14 +6854,11 @@ bool EnumDecl::treatAsExhaustiveForDiags(const DeclContext *useDC) const {
68546854
if (enumModule->inSamePackage(useDC->getParentModule()))
68556855
return true;
68566856

6857-
// If the module where enum is declared supports extensible enumerations
6858-
// and this enum is not explicitly marked as "@frozen", cross-module
6859-
// access cannot be exhaustive and requires `@unknown default:`.
6860-
if (enumModule->supportsExtensibleEnums() &&
6861-
!getAttrs().hasAttribute<FrozenAttr>()) {
6862-
if (useDC != enumModule->getDeclContext())
6863-
return false;
6864-
}
6857+
// When the enum is marked as `@extensible` cross-module access
6858+
// cannot be exhaustive and requires `@unknown default:`.
6859+
if (getAttrs().hasAttribute<ExtensibleAttr>() &&
6860+
enumModule != useDC->getParentModule())
6861+
return false;
68656862
}
68666863

68676864
return isFormallyExhaustive(useDC);

lib/ASTGen/Sources/ASTGen/DeclAttrs.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ extension ASTGenVisitor {
228228
.dynamicCallable,
229229
.eagerMove,
230230
.exported,
231+
.extensible,
231232
.discardableResult,
232233
.disfavoredOverload,
233234
.dynamicMemberLookup,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
241241
}
242242
}
243243

244+
void visitExtensibleAttr(ExtensibleAttr *attr) {
245+
auto *E = cast<EnumDecl>(D);
246+
247+
if (D->getAttrs().hasAttribute<FrozenAttr>()) {
248+
diagnoseAndRemoveAttr(attr, diag::extensible_attr_on_frozen_type);
249+
return;
250+
}
251+
252+
if (E->getFormalAccess() < AccessLevel::Package &&
253+
!E->getAttrs().hasAttribute<UsableFromInlineAttr>()) {
254+
diagnoseAndRemoveAttr(attr, diag::extensible_attr_on_internal_type,
255+
E->getName(), E->getFormalAccess());
256+
return;
257+
}
258+
}
259+
244260
void visitAlignmentAttr(AlignmentAttr *attr) {
245261
// Alignment must be a power of two.
246262
auto value = attr->getValue();

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,7 @@ namespace {
16151615
UNINTERESTING_ATTR(Isolated)
16161616
UNINTERESTING_ATTR(Optimize)
16171617
UNINTERESTING_ATTR(Exclusivity)
1618+
UNINTERESTING_ATTR(Extensible)
16181619
UNINTERESTING_ATTR(NoLocks)
16191620
UNINTERESTING_ATTR(NoAllocation)
16201621
UNINTERESTING_ATTR(NoRuntime)

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,10 +1159,7 @@ namespace {
11591159
auto *enumModule = theEnum->getParentModule();
11601160
shouldIncludeFutureVersionComment =
11611161
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();
1162+
theEnum->getAttrs().hasAttribute<ExtensibleAttr>();
11661163
}
11671164
DE.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only,
11681165
subjectType, shouldIncludeFutureVersionComment)

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 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 = 933; // isConstantValue
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 934; // @extensible attribute
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///

test/IDE/complete_decl_attribute.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ struct _S {
325325
// ON_MEMBER_LAST-DAG: Keyword/None: freestanding[#Declaration Attribute#]; name=freestanding
326326
// ON_MEMBER_LAST-DAG: Keyword/None: storageRestrictions[#Declaration Attribute#]; name=storageRestrictions
327327
// ON_MEMBER_LAST-DAG: Keyword/None: lifetime[#Declaration Attribute#]; name=lifetime
328+
// ON_MEMBER_LAST-DAG: Keyword/None: extensible[#Declaration Attribute#]; name=extensible
328329
// ON_MEMBER_LAST-NOT: Keyword
329330
// ON_MEMBER_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct
330331
// ON_MEMBER_LAST-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: MyPropertyWrapper[#Property Wrapper#]; name=MyPropertyWrapper
@@ -397,6 +398,7 @@ func dummy2() {}
397398
// KEYWORD_LAST-DAG: Keyword/None: attached[#Declaration Attribute#]; name=attached
398399
// KEYWORD_LAST-DAG: Keyword/None: storageRestrictions[#Declaration Attribute#]; name=storageRestrictions
399400
// KEYWORD_LAST-DAG: Keyword/None: lifetime[#Declaration Attribute#]; name=lifetime
401+
// KEYWORD_LAST-DAG: Keyword/None: extensible[#Declaration Attribute#]; name=extensible
400402
// KEYWORD_LAST-NOT: Keyword
401403
// KEYWORD_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct
402404
// KEYWORD_LAST-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: MyGenericPropertyWrapper[#Property Wrapper#]; name=MyGenericPropertyWrapper

test/ModuleInterface/attrs.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,11 @@ public func testExecutionConcurrent() async {}
9494
@execution(caller)
9595
public func testExecutionCaller() async {}
9696
// CHECK: @execution(caller) public func testExecutionCaller() async
97+
98+
// CHECK-NOT: @extensible
99+
// CHECK: public enum TestExtensible
100+
@extensible
101+
public enum TestExtensible {
102+
case a
103+
case b
104+
}

test/ModuleInterface/extensible_enums.swift

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
/// Build the library
66
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
77
// RUN: -module-name Lib \
8-
// RUN: -emit-module-path %t/Lib.swiftmodule \
9-
// RUN: -enable-experimental-feature ExtensibleEnums
8+
// RUN: -emit-module-path %t/Lib.swiftmodule
109

1110
// Check that the errors are produced when using enums from module with `ExtensibleEnums` feature enabled.
1211
// RUN: %target-swift-frontend -typecheck %t/src/TestChecking.swift \
@@ -19,19 +18,17 @@
1918
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
2019
// RUN: -module-name Lib \
2120
// RUN: -package-name Test \
22-
// RUN: -emit-module-path %t/Lib.swiftmodule \
23-
// RUN: -enable-experimental-feature ExtensibleEnums
21+
// RUN: -emit-module-path %t/Lib.swiftmodule
2422

2523
// Different module but the same package
2624
// RUN: %target-swift-frontend -typecheck %t/src/TestSamePackage.swift \
2725
// RUN: -swift-version 5 -module-name Client -I %t \
2826
// RUN: -package-name Test \
2927
// RUN: -verify
3028

31-
// REQUIRES: swift_feature_ExtensibleEnums
32-
3329
//--- Lib.swift
3430

31+
@extensible
3532
public enum E {
3633
case a
3734
}
@@ -57,10 +54,10 @@ func test_same_module(e: E, f: F) {
5754
import Lib
5855

5956
func test(e: E, f: F) {
60-
// `E` is not marked as `@frozen` which means it gets new semantics
57+
// `E` is marked as `@extensible` which means it gets new semantics
6158

6259
switch e {
63-
// expected-error@-1 {{switch covers known cases, but 'E' may have additional unknown values, possibly added in future versions}}
60+
// expected-warning@-1 {{switch covers known cases, but 'E' may have additional unknown values, possibly added in future versions; this is an error in the Swift 6 language mode}}
6461
// expected-note@-2 {{handle unknown values using "@unknown default"}}
6562
case .a: break
6663
}
@@ -70,7 +67,7 @@ func test(e: E, f: F) {
7067
@unknown default: break
7168
}
7269

73-
// `F` is marked as `@frozen` which means regular rules apply even with `ExtensibleEnums` feature enabled.
70+
// `F` is marked as `@frozen` which means regular rules apply.
7471

7572
switch f { // Ok (no errors because `F` is `@frozen`)
7673
case .a: break

test/attr/attr_extensible.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
@extensible
4+
public enum E1 { // Ok
5+
}
6+
7+
@extensible // expected-error {{'@extensible' attribute can only be applied to '@usableFromInline', package, or public declarations, but 'E2' is fileprivate}}
8+
fileprivate enum E2 {}
9+
10+
@extensible // expected-error {{cannot use '@extensible' together with '@frozen'}}
11+
@frozen
12+
public enum E3 {
13+
}
14+
15+
@extensible
16+
@usableFromInline
17+
enum E4 {} // Ok
18+
19+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
20+
struct Test {
21+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
22+
var v: Int {
23+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
24+
get { 0 }
25+
}
26+
27+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
28+
var v2: String = ""
29+
30+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
31+
func test() {}
32+
33+
@extensible // expected-error {{@extensible may only be used on 'enum' declarations}}
34+
subscript(a: Int) -> Bool {
35+
get { false }
36+
set { }
37+
}
38+
}

0 commit comments

Comments
 (0)