-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SE-0194] Implement deriving collections of enum cases #13655
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
Conversation
恭賀新年! Happy New Year. Poor swift-ci, never gets a break though. @swift-ci please test |
Build failed |
Build failed |
case garply | ||
} | ||
|
||
extension InExtension: ValueEnumerable {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably have test cases to show that extensions can’t be used to derive a conformance for an enum from another module or imported from a header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal wasn't clear on this. Equatable, Hashable, and Codable all disallow synthesis in extensions even for the same module. If that's the desired behavior I can remove this section of the test.
@swift-ci please smoke test |
/// For any Swift enumeration where every case does not have an associated | ||
/// value, the Swift compiler can automatically fill out the `ValueEnumerable` | ||
/// conformance. When defining your own custom enumeration, specify a | ||
/// conformance to `ValueEnumerable` either in an extension or on the type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outdated: synthesizing in an extension no longer allowed
@@ -269,6 +269,20 @@ internal struct _StructMirror : _Mirror { | |||
internal var disposition: _MirrorDisposition { return .`struct` } | |||
} | |||
|
|||
@_inlineable // FIXME(sil-serialize-all) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does sil-serialize-all
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sil-serialize-all
is a precursor to @_inlineable
that stdlib used to build with. Does what it says on the package: all the SIL used to be serialized so the optimizer could work. The FIXME is because somebody needs to go through and evaluate whether all these functions are profitable to inline.
lib/AST/ASTContext.cpp
Outdated
@@ -215,7 +215,10 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL) | |||
/// func _stdlib_isOSVersionAtLeast(Builtin.Word,Builtin.Word, Builtin.word) | |||
// -> Builtin.Int1 | |||
FuncDecl *IsOSVersionAtLeastDecl = nullptr; | |||
|
|||
|
|||
/// func _stdlib_enumerateCaseValues<Enum: ValueEnumerable>(Enum.Type) -> [T] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should be -> [Enum]
. How did you choose this name? To me enumerate
sounds like a verb here, but I would expect a noun. Perhaps enumeration
or enum
? or even a get
prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enumerate
is the operative verb, as in “to determine the number of”. Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can decide whether the API Design Guidelines apply here, but:
Name functions and methods according to their side-effects
Those without side-effects should read as noun phrases, e.g.x.distance(to: y)
,i.successor()
.
public protocol ValueEnumerable { | ||
associatedtype ValueCollection: Collection | ||
where ValueCollection.Element == Self | ||
/// Creates a collection of all values of this type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why creates
?
889794f
to
7113a2b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor comments/requests, but this is looking great t me.
if (!metaTy || metaTy->hasError()) | ||
return nullptr; | ||
|
||
return BoundGenericType::get(arrayDecl, Type(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not build an ArraySliceType
to get the nice sugar here?
{metaTy->getRValueInstanceType()}); | ||
} | ||
|
||
static Type deriveRawRepresentable_AllCases(TypeChecker &tc, Decl *parentDecl, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... you mean deriveCaseIterable_AllCases
, not deriveRawRepresentable_AllCases
static BoundGenericType *computeAllCasesType(NominalTypeDecl *enumType) { | ||
auto *arrayDecl = enumType->getASTContext().getArrayDecl(); | ||
assert(arrayDecl); | ||
auto metaTy = enumType->getInterfaceType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean getDeclaredInterfaceType
here!
void deriveCaseIterable_enum_getter(AbstractFunctionDecl *funcDecl) { | ||
auto *parentDC = funcDecl->getDeclContext(); | ||
auto *parentEnum = parentDC->getAsEnumOrEnumExtensionContext(); | ||
auto enumTy = parentEnum->getDeclaredType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... How about using parentDC->getDeclaredTypeInContext()
here, so it does the right thing should we loosen the restriction on synthesis to support (e.g.) extensions in the same file.
return nullptr; | ||
|
||
return BoundGenericType::get(arrayDecl, Type(), | ||
{metaTy->getRValueInstanceType()}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, okay, you can getRValueInstanceType()
here, but I'd prefer that you use getDeclaredInterfaceType()
above instead.
I've addressed what you've reviewed so far. I'll cast off the [DO NOT MERGE] and squash the patch when I'm less sleep-deprived. |
Sounds great! |
c216900
to
c73c836
Compare
return nullptr; | ||
} | ||
|
||
// We can only synthesize RawRepresentable for enums. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed one RawRepresentable here.
lib/Sema/DerivedConformances.cpp
Outdated
@@ -192,6 +203,10 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc, | |||
if (name.isSimpleName(ctx.Id_RawValue)) | |||
return getRequirement(KnownProtocolKind::RawRepresentable); | |||
|
|||
// RawRepresentable.AllCases |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here.
@@ -23,6 +24,7 @@ enum Generic<T> { | |||
|
|||
if Generic<Foo>.A == .B { } | |||
var gaHash: Int = Generic<Foo>.A.hashValue | |||
print(Generic<Foo>.allCases) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this left over from debugging or intended to be included in this file? I suppose it doesn't matter since we're -typecheck
ing only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To suppress an unused lvalue warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think _ =
would be (mildly) preferable
if (!canDeriveConformance(targetDecl)) | ||
return nullptr; | ||
|
||
if (assocType->getName() == tc.Context.Id_AllCases) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the caller already check this?
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the comments on these two functions supposed to be identical? (I also find it a bit strange that the functions are named identically, but I realize that's a pattern that already exists in this file — nonetheless, did you consider something like deriveCaseIterable_property_allCases
and deriveCaseIterable_associatedtype_AllCases
? Is there some motivation I'm missing here for this naming convention?)
case quux | ||
} | ||
|
||
expectEqual(SimpleEnum.allCases.count, 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's type(of: SimpleEnum.allCases)
here? Is it just Array<SimpleEnum>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now. I hope we can come up with something more creative.
With the acceptance I’m going to merge this through and file SRs to investigate the following outstanding issues: |
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.
@swift-ci please smoke test |
@swift-ci please test source compatibility |
⛵️ |
Awesome! @CodaFi , can you update the ChangeLog? |
@CodaFi @DougGregor Is there a default implementation of extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
guard current.hashValue == raw else {
return nil
}
raw += 1
return current
}
})
}
} Or is it not needed, and |
@Coeur Any enumeration without associated values gets its enum Suit: String, CaseIterable {
case spades, hearts, clubs, diamonds
}
Suit.allCases
// [Suit.spades, Suit.hearts, Suit.clubs, Suit.diamonds]
Suit.allCases.map({ $0.rawValue })
// ["spades", "hearts", "clubs", "diamonds"] |
This is the corresponding implementation for swiftlang/swift-evolution#114 "Deriving collections of enum cases".
Prior Discussion
swift-evolution discussion thread (other related threads are linked in the proposal).
Proposal Text
Rendered Markdown.
Resolves #42653.