From dac06898e94b24b5a6b83533d2e0bae356a64529 Mon Sep 17 00:00:00 2001 From: Robert Widmann Date: Mon, 3 Oct 2016 01:28:28 -0400 Subject: [PATCH] [SE-0194] Deriving Collections of Enum Cases Implements the minimum specified by the SE-proposal. * Add the CaseIterable protocol with AllCases associatedtype and allCases requirement * Automatic synthesis occurs for "simple" enums - Caveat: Availability attributes suppress synthesis. This can be lifted in the future - Caveat: Conformance must be stated on the original type declaration (just like synthesizing Equatable/Hashable) - Caveat: Synthesis generates an [T]. A more efficient collection - possibly even a lazy one - should be put here. --- include/swift/AST/Decl.h | 13 +- include/swift/AST/DiagnosticsSema.def | 3 +- include/swift/AST/KnownIdentifiers.def | 2 + include/swift/AST/KnownProtocols.def | 1 + lib/AST/Decl.cpp | 22 +++ lib/IRGen/GenMeta.cpp | 1 + lib/Sema/CMakeLists.txt | 1 + lib/Sema/DerivedConformanceCaseIterable.cpp | 164 ++++++++++++++++++ lib/Sema/DerivedConformances.cpp | 17 +- lib/Sema/DerivedConformances.h | 19 ++ lib/Sema/TypeCheckDecl.cpp | 7 + lib/Sema/TypeCheckProtocol.cpp | 14 +- stdlib/public/core/CompilerProtocols.swift | 34 ++++ test/IDE/complete_enum_elements.swift | 6 +- ...=> enum_conformance_synthesis_other.swift} | 0 ...swift => enum_conformance_synthesis.swift} | 15 +- test/stdlib/CaseIterableTests.swift | 21 +++ 17 files changed, 327 insertions(+), 13 deletions(-) create mode 100644 lib/Sema/DerivedConformanceCaseIterable.cpp rename test/Sema/Inputs/{enum_equatable_hashable_other.swift => enum_conformance_synthesis_other.swift} (100%) rename test/Sema/{enum_equatable_hashable.swift => enum_conformance_synthesis.swift} (90%) create mode 100644 test/stdlib/CaseIterableTests.swift diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 938f6515ef417..f18d160e265c7 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -543,12 +543,16 @@ class alignas(1 << DeclAlignInBits) Decl { HasUnreferenceableStorage : 1 ); - SWIFT_INLINE_BITFIELD(EnumDecl, NominalTypeDecl, 2+2, + SWIFT_INLINE_BITFIELD(EnumDecl, NominalTypeDecl, 2+2+1, /// The stage of the raw type circularity check for this class. Circularity : 2, /// True if the enum has cases and at least one case has associated values. - HasAssociatedValues : 2 + HasAssociatedValues : 2, + /// True if the enum has at least one case that has some availability + /// attribute. A single bit because it's lazily computed along with the + /// HasAssociatedValues bit. + HasAnyUnavailableValues : 1 ); SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2, @@ -3220,6 +3224,11 @@ class EnumDecl final : public NominalTypeDecl { /// Note that this is true for enums with absolutely no cases. bool hasOnlyCasesWithoutAssociatedValues() const; + /// True if any of the enum cases have availability annotations. + /// + /// Note that this is false for enums with absolutely no cases. + bool hasPotentiallyUnavailableCaseValue() const; + /// True if the enum has cases. bool hasCases() const { return !getAllElements().empty(); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index b2b5d35717c3e..744a3e193d3f7 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2096,10 +2096,11 @@ NOTE(construct_raw_representable_from_unwrapped_value,none, "construct %0 from unwrapped %1 value", (Type, Type)) // Derived conformances - ERROR(cannot_synthesize_in_extension,none, "implementation of %0 cannot be automatically synthesized in an extension", (Type)) +ERROR(broken_case_iterable_requirement,none, + "CaseIterable protocol is broken: unexpected requirement", ()) ERROR(broken_raw_representable_requirement,none, "RawRepresentable protocol is broken: unexpected requirement", ()) ERROR(broken_equatable_requirement,none, diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 6f5d8e94d4649..c96b0086ae474 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -22,6 +22,8 @@ #define IDENTIFIER(name) IDENTIFIER_WITH_NAME(name, #name) #define IDENTIFIER_(name) IDENTIFIER_WITH_NAME(name, "_" #name) +IDENTIFIER(AllCases) +IDENTIFIER(allCases) IDENTIFIER(alloc) IDENTIFIER(allocWithZone) IDENTIFIER(allZeros) diff --git a/include/swift/AST/KnownProtocols.def b/include/swift/AST/KnownProtocols.def index d405c79f5c7c7..e19f7bc260106 100644 --- a/include/swift/AST/KnownProtocols.def +++ b/include/swift/AST/KnownProtocols.def @@ -57,6 +57,7 @@ PROTOCOL(Comparable) PROTOCOL(Error) PROTOCOL_(ErrorCodeProtocol) PROTOCOL(OptionSet) +PROTOCOL(CaseIterable) PROTOCOL_(BridgedNSError) PROTOCOL_(BridgedStoredNSError) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 69cebce6a6519..b48a3bc90c160 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2689,6 +2689,8 @@ EnumDecl::EnumDecl(SourceLoc EnumLoc, = static_cast(CircularityCheck::Unchecked); Bits.EnumDecl.HasAssociatedValues = static_cast(AssociatedValueCheck::Unchecked); + Bits.EnumDecl.HasAnyUnavailableValues + = false; } StructDecl::StructDecl(SourceLoc StructLoc, Identifier Name, SourceLoc NameLoc, @@ -3041,6 +3043,17 @@ EnumElementDecl *EnumDecl::getElement(Identifier Name) const { return nullptr; } +bool EnumDecl::hasPotentiallyUnavailableCaseValue() const { + switch (static_cast(Bits.EnumDecl.HasAssociatedValues)) { + case AssociatedValueCheck::Unchecked: + // Compute below + this->hasOnlyCasesWithoutAssociatedValues(); + LLVM_FALLTHROUGH; + default: + return static_cast(Bits.EnumDecl.HasAnyUnavailableValues); + } +} + bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const { // Check whether we already have a cached answer. switch (static_cast( @@ -3056,6 +3069,15 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const { return false; } for (auto elt : getAllElements()) { + for (auto Attr : elt->getAttrs()) { + if (auto AvAttr = dyn_cast(Attr)) { + if (!AvAttr->isInvalid()) { + const_cast(this)->Bits.EnumDecl.HasAnyUnavailableValues + = true; + } + } + } + if (elt->hasAssociatedValues()) { const_cast(this)->Bits.EnumDecl.HasAssociatedValues = static_cast(AssociatedValueCheck::HasAssociatedValues); diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index 3ed7bce9cd90c..b4694f70c6913 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -5856,6 +5856,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) { case KnownProtocolKind::RawRepresentable: case KnownProtocolKind::Equatable: case KnownProtocolKind::Hashable: + case KnownProtocolKind::CaseIterable: case KnownProtocolKind::Comparable: case KnownProtocolKind::ObjectiveCBridgeable: case KnownProtocolKind::DestructorSafeContainer: diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 325954f29a426..fb52162bab8dd 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -17,6 +17,7 @@ add_swift_library(swiftSema STATIC ConstraintGraph.cpp ConstraintLocator.cpp ConstraintSystem.cpp + DerivedConformanceCaseIterable.cpp DerivedConformanceCodable.cpp DerivedConformanceCodingKey.cpp DerivedConformanceEquatableHashable.cpp diff --git a/lib/Sema/DerivedConformanceCaseIterable.cpp b/lib/Sema/DerivedConformanceCaseIterable.cpp new file mode 100644 index 0000000000000..6961d6e7022a6 --- /dev/null +++ b/lib/Sema/DerivedConformanceCaseIterable.cpp @@ -0,0 +1,164 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements implicit derivation of the CaseIterable protocol. +// +//===----------------------------------------------------------------------===// + +#include "TypeChecker.h" +#include "swift/AST/Decl.h" +#include "swift/AST/Stmt.h" +#include "swift/AST/Expr.h" +#include "swift/AST/Types.h" +#include "llvm/Support/raw_ostream.h" +#include "DerivedConformances.h" + +using namespace swift; +using namespace DerivedConformance; + +/// Common preconditions for CaseIterable. +static bool canDeriveConformance(NominalTypeDecl *type) { + // The type must be an enum. + auto enumDecl = dyn_cast(type); + if (!enumDecl) + return false; + + // "Simple" enums without availability attributes can derive + // a CaseIterable conformance. + // + // FIXME: Lift the availability restriction. + return !enumDecl->hasPotentiallyUnavailableCaseValue() + && enumDecl->hasOnlyCasesWithoutAssociatedValues(); +} + +/// Derive the implementation of allCases for a "simple" no-payload enum. +void deriveCaseIterable_enum_getter(AbstractFunctionDecl *funcDecl) { + auto *parentDC = funcDecl->getDeclContext(); + auto *parentEnum = parentDC->getAsEnumOrEnumExtensionContext(); + auto enumTy = parentEnum->getDeclaredTypeInContext(); + auto &C = parentDC->getASTContext(); + + SmallVector elExprs; + for (EnumElementDecl *elt : parentEnum->getAllElements()) { + auto *ref = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true); + auto *base = TypeExpr::createImplicit(enumTy, C); + auto *apply = new (C) DotSyntaxCallExpr(ref, SourceLoc(), base); + elExprs.push_back(apply); + } + auto *arrayExpr = ArrayExpr::create(C, SourceLoc(), elExprs, {}, SourceLoc()); + + auto *returnStmt = new (C) ReturnStmt(SourceLoc(), arrayExpr); + auto *body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), + SourceLoc()); + funcDecl->setBody(body); +} + +static ArraySliceType *computeAllCasesType(NominalTypeDecl *enumType) { + auto metaTy = enumType->getDeclaredInterfaceType(); + if (!metaTy || metaTy->hasError()) + return nullptr; + + return ArraySliceType::get(metaTy->getRValueInstanceType()); +} + +static Type deriveCaseIterable_AllCases(TypeChecker &tc, Decl *parentDecl, + EnumDecl *enumDecl) { + // enum SomeEnum : CaseIterable { + // @derived + // typealias AllCases = [SomeEnum] + // } + auto *rawInterfaceType = computeAllCasesType(enumDecl); + return cast(parentDecl)->mapTypeIntoContext(rawInterfaceType); +} + +ValueDecl *DerivedConformance::deriveCaseIterable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *targetDecl, + ValueDecl *requirement) { + // Conformance can't be synthesized in an extension. + auto caseIterableProto + = tc.Context.getProtocol(KnownProtocolKind::CaseIterable); + auto caseIterableType = caseIterableProto->getDeclaredType(); + if (targetDecl != parentDecl) { + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + caseIterableType); + return nullptr; + } + + // Check that we can actually derive CaseIterable for this type. + if (!canDeriveConformance(targetDecl)) + return nullptr; + + // Build the necessary decl. + if (requirement->getBaseName() != tc.Context.Id_allCases) { + tc.diagnose(requirement->getLoc(), + diag::broken_case_iterable_requirement); + return nullptr; + } + + auto enumDecl = cast(targetDecl); + ASTContext &C = tc.Context; + + + // Define the property. + auto *returnTy = computeAllCasesType(targetDecl); + + VarDecl *propDecl; + PatternBindingDecl *pbDecl; + std::tie(propDecl, pbDecl) + = declareDerivedProperty(tc, parentDecl, enumDecl, C.Id_allCases, + returnTy, returnTy, + /*isStatic=*/true, /*isFinal=*/true); + + // Define the getter. + auto *getterDecl = addGetterToReadOnlyDerivedProperty(tc, propDecl, returnTy); + + getterDecl->setBodySynthesizer(&deriveCaseIterable_enum_getter); + + auto dc = cast(parentDecl); + dc->addMember(getterDecl); + dc->addMember(propDecl); + dc->addMember(pbDecl); + + return propDecl; +} + +Type DerivedConformance::deriveCaseIterable(TypeChecker &tc, Decl *parentDecl, + NominalTypeDecl *targetDecl, + AssociatedTypeDecl *assocType) { + // Conformance can't be synthesized in an extension. + auto caseIterableProto + = tc.Context.getProtocol(KnownProtocolKind::CaseIterable); + auto caseIterableType = caseIterableProto->getDeclaredType(); + if (targetDecl != parentDecl) { + tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension, + caseIterableType); + return nullptr; + } + + // We can only synthesize CaseIterable for enums. + auto enumDecl = dyn_cast(targetDecl); + if (!enumDecl) + return nullptr; + + // Check that we can actually derive CaseIterable for this type. + if (!canDeriveConformance(targetDecl)) + return nullptr; + + if (assocType->getName() == tc.Context.Id_AllCases) { + return deriveCaseIterable_AllCases(tc, parentDecl, enumDecl); + } + + tc.diagnose(assocType->getLoc(), + diag::broken_case_iterable_requirement); + return nullptr; +} + diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 610db73e59c8d..22d063df7726f 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -39,11 +39,18 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc, return enumDecl->hasRawType(); // Enums without associated values can implicitly derive Equatable and - // Hashable conformance. + // Hashable conformances. case KnownProtocolKind::Equatable: return canDeriveEquatable(tc, enumDecl, protocol); case KnownProtocolKind::Hashable: return canDeriveHashable(tc, enumDecl, protocol); + // "Simple" enums without availability attributes can explicitly derive + // a CaseIterable conformance. + // + // FIXME: Lift the availability restriction. + case KnownProtocolKind::CaseIterable: + return !enumDecl->hasPotentiallyUnavailableCaseValue() + && enumDecl->hasOnlyCasesWithoutAssociatedValues(); // @objc enums can explicitly derive their _BridgedNSError conformance. case KnownProtocolKind::BridgedNSError: @@ -135,6 +142,10 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, if (name.isSimpleName(ctx.Id_hashValue)) return getRequirement(KnownProtocolKind::Hashable); + // CaseIterable.allValues + if (name.isSimpleName(ctx.Id_allCases)) + return getRequirement(KnownProtocolKind::CaseIterable); + // _BridgedNSError._nsErrorDomain if (name.isSimpleName(ctx.Id_nsErrorDomain)) return getRequirement(KnownProtocolKind::BridgedNSError); @@ -192,6 +203,10 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, if (name.isSimpleName(ctx.Id_RawValue)) return getRequirement(KnownProtocolKind::RawRepresentable); + // CaseIterable.AllCases + if (name.isSimpleName(ctx.Id_AllCases)) + return getRequirement(KnownProtocolKind::CaseIterable); + return nullptr; } diff --git a/lib/Sema/DerivedConformances.h b/lib/Sema/DerivedConformances.h index 73616cb41f30e..2b5a6d1206fd8 100644 --- a/lib/Sema/DerivedConformances.h +++ b/lib/Sema/DerivedConformances.h @@ -71,6 +71,25 @@ ValueDecl *getDerivableRequirement(TypeChecker &tc, NominalTypeDecl *nominal, ValueDecl *requirement); + +/// Derive a CaseIterable requirement for an enum if it has no associated +/// values for any of its cases. +/// +/// \returns the derived member, which will also be added to the type. +ValueDecl *deriveCaseIterable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + ValueDecl *requirement); + +/// Derive a CaseIterable type witness for an enum if it has no associated +/// values for any of its cases. +/// +/// \returns the derived member, which will also be added to the type. +Type deriveCaseIterable(TypeChecker &tc, + Decl *parentDecl, + NominalTypeDecl *type, + AssociatedTypeDecl *assocType); + /// Derive a RawRepresentable requirement for an enum, if it has a valid /// raw type and raw values for all of its cases. /// diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index d7ddff33479b0..fb835f4f0e68a 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -9012,6 +9012,13 @@ void TypeChecker::synthesizeMemberForLookup(NominalTypeDecl *target, auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable); if (!evaluateTargetConformanceTo(decodableProto)) (void)evaluateTargetConformanceTo(encodableProto); + } else if (baseName.getIdentifier() == Context.Id_allCases || + baseName.getIdentifier() == Context.Id_AllCases) { + // If the target should conform to the CaseIterable protocol, check the + // conformance here to attempt synthesis. + auto *caseIterableProto + = Context.getProtocol(KnownProtocolKind::CaseIterable); + (void)evaluateTargetConformanceTo(caseIterableProto); } } else { auto argumentNames = member.getArgumentNames(); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 60354fd4b3ec2..cdb91bb2928b6 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4712,11 +4712,17 @@ ValueDecl *TypeChecker::deriveProtocolRequirement(DeclContext *DC, return DerivedConformance::deriveRawRepresentable(*this, Decl, TypeDecl, Requirement); + case KnownProtocolKind::CaseIterable: + return DerivedConformance::deriveCaseIterable(*this, Decl, + TypeDecl, Requirement); + case KnownProtocolKind::Equatable: - return DerivedConformance::deriveEquatable(*this, Decl, TypeDecl, Requirement); + return DerivedConformance::deriveEquatable(*this, Decl, TypeDecl, + Requirement); case KnownProtocolKind::Hashable: - return DerivedConformance::deriveHashable(*this, Decl, TypeDecl, Requirement); + return DerivedConformance::deriveHashable(*this, Decl, TypeDecl, + Requirement); case KnownProtocolKind::BridgedNSError: return DerivedConformance::deriveBridgedNSError(*this, Decl, TypeDecl, @@ -4752,7 +4758,9 @@ Type TypeChecker::deriveTypeWitness(DeclContext *DC, case KnownProtocolKind::RawRepresentable: return DerivedConformance::deriveRawRepresentable(*this, Decl, TypeDecl, AssocType); - + case KnownProtocolKind::CaseIterable: + return DerivedConformance::deriveCaseIterable(*this, Decl, + TypeDecl, AssocType); default: return nullptr; } diff --git a/stdlib/public/core/CompilerProtocols.swift b/stdlib/public/core/CompilerProtocols.swift index 7bf7735a6f21b..3d410120271f4 100644 --- a/stdlib/public/core/CompilerProtocols.swift +++ b/stdlib/public/core/CompilerProtocols.swift @@ -178,6 +178,40 @@ public func != (lhs: T, rhs: T) -> Bool return lhs.rawValue != rhs.rawValue } +/// A type that can produce a collection of all of its values. +/// +/// Simple Enumerations +/// =================== +/// +/// For any Swift enumeration where every case does not have an associated +/// value, the Swift compiler can automatically fill out the `CaseIterable` +/// conformance. When defining your own custom enumeration, specify a +/// conformance to `CaseIterable` to take advantage of this automatic +/// derivation. +/// +/// For example, every case of the `CardinalPoint` enumeration defined here +/// does not have an associated value: +/// +/// enum CardinalPoint: CaseIterable { +/// case north, south, east, west +/// } +/// +/// So the compiler automatically creates a conformance. +/// +/// for cardinality in CardinalPoint.allCases { +/// print(cardinality) +/// } +/// // Prints "north" +/// // Prints "south" +/// // Prints "east" +/// // Prints "west" +public protocol CaseIterable { + associatedtype AllCases: Collection + where AllCases.Element == Self + /// Returns a collection of all values of this type. + static var allCases: AllCases { get } +} + /// A type that can be initialized using the nil literal, `nil`. /// /// `nil` has a specific meaning in Swift---the absence of a value. Only the diff --git a/test/IDE/complete_enum_elements.swift b/test/IDE/complete_enum_elements.swift index 21b573010f855..39a2dae9b7d1c 100644 --- a/test/IDE/complete_enum_elements.swift +++ b/test/IDE/complete_enum_elements.swift @@ -73,7 +73,7 @@ //===--- Helper types. -enum FooEnum { +enum FooEnum: CaseIterable { case Foo1 case Foo2 } @@ -86,11 +86,15 @@ enum FooEnum { // FOO_ENUM_NO_DOT: Begin completions // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_NO_DOT-NEXT: Decl[EnumElement]/CurrNominal: .Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_NO_DOT-NEXT: Decl[TypeAlias]/CurrNominal: .AllCases[#[FooEnum]#]{{; name=.+$}} +// FOO_ENUM_NO_DOT-NEXT: Decl[StaticVar]/CurrNominal: .allCases[#[FooEnum]#]{{; name=.+$}} // FOO_ENUM_NO_DOT-NEXT: End completions // FOO_ENUM_DOT: Begin completions // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo1[#FooEnum#]{{; name=.+$}} // FOO_ENUM_DOT-NEXT: Decl[EnumElement]/CurrNominal: Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT-NEXT: Decl[TypeAlias]/CurrNominal: AllCases[#[FooEnum]#]{{; name=.+$}} +// FOO_ENUM_DOT-NEXT: Decl[StaticVar]/CurrNominal: allCases[#[FooEnum]#]{{; name=.+$}} // FOO_ENUM_DOT-NEXT: End completions // FOO_ENUM_DOT_ELEMENTS: Begin completions, 2 items diff --git a/test/Sema/Inputs/enum_equatable_hashable_other.swift b/test/Sema/Inputs/enum_conformance_synthesis_other.swift similarity index 100% rename from test/Sema/Inputs/enum_equatable_hashable_other.swift rename to test/Sema/Inputs/enum_conformance_synthesis_other.swift diff --git a/test/Sema/enum_equatable_hashable.swift b/test/Sema/enum_conformance_synthesis.swift similarity index 90% rename from test/Sema/enum_equatable_hashable.swift rename to test/Sema/enum_conformance_synthesis.swift index 1e765e5e9a93c..d7929b6304091 100644 --- a/test/Sema/enum_equatable_hashable.swift +++ b/test/Sema/enum_conformance_synthesis.swift @@ -1,17 +1,18 @@ // RUN: %empty-directory(%t) // RUN: cp %s %t/main.swift -// RUN: %target-swift-frontend -typecheck -verify -primary-file %t/main.swift %S/Inputs/enum_equatable_hashable_other.swift -verify-ignore-unknown +// RUN: %target-swift-frontend -typecheck -verify -primary-file %t/main.swift %S/Inputs/enum_conformance_synthesis_other.swift -verify-ignore-unknown -enum Foo { +enum Foo: CaseIterable { case A, B } if Foo.A == .B { } var aHash: Int = Foo.A.hashValue +_ = Foo.allCases Foo.A == Foo.B // expected-warning {{result of operator '==' is unused}} -enum Generic { +enum Generic: CaseIterable { case A, B static func method() -> Int { @@ -23,6 +24,7 @@ enum Generic { if Generic.A == .B { } var gaHash: Int = Generic.A.hashValue +_ = Generic.allCases func localEnum() -> Bool { enum Local { @@ -80,8 +82,7 @@ func overloadFromOtherFile() -> YetAnotherFromOtherFile { return .A } func overloadFromOtherFile() -> Bool { return false } if .A == overloadFromOtherFile() {} - -// Complex enums are not implicitly Equatable or Hashable. +// Complex enums are not automatically Equatable, Hashable, or CaseIterable. enum Complex { case A(Int) case B @@ -181,12 +182,15 @@ public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 3 {{non- // No explicit conformance; it could be derived, but we don't support extensions // yet. extension Complex : Hashable {} // expected-error 2 {{cannot be automatically synthesized in an extension}} +extension Complex : CaseIterable {} // expected-error {{type 'Complex' does not conform to protocol 'CaseIterable'}} +extension FromOtherFile: CaseIterable {} // expected-error {{cannot be automatically synthesized in an extension}} expected-error {{does not conform to protocol 'CaseIterable'}} // No explicit conformance and it cannot be derived. enum NotExplicitlyHashableAndCannotDerive { case A(NotHashable) } extension NotExplicitlyHashableAndCannotDerive : Hashable {} // expected-error 2 {{does not conform}} +extension NotExplicitlyHashableAndCannotDerive : CaseIterable {} // expected-error {{does not conform}} // Verify that conformance (albeit manually implemented) can still be added to // a type in a different file. @@ -198,6 +202,7 @@ extension OtherFileNonconforming: Hashable { } // ...but synthesis in a type defined in another file doesn't work yet. extension YetOtherFileNonconforming: Equatable {} // expected-error {{cannot be automatically synthesized in an extension}} +extension YetOtherFileNonconforming: CaseIterable {} // expected-error {{does not conform}} // Verify that an indirect enum doesn't emit any errors as long as its "leaves" // are conformant. diff --git a/test/stdlib/CaseIterableTests.swift b/test/stdlib/CaseIterableTests.swift new file mode 100644 index 0000000000000..35854e0a816bf --- /dev/null +++ b/test/stdlib/CaseIterableTests.swift @@ -0,0 +1,21 @@ +// RUN: %target-run-simple-swift %t +// REQUIRES: executable_test + +import StdlibUnittest + +var CaseIterableTests = TestSuite("CaseIterableTests") + +CaseIterableTests.test("Simple Enums") { + enum SimpleEnum: CaseIterable { + case bar + case baz + case quux + } + + expectEqual(SimpleEnum.allCases.count, 3) + expectTrue(SimpleEnum.allCases.contains(.bar)) + expectTrue(SimpleEnum.allCases.contains(.baz)) + expectTrue(SimpleEnum.allCases.contains(.quux)) +} + +runAllTests()