Skip to content

AST: Type-check @available attributes before synthesizing CaseIterable #79423

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 1 commit into from
Feb 17, 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
8 changes: 3 additions & 5 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6665,11 +6665,9 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
bool hasAssociatedValues = false;

for (auto elt : getAllElements()) {
for (auto Attr : elt->getAttrs()) {
if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
if (!AvAttr->isInvalid())
hasAnyUnavailableValues = true;
}
for (auto Attr : elt->getSemanticAvailableAttrs()) {
// FIXME: [availability] Deprecation doesn't make an element unavailable
hasAnyUnavailableValues = true;
}

if (!elt->isAvailableDuringLowering())
Expand Down
9 changes: 9 additions & 0 deletions test/SILGen/lazy_typecheck_rdar144897917.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %target-swift-frontend -emit-silgen %s -parse-as-library -enable-library-evolution -module-name Test -experimental-lazy-typecheck | %FileCheck %s

public enum E: CaseIterable {
case a
@available(deprecated, renamed: "a") // Intentionally invalid
case b
}

// CHECK: sil_witness_table E: CaseIterable module Test
5 changes: 0 additions & 5 deletions test/Sema/Inputs/enum_conformance_synthesis_other.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Note that for the test to be effective, each of these enums must only have
// its Equatable or Hashable conformance referenced /once/ in the primary file.
enum FromOtherFile : String {
// expected-note@-1 {{type declared here}}
case A = "a"
}
enum AlsoFromOtherFile : Int {
Expand Down Expand Up @@ -29,7 +28,3 @@ extension ImpliedMain: ImplierMain {}
enum ImpliedOther: ImplierOther {
case a(Int)
}

enum CaseIterableAcrossFiles {
case A
}
25 changes: 3 additions & 22 deletions test/Sema/enum_conformance_synthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@

var hasher = Hasher()

enum Foo: CaseIterable {
enum Foo {
case A, B
}

func foo() {
if Foo.A == .B { }
var _: Int = Foo.A.hashValue
Foo.A.hash(into: &hasher)
_ = Foo.allCases

Foo.A == Foo.B // expected-warning {{result of operator '==' is unused}}
}

enum Generic<T>: CaseIterable {
enum Generic<T> {
case A, B

static func method() -> Int {
Expand All @@ -29,7 +27,6 @@ func generic() {
if Generic<Foo>.A == .B { }
var _: Int = Generic<Foo>.A.hashValue
Generic<Foo>.A.hash(into: &hasher)
_ = Generic<Foo>.allCases
}

func localEnum() -> Bool {
Expand Down Expand Up @@ -98,7 +95,7 @@ func useEnumBeforeDeclaration() {
if .A == overloadFromOtherFile() {}
}

// Complex enums are not automatically Equatable, Hashable, or CaseIterable.
// Complex enums are not automatically Equatable, or Hashable.
enum Complex {
case A(Int)
case B
Expand Down Expand Up @@ -204,13 +201,6 @@ enum Instrument {

extension Instrument : Equatable {}

extension Instrument : CaseIterable {}

enum UnusedGeneric<T> {
case a, b, c
}
extension UnusedGeneric : CaseIterable {}

// Explicit conformance should work too
public enum Medicine {
case Antibiotic
Expand All @@ -229,21 +219,13 @@ enum Complex2 {
case B
}
extension Complex2 : Hashable {}
extension Complex2 : CaseIterable {} // expected-error {{type 'Complex2' does not conform to protocol 'CaseIterable'}} expected-note {{add stubs for conformance}}
extension FromOtherFile: CaseIterable {} // expected-error {{extension outside of file declaring enum 'FromOtherFile' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}} expected-note {{add stubs for conformance}}
extension CaseIterableAcrossFiles: CaseIterable {
public static var allCases: [CaseIterableAcrossFiles] {
return [ .A ]
}
}

// No explicit conformance and it cannot be derived.
enum NotExplicitlyHashableAndCannotDerive {
case A(NotHashable) //expected-note {{associated value type 'NotHashable' does not conform to protocol 'Hashable', preventing synthesized conformance of 'NotExplicitlyHashableAndCannotDerive' to 'Hashable'}}
// expected-note@-1 {{associated value type 'NotHashable' does not conform to protocol 'Equatable', preventing synthesized conformance of 'NotExplicitlyHashableAndCannotDerive' to 'Equatable'}}
}
extension NotExplicitlyHashableAndCannotDerive : Hashable {} // expected-error 2 {{does not conform}} expected-note {{add stubs for conformance}}
extension NotExplicitlyHashableAndCannotDerive : CaseIterable {} // expected-error {{does not conform}} expected-note {{add stubs for conformance}}

// Verify that conformance (albeit manually implemented) can still be added to
// a type in a different file.
Expand All @@ -255,7 +237,6 @@ extension OtherFileNonconforming: Hashable {
}
// ...but synthesis in a type defined in another file doesn't work yet.
extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring enum 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}} expected-note {{add stubs for conformance}}
extension YetOtherFileNonconforming: CaseIterable {} // expected-error {{does not conform}} expected-note {{add stubs for conformance}}

// Verify that an indirect enum doesn't emit any errors as long as its "leaves"
// are conformant.
Expand Down
4 changes: 4 additions & 0 deletions test/decl/protocol/special/Inputs/case_iterable_other.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
enum FromOtherFile { // expected-unsupported-note {{type declared here}}
case a
case b
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %target-swift-frontend -typecheck -verify -primary-file %s %S/../Inputs/case_iterable_other.swift

enum Simple: CaseIterable {
case a, b

static func staticMethod() -> Int {
return Self.allCases.count
}
}

enum Generic<T>: CaseIterable {
case a, b

static func staticMethod() -> Int {
return Self.allCases.count
}
}

enum InExtension {
case a, b
}

extension InExtension: CaseIterable {}

enum UnavailableCase: CaseIterable {
case a
@available(*, unavailable)
case b

public static var allCases: [UnavailableCase] {
return [.a]
}
}

extension FromOtherFile: CaseIterable {
public static var allCases: [FromOtherFile] {
return [.a, .b]
}
}

enum InvalidAvailableAttribute: CaseIterable {
case a
@available(deprecated, renamed: "a") // expected-warning {{unknown platform 'deprecated' for attribute 'available'}}
case b
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// RUN: %target-swift-frontend -typecheck -verify -primary-file %s %S/../Inputs/case_iterable_other.swift -verify-additional-prefix unsupported- -verify-ignore-unknown

extension FromOtherFile: CaseIterable {} // expected-error {{extension outside of file declaring enum 'FromOtherFile' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}} expected-note {{add stubs for conformance}}

enum NotCaseIterableAssociatedValues: CaseIterable { // expected-error {{type 'NotCaseIterableAssociatedValues' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a(Int)
case b
}

// FIXME: [availability] Deprecation should not block this conformance synthesis
enum NotCaseIterableUniversallyDeprecatedCase: CaseIterable { // expected-error {{type 'NotCaseIterableUniversallyDeprecatedCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(*, deprecated)
case b
}

enum NotCaseIterableUniversallyUnavailableCase: CaseIterable { // expected-error {{type 'NotCaseIterableUniversallyUnavailableCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(*, unavailable)
case b
}

enum NotCaseIterableSwiftIntroducedLaterCase: CaseIterable { // expected-error {{type 'NotCaseIterableSwiftIntroducedLaterCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(swift, introduced: 99)
case b
}

enum NotCaseIterableSwiftIntroducedEarlierCase: CaseIterable { // expected-error {{type 'NotCaseIterableSwiftIntroducedEarlierCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(swift, introduced: 4)
case b
}

enum NotCaseIterableSwiftObsoletedLaterCase: CaseIterable { // expected-error {{type 'NotCaseIterableSwiftObsoletedLaterCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(swift, obsoleted: 99)
case b
}

enum NotCaseIterableSwiftObsoletedEarlierCase: CaseIterable { // expected-error {{type 'NotCaseIterableSwiftObsoletedEarlierCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(swift, obsoleted: 4)
case b
}

enum NotCaseIterableMacOSUnavailableCase: CaseIterable { // expected-error {{type 'NotCaseIterableMacOSUnavailableCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(macOS, unavailable)
case b
}

enum NotCaseIterableMacOSPotentiallyUnavailableCase: CaseIterable { // expected-error {{type 'NotCaseIterableMacOSPotentiallyUnavailableCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(macOS, introduced: 99)
case b
}

enum NotCaseIterableMacOSObsoletedCase: CaseIterable { // expected-error {{type 'NotCaseIterableMacOSObsoletedCase' does not conform to protocol 'CaseIterable'}}
// expected-note@-1 {{add stubs for conformance}}
case a
@available(macOS, obsoleted: 10.9)
case b
}