diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index ba2e8c28a28..f82320d0237 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -876,6 +876,11 @@ SIMPLE_DECL_ATTR(constInitialized, ConstInitialized, 168) DECL_ATTR_FEATURE_REQUIREMENT(ConstInitialized, CompileTimeValues) +SIMPLE_DECL_ATTR(extensible, Extensible, + OnEnum, + ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | ForbiddenInABIAttr, + 169) + SIMPLE_DECL_ATTR(concurrent, Concurrent, OnFunc | OnConstructor | OnSubscript | OnVar, ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UnconstrainedInABIAttr, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 0c646d2c934..d5166884132 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8711,6 +8711,19 @@ GROUPED_WARNING( "behavior", (StringRef)) +//===----------------------------------------------------------------------===// +// MARK: @extensible Attribute +//===----------------------------------------------------------------------===// + +ERROR(extensible_attr_on_frozen_type,none, + "cannot use '@extensible' together with '@frozen'", ()) + +ERROR(extensible_attr_on_internal_type,none, + "'@extensible' 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 //===----------------------------------------------------------------------===// diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 9a9ac6fb8e1..5c7dbecfdc2 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -5064,6 +5064,7 @@ public: 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(Concurrent, concurrent) #undef TRIVIAL_ATTR_PRINTER diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index e6cf506e37d..8fcf6674872 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -407,6 +407,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint, DeclAttrKind::RestatedObjCConformance, DeclAttrKind::NonSendable, DeclAttrKind::AllowFeatureSuppression, + DeclAttrKind::Extensible, }; return result; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index f96299cf1c0..912d8f2d546 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7027,14 +7027,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()) { - if (useDC != enumModule->getDeclContext()) - return false; - } + // When the enum is marked as `@extensible` cross-module access + // cannot be exhaustive and requires `@unknown default:`. + if (getAttrs().hasAttribute() && + enumModule != useDC->getParentModule()) + return false; } return isFormallyExhaustive(useDC); diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index 268b9f74d25..10c8dc1c731 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -228,6 +228,7 @@ extension ASTGenVisitor { .dynamicCallable, .eagerMove, .exported, + .extensible, .discardableResult, .disfavoredOverload, .dynamicMemberLookup, diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 00b5258e7c1..9dd08f517f7 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -247,6 +247,21 @@ public: } } + void visitExtensibleAttr(ExtensibleAttr *attr) { + auto *E = cast(D); + + if (D->getAttrs().hasAttribute()) { + diagnoseAndRemoveAttr(attr, diag::extensible_attr_on_frozen_type); + return; + } + + if (E->getFormalAccess() < AccessLevel::Package) { + diagnoseAndRemoveAttr(attr, diag::extensible_attr_on_internal_type, + E->getName(), E->getFormalAccess()); + return; + } + } + void visitAlignmentAttr(AlignmentAttr *attr) { // Alignment must be a power of two. auto value = attr->getValue(); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index e4bce4488a7..ffe9b84c67b 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1616,6 +1616,7 @@ namespace { UNINTERESTING_ATTR(Isolated) UNINTERESTING_ATTR(Optimize) UNINTERESTING_ATTR(Exclusivity) + UNINTERESTING_ATTR(Extensible) UNINTERESTING_ATTR(NoLocks) UNINTERESTING_ATTR(NoAllocation) UNINTERESTING_ATTR(NoRuntime) diff --git a/lib/Sema/TypeCheckSwitchStmt.cpp b/lib/Sema/TypeCheckSwitchStmt.cpp index 5e5faf5d0b3..cb87f469c97 100644 --- a/lib/Sema/TypeCheckSwitchStmt.cpp +++ b/lib/Sema/TypeCheckSwitchStmt.cpp @@ -1159,10 +1159,7 @@ namespace { auto *enumModule = theEnum->getParentModule(); shouldIncludeFutureVersionComment = enumModule->isSystemModule() || - enumModule->supportsExtensibleEnums(); - // Since the module enabled `ExtensibleEnums` feature they - // opted-in all of their clients into exhaustivity errors. - shouldDowngradeToWarning = !enumModule->supportsExtensibleEnums(); + theEnum->getAttrs().hasAttribute(); } DE.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only, subjectType, shouldIncludeFutureVersionComment) diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 004131ee7bd..7ec7766274c 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 944; // nonisolated(nonsending) isolation was moved +const uint16_t SWIFTMODULE_VERSION_MINOR = 945; // @extensible attribute /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/test/IDE/complete_decl_attribute.swift b/test/IDE/complete_decl_attribute.swift index a7104e38d3d..4c0970de2f1 100644 --- a/test/IDE/complete_decl_attribute.swift +++ b/test/IDE/complete_decl_attribute.swift @@ -344,6 +344,7 @@ struct _S { // ON_MEMBER_LAST-DAG: Keyword/None: freestanding[#Declaration Attribute#]; name=freestanding // ON_MEMBER_LAST-DAG: Keyword/None: storageRestrictions[#Declaration Attribute#]; name=storageRestrictions // ON_MEMBER_LAST-DAG: Keyword/None: lifetime[#Declaration Attribute#]; name=lifetime +// ON_MEMBER_LAST-DAG: Keyword/None: extensible[#Declaration Attribute#]; name=extensible // ON_MEMBER_LAST-DAG: Keyword/None: concurrent[#Declaration Attribute#]; name=concurrent // ON_MEMBER_LAST-NOT: Keyword // ON_MEMBER_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct @@ -420,6 +421,7 @@ func dummy2() {} // KEYWORD_LAST-DAG: Keyword/None: attached[#Declaration Attribute#]; name=attached // KEYWORD_LAST-DAG: Keyword/None: storageRestrictions[#Declaration Attribute#]; name=storageRestrictions // KEYWORD_LAST-DAG: Keyword/None: lifetime[#Declaration Attribute#]; name=lifetime +// KEYWORD_LAST-DAG: Keyword/None: extensible[#Declaration Attribute#]; name=extensible // KEYWORD_LAST-DAG: Keyword/None: concurrent[#Declaration Attribute#]; name=concurrent // KEYWORD_LAST-NOT: Keyword // KEYWORD_LAST-DAG: Decl[Struct]/CurrModule: MyStruct[#MyStruct#]; name=MyStruct diff --git a/test/ModuleInterface/attrs.swift b/test/ModuleInterface/attrs.swift index 56da19cc2e3..c7d9481c2d1 100644 --- a/test/ModuleInterface/attrs.swift +++ b/test/ModuleInterface/attrs.swift @@ -112,3 +112,11 @@ public struct TestPlacementOfAttrsAndSpecifiers { // CHECK: public func test3(_: inout () async -> T) public func test3(_: inout () async -> T) {} } + +// CHECK-NOT: @extensible +// CHECK: public enum TestExtensible +@extensible +public enum TestExtensible { + case a + case b +} diff --git a/test/ModuleInterface/extensible_enums.swift b/test/ModuleInterface/extensible_enums.swift index 8f3d985a32a..e23a19b6eff 100644 --- a/test/ModuleInterface/extensible_enums.swift +++ b/test/ModuleInterface/extensible_enums.swift @@ -5,8 +5,7 @@ /// Build the library // RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \ // RUN: -module-name Lib \ -// RUN: -emit-module-path %t/Lib.swiftmodule \ -// RUN: -enable-experimental-feature ExtensibleEnums +// RUN: -emit-module-path %t/Lib.swiftmodule // Check that the errors are produced when using enums from module with `ExtensibleEnums` feature enabled. // RUN: %target-swift-frontend -typecheck %t/src/TestChecking.swift \ @@ -19,8 +18,7 @@ // RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \ // RUN: -module-name Lib \ // RUN: -package-name Test \ -// RUN: -emit-module-path %t/Lib.swiftmodule \ -// RUN: -enable-experimental-feature ExtensibleEnums +// RUN: -emit-module-path %t/Lib.swiftmodule // Different module but the same package // RUN: %target-swift-frontend -typecheck %t/src/TestSamePackage.swift \ @@ -28,10 +26,9 @@ // RUN: -package-name Test \ // RUN: -verify -// REQUIRES: swift_feature_ExtensibleEnums - //--- Lib.swift +@extensible public enum E { case a } @@ -57,10 +54,10 @@ func test_same_module(e: E, f: F) { import Lib func test(e: E, f: F) { - // `E` is not marked as `@frozen` which means it gets new semantics + // `E` is marked as `@extensible` which means it gets new semantics switch e { - // expected-error@-1 {{switch covers known cases, but 'E' may have additional unknown values, possibly added in future versions}} + // 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}} // expected-note@-2 {{handle unknown values using "@unknown default"}} case .a: break } @@ -70,7 +67,7 @@ func test(e: E, f: F) { @unknown default: break } - // `F` is marked as `@frozen` which means regular rules apply even with `ExtensibleEnums` feature enabled. + // `F` is marked as `@frozen` which means regular rules apply. switch f { // Ok (no errors because `F` is `@frozen`) case .a: break diff --git a/test/attr/attr_extensible.swift b/test/attr/attr_extensible.swift new file mode 100644 index 00000000000..60eabf9574c --- /dev/null +++ b/test/attr/attr_extensible.swift @@ -0,0 +1,38 @@ +// RUN: %target-typecheck-verify-swift + +@extensible +public enum E1 { // Ok +} + +@extensible // expected-error {{'@extensible' attribute can only be applied to public or package declarations, but 'E2' is fileprivate}} +fileprivate enum E2 {} + +@extensible // expected-error {{cannot use '@extensible' together with '@frozen'}} +@frozen +public enum E3 { +} + +@extensible // expected-error {{'@extensible' attribute can only be applied to public or package declarations, but 'E4' is internal}} +@usableFromInline +enum E4 {} + +@extensible // expected-error {{@extensible may only be used on 'enum' declarations}} +struct Test { + @extensible // expected-error {{@extensible may only be used on 'enum' declarations}} + var v: Int { + @extensible // expected-error {{@extensible may only be used on 'enum' declarations}} + get { 0 } + } + + @extensible // expected-error {{@extensible may only be used on 'enum' declarations}} + var v2: String = "" + + @extensible // expected-error {{@extensible may only be used on 'enum' declarations}} + func test() {} + + @extensible // expected-error {{@extensible may only be used on 'enum' declarations}} + subscript(a: Int) -> Bool { + get { false } + set { } + } +}