Skip to content

[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

Merged
merged 1 commit into from
Mar 9, 2018

Conversation

CodaFi
Copy link
Contributor

@CodaFi CodaFi commented Jan 1, 2018

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.

@CodaFi CodaFi added the swift evolution pending discussion Flag → feature: A feature that has a Swift evolution proposal currently in review label Jan 1, 2018
@CodaFi
Copy link
Contributor Author

CodaFi commented Jan 1, 2018

恭賀新年! Happy New Year.

Poor swift-ci, never gets a break though.

@swift-ci please test

@swift-ci
Copy link
Contributor

swift-ci commented Jan 1, 2018

Build failed
Swift Test Linux Platform
Git Sha - a726f8f9fd574258701ca5fb89df8c0532c1987a

@swift-ci
Copy link
Contributor

swift-ci commented Jan 1, 2018

Build failed
Swift Test OS X Platform
Git Sha - a726f8f9fd574258701ca5fb89df8c0532c1987a

case garply
}

extension InExtension: ValueEnumerable {}
Copy link
Contributor

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.

Copy link
Contributor Author

@CodaFi CodaFi Jan 1, 2018

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.

@CodaFi
Copy link
Contributor Author

CodaFi commented Jan 1, 2018

@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
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -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]
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why creates?

@CodaFi CodaFi force-pushed the ace-attorney branch 2 times, most recently from 889794f to 7113a2b Compare March 1, 2018 04:26
@CodaFi CodaFi requested a review from DougGregor March 1, 2018 04:27
Copy link
Member

@DougGregor DougGregor left a 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(),
Copy link
Member

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,
Copy link
Member

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();
Copy link
Member

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();
Copy link
Member

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()});
Copy link
Member

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.

@CodaFi
Copy link
Contributor Author

CodaFi commented Mar 1, 2018

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.

@DougGregor
Copy link
Member

Sounds great!

@CodaFi CodaFi changed the title [DO NOT MERGE] Implement ValueEnumerable [SE-0194] Implement deriving collections of enum cases Mar 1, 2018
@CodaFi CodaFi force-pushed the ace-attorney branch 3 times, most recently from c216900 to c73c836 Compare March 1, 2018 17:44
return nullptr;
}

// We can only synthesize RawRepresentable for enums.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed one RawRepresentable here.

@@ -192,6 +203,10 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc,
if (name.isSimpleName(ctx.Id_RawValue))
return getRequirement(KnownProtocolKind::RawRepresentable);

// RawRepresentable.AllCases
Copy link
Contributor

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)
Copy link
Contributor

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 -typechecking only.

Copy link
Contributor Author

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

Copy link
Contributor

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) {
Copy link
Contributor

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.
Copy link
Contributor

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)
Copy link
Member

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>?

Copy link
Contributor Author

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.

@CodaFi
Copy link
Contributor Author

CodaFi commented Mar 9, 2018

With the acceptance I’m going to merge this through and file SRs to investigate the following outstanding issues:

  • SR-7151: A generalized implementation of synthesis that accounts for availability attributes and maintains the source-order invariant.
  • SR-7152: Potentially changing the Collection we default to from an Array.

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.
@CodaFi
Copy link
Contributor Author

CodaFi commented Mar 9, 2018

@swift-ci please smoke test

@CodaFi
Copy link
Contributor Author

CodaFi commented Mar 9, 2018

@swift-ci please test source compatibility

@CodaFi
Copy link
Contributor Author

CodaFi commented Mar 9, 2018

⛵️

@CodaFi CodaFi merged commit 72e4224 into swiftlang:master Mar 9, 2018
@CodaFi CodaFi deleted the ace-attorney branch March 9, 2018 17:43
@DougGregor
Copy link
Member

Awesome! @CodaFi , can you update the ChangeLog?

@Coeur
Copy link
Contributor

Coeur commented Mar 31, 2018

@CodaFi @DougGregor Is there a default implementation of CaseIterable for Hashable enums? For instance, could we add the following extension to Swift?

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 CaseIterable will already work by itself for Hashable enums?
What about enum that conforms to String?

@natecook1000
Copy link
Member

@Coeur Any enumeration without associated values gets its allCases requirement synthesized without any extra work, even when it has a raw value type like String:

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"]

@AnthonyLatsis AnthonyLatsis removed the swift evolution pending discussion Flag → feature: A feature that has a Swift evolution proposal currently in review label Mar 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants