mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
AST: Type-check @available attributes before synthesizing CaseIterable.
An `AvailableAttr` written in source with an unrecognized availability domain is now only marked invalid after type-checking the attribute. This resulted in a regression where `CaseIterable` synthesis was blocked incorrectly under the following very narrow circumstances: 1. Every `@available` attribute on the elements of the enum is invalid. 2. The module is being emitted and lazy type-checking is enabled. 3. The enum is public and the only top-level declaration in the file. Type-checking the attribute was delayed just enough that it would not be considered invalid by the type the `CaseIterable` conformance was being synthesized, resulting in a spurious error. There were zero tests exercising `CaseIterable` synthesis for enums with elements that have availability requirements, so I added some. Resolves rdar://144897917.
This commit is contained in:
@@ -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())
|
||||
|
||||
9
test/SILGen/lazy_typecheck_rdar144897917.swift
Normal file
9
test/SILGen/lazy_typecheck_rdar144897917.swift
Normal file
@@ -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
|
||||
@@ -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 {
|
||||
@@ -29,7 +28,3 @@ extension ImpliedMain: ImplierMain {}
|
||||
enum ImpliedOther: ImplierOther {
|
||||
case a(Int)
|
||||
}
|
||||
|
||||
enum CaseIterableAcrossFiles {
|
||||
case A
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
var hasher = Hasher()
|
||||
|
||||
enum Foo: CaseIterable {
|
||||
enum Foo {
|
||||
case A, B
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ 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 {
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -229,13 +219,6 @@ 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 {
|
||||
@@ -243,7 +226,6 @@ enum NotExplicitlyHashableAndCannotDerive {
|
||||
// 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
enum FromOtherFile { // expected-unsupported-note {{type declared here}}
|
||||
case a
|
||||
case b
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user